login screen vm , qr detect ml lit implemented ,hilt implemented ,data store implemented ,side navigation implemented

This commit is contained in:
sathwikcs
2024-10-15 16:15:20 +05:30
parent 6018fd73d3
commit fa54dcba1c
29 changed files with 699 additions and 182 deletions

View File

@@ -5,6 +5,9 @@
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="lofpre">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

View File

@@ -1,6 +1,9 @@
plugins {
kotlin("kapt")
id("com.google.dagger.hilt.android")
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
}
android {
@@ -47,6 +50,10 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
// Allow references to generated code
kapt {
correctErrorTypes = true
}
}
dependencies {
@@ -59,6 +66,8 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.datastore.core.android)
implementation(libs.androidx.datastore.preferences.core.jvm)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@@ -66,4 +75,23 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation ("androidx.camera:camera-camera2:1.3.4")
implementation ( "androidx.camera:camera-lifecycle:1.3.4")
implementation ("androidx.camera:camera-view:1.3.4")
implementation ("com.google.mlkit:barcode-scanning:17.3.0")
implementation ("com.google.accompanist:accompanist-permissions:0.31.2-alpha")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")
implementation ("androidx.navigation:navigation-compose:2.7.2")
implementation("androidx.datastore:datastore-preferences:1.1.1")
implementation("com.google.dagger:hilt-android:2.51.1")
kapt("com.google.dagger:hilt-android-compiler:2.51.1")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
val nav_version = "2.8.2"
implementation("androidx.navigation:navigation-compose:$nav_version")
}

View File

@@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application
android:name=".MyApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@@ -15,11 +19,9 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.SMIAttendance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

View File

@@ -0,0 +1,13 @@
package `in`.sminnovations.attendanceapp
// File: MyApp.kt
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApp : Application(){
}

View File

@@ -0,0 +1,40 @@
package `in`.sminnovations.attendanceapp
import androidx.compose.runtime.Composable
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import `in`.sminnovations.attendanceapp.screens.login.ui.LoginScreen
import `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.ui.NavigationDrawer
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavigationHost(navController = navController)
}
@Composable
fun NavigationHost(navController: NavHostController) {
NavHost(navController = navController, startDestination = "login") {
// Login Screen Route
composable("login") {
LoginScreen(
loginViewModel = hiltViewModel(),
onLoginSuccess = {
// Navigate to the home screen after login success
navController.navigate("home") {
// Pop the login screen from the back stack
popUpTo("login") { inclusive = true }
}
}
)
}
composable("home") {
NavigationDrawer()
}
}
}

View File

@@ -4,16 +4,18 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import `in`.sminnovations.attendanceapp.screens.navigationDrawer.ui.NavigationDrawer
import dagger.hilt.android.AndroidEntryPoint
import `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.ui.NavigationDrawer
import `in`.sminnovations.attendanceapp.ui.theme.SMIAttendanceTheme
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
SMIAttendanceTheme {
NavigationDrawer()
AppNavigation()
}
}
}

View File

@@ -0,0 +1,2 @@
package `in`.sminnovations.attendanceapp.data.dataStore

View File

@@ -0,0 +1,3 @@
package `in`.sminnovations.attendanceapp.screens.home.data
class EmployeeData

View File

@@ -1,4 +1,4 @@
package `in`.sminnovations.attendanceapp.screens.navigationDrawer.ui.components
package `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth

View File

@@ -1,13 +1,17 @@
package `in`.sminnovations.attendanceapp.screens.navigationDrawer.ui.components
package `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -23,22 +27,37 @@ import `in`.sminnovations.attendanceapp.R
@Composable
fun TopComponents() {
fun TopComponents(
onclose : () -> Unit
) {
Column(
modifier = Modifier.fillMaxWidth().padding(8.dp),
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Image with rounded corners
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Absolute.Right
) {
IconButton(
onClick = {onclose()},
modifier = Modifier.padding(10.dp)
) {
Icon(painter = painterResource(id =R.drawable.baseline_close_24 ), contentDescription ="close" )
}
}
Image(
painter = painterResource(id = R.drawable.smi_logo), // replace with your image resource
contentDescription = "Profile Photo",
contentScale = ContentScale.Crop,
modifier = Modifier.size(100.dp).clip(RoundedCornerShape(16.dp))
modifier = Modifier
.size(150.dp)
.clip(RoundedCornerShape(16.dp))
)
Spacer(modifier = Modifier.height(16.dp))
// Name
Text(
text = "John Doe",
style = MaterialTheme.typography.titleMedium,
@@ -49,7 +68,6 @@ fun TopComponents() {
Spacer(modifier = Modifier.height(8.dp))
// ID
Text(
text = "ID: 123456",
style = MaterialTheme.typography.bodyMedium,
@@ -59,7 +77,6 @@ fun TopComponents() {
Spacer(modifier = Modifier.height(4.dp))
// Designation
Text(
text = "Software Engineer",
style = MaterialTheme.typography.bodyMedium,

View File

@@ -0,0 +1,74 @@
package `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.Scaffold
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import `in`.sminnovations.attendanceapp.R
import `in`.sminnovations.attendanceapp.screens.home.ui.Home
import `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.ui.components.BottomComponents
import `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.ui.components.TopComponents
import kotlinx.coroutines.launch
@Composable
fun NavigationDrawer() {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
fun openClose() {
scope.launch {
drawerState.apply {
if (isClosed) open() else close()
}
}
}
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Column(modifier = Modifier.clickable { openClose() }) {
TopComponents(onclose = {
scope.launch {
openClose()
}
})
Spacer(modifier = Modifier.weight(1f))
BottomComponents(appVersion = "1.0")
}
}
},
) {
Scaffold(topBar = {
Row {
IconButton(
onClick = { openClose() }, modifier = Modifier.padding(20.dp)
) {
Icon(
painter = painterResource(id = R.drawable.baseline_menu_24),
contentDescription = "menu"
)
}
}
}) { contentPadding ->
Column(modifier = Modifier.padding(contentPadding)) {
Home()
}
}
}
}

View File

@@ -0,0 +1,4 @@
package `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.viewModel
class navigationViewModel {
}

View File

@@ -0,0 +1,56 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted
@Composable
fun DefaultScreen(
qrCodeValue: String?,
onScanClick: () -> Unit,
onRetryClick: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
qrCodeValue?.let {
Text(text = "Scanned Value: $it", modifier = Modifier.padding(16.dp))
}
Button(onClick = if (qrCodeValue == null) onScanClick else onRetryClick) {
Text(if (qrCodeValue == null) "Scan" else "Retry")
}
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun RequestPermissions(
cameraPermissionState: PermissionState,
locationPermissionState: PermissionState
) {
val context = LocalContext.current
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
locationPermissionState.launchPermissionRequest()
}
if (!cameraPermissionState.status.isGranted) {
Toast.makeText(context, "Camera permission is required", Toast.LENGTH_LONG).show()
}
if (!locationPermissionState.status.isGranted) {
Toast.makeText(context, "Location permission is required", Toast.LENGTH_LONG).show()
}
}

View File

@@ -0,0 +1,100 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import android.annotation.SuppressLint
import android.util.Log
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
@SuppressLint("UnsafeOptInUsageError")
@Composable
fun QRCodeScannerScreen(
onQRCodeScanned: (String) -> Unit,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
) {
val context = LocalContext.current
val previewView = remember { PreviewView(context) }
LaunchedEffect(Unit) {
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().apply {
setSurfaceProvider(previewView.surfaceProvider)
}
val barcodeScanner = BarcodeScanning.getClient(
BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
)
val imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { analyzer ->
analyzer.setAnalyzer(ContextCompat.getMainExecutor(context)) { imageProxy ->
processImageProxy(imageProxy, barcodeScanner) { value ->
onQRCodeScanned(value)
}
}
}
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageAnalyzer
)
} catch (e: Exception) {
Log.e("QRCodeScanner", "Camera binding failed", e)
}
}
AndroidView(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f),
factory = { previewView }
)
}
@SuppressLint("UnsafeOptInUsageError")
private fun processImageProxy(
imageProxy: ImageProxy,
scanner: com.google.mlkit.vision.barcode.BarcodeScanner,
onQRCodeScanned: (String) -> Unit
) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(image)
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull()?.rawValue?.let { qrCodeValue ->
onQRCodeScanned(qrCodeValue)
}
}
.addOnFailureListener {
Log.e("QRCodeScanner", "Barcode processing failed", it)
}
.addOnCompleteListener {
imageProxy.close()
}
} else {
imageProxy.close()
}
}

View File

@@ -0,0 +1,2 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets

View File

@@ -1,8 +1,47 @@
package `in`.sminnovations.attendanceapp.screens.home.ui
import android.Manifest
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.DefaultScreen
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.QRCodeScannerScreen
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.RequestPermissions
import `in`.sminnovations.attendanceapp.screens.home.viewModel.HomeViewModel
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun Home(){
val viewModel: HomeViewModel = viewModel()
val isScanning by viewModel.isScanning.collectAsState()
val qrCodeValue by viewModel.qrCodeValue.collectAsState()
// Request permissions
val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
// Function to check if all permissions are granted
val allPermissionsGranted = cameraPermissionState.status.isGranted &&
locationPermissionState.status.isGranted
if (!allPermissionsGranted) {
RequestPermissions(
cameraPermissionState = cameraPermissionState,
locationPermissionState = locationPermissionState
)
} else if (isScanning) {
QRCodeScannerScreen(viewModel::setQRCodeValue)
} else {
DefaultScreen(
qrCodeValue = qrCodeValue,
onScanClick = { viewModel.startScanning() },
onRetryClick = { viewModel.startScanning() }
)
}
}

View File

@@ -0,0 +1,38 @@
package `in`.sminnovations.attendanceapp.screens.home.viewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class HomeViewModel : ViewModel() {
private val _qrCodeValue = MutableStateFlow<String?>(null)
val qrCodeValue: StateFlow<String?> = _qrCodeValue.asStateFlow()
private val _isScanning = MutableStateFlow(false)
val isScanning: StateFlow<Boolean> = _isScanning.asStateFlow()
fun startScanning() {
_isScanning.value = true
}
private fun stopScanning() {
_isScanning.value = false
}
fun setQRCodeValue(value: String) {
viewModelScope.launch {
_qrCodeValue.emit(value)
stopScanning()
}
}
fun getEmployeeData(){
}
}

View File

@@ -0,0 +1,22 @@
package `in`.sminnovations.attendanceapp.screens.login.data
// File: AppModule.kt
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideLoginDataStore(@ApplicationContext context: Context): LoginDataStore {
return LoginDataStore(context)
}
}

View File

@@ -0,0 +1,54 @@
package `in`.sminnovations.attendanceapp.screens.login.data
// File: LoginDataStore.kt
import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
private const val DATASTORE_NAME = "user_prefs"
private val Context.dataStore by preferencesDataStore(name = DATASTORE_NAME)
@Singleton
class LoginDataStore @Inject constructor(@ApplicationContext context: Context) {
private val dataStore = context.dataStore
private val LOGIN_ID_KEY = stringPreferencesKey("login_id")
private val PASSWORD_KEY = stringPreferencesKey("password")
private val IS_LOGGED_IN_KEY = booleanPreferencesKey("is_logged_in")
val loginId: Flow<String> = dataStore.data.map { preferences ->
preferences[LOGIN_ID_KEY] ?: ""
}
val password: Flow<String> = dataStore.data.map { preferences ->
preferences[PASSWORD_KEY] ?: ""
}
val isLoggedIn: Flow<Boolean> = dataStore.data.map { preferences ->
preferences[IS_LOGGED_IN_KEY] ?: false
}
suspend fun saveLoginData(id: String, password: String) {
dataStore.edit { preferences ->
preferences[LOGIN_ID_KEY] = id
preferences[PASSWORD_KEY] = password
preferences[IS_LOGGED_IN_KEY] = true
}
}
suspend fun clearLoginData() {
dataStore.edit { preferences ->
preferences.remove(LOGIN_ID_KEY)
preferences.remove(PASSWORD_KEY)
preferences[IS_LOGGED_IN_KEY] = false
}
}
}

View File

@@ -0,0 +1,6 @@
package `in`.sminnovations.attendanceapp.screens.login.data
data class LoginData(
val id : String ="",
val password: String = ""
)

View File

@@ -1,13 +1,14 @@
package `in`.sminnovations.attendanceapp.screens.login.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
@@ -15,125 +16,125 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import `in`.sminnovations.attendanceapp.R
import kotlinx.coroutines.delay
import `in`.sminnovations.attendanceapp.screens.login.viewModel.LoginViewModel
import kotlinx.coroutines.launch
@Composable
fun LoginScreen(onLoginSuccess: () -> Unit) {
fun LoginScreen(
loginViewModel: LoginViewModel = hiltViewModel(),
onLoginSuccess: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val isLoading by loginViewModel.isLoading.collectAsState()
val isLoggedIn by loginViewModel.isLoggedIn.collectAsState()
var userId by remember { mutableStateOf(TextFieldValue("")) }
var password by remember { mutableStateOf(TextFieldValue("")) }
var showLoginButton by remember { mutableStateOf(false) }
var isButtonLoading by remember { mutableStateOf(false) }
var passwordVisible by remember { mutableStateOf(false) }
// Animated visibility for screen entrance
LaunchedEffect(Unit) {
delay(300) // Small delay before showing content
showLoginButton = true
// Navigate to the main screen if the user is already logged in
LaunchedEffect(isLoggedIn) {
if (isLoggedIn) onLoginSuccess()
}
AnimatedVisibility(
visible = showLoginButton,
enter = fadeIn(),
exit = fadeOut()
Column(
modifier = Modifier
.fillMaxSize()
.padding(26.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(26.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "Attendance App ", style = MaterialTheme.typography.headlineLarge)
Spacer(modifier = Modifier.height(50.dp))
Image(painter = painterResource(id = R.drawable.smi_logo), contentDescription = "Image", alignment = Alignment.TopCenter)
Spacer(modifier = Modifier.height(100.dp))
Text(text = "Credentials", style = MaterialTheme.typography.headlineMedium)
Text(text = "Attendance App", style = MaterialTheme.typography.headlineLarge)
Spacer(modifier = Modifier.height(50.dp))
Spacer(modifier = Modifier.height(20.dp))
// Logo Image
Image(
painter = painterResource(id = R.drawable.smi_logo),
contentDescription = "App Logo",
alignment = Alignment.TopCenter
)
OutlinedTextField(
value = userId,
onValueChange = { userId = it },
textStyle = MaterialTheme.typography.bodyLarge,
label = { Text("User ID") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next)
)
Spacer(modifier = Modifier.height(100.dp))
Text(text = "Credentials", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(30.dp))
Spacer(modifier = Modifier.height(20.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyLarge,
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
trailingIcon = {
val image = if (passwordVisible) painterResource(id = R.drawable.baseline_visibility_24) else painterResource(
id = R.drawable.baseline_visibility_off_24
)
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(painter = image, contentDescription = "Toggle Password Visibility")
}
}
)
// User ID TextField
OutlinedTextField(
value = userId,
onValueChange = { userId = it },
label = { Text("User ID") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next)
)
Spacer(modifier = Modifier.height(60.dp))
Spacer(modifier = Modifier.height(30.dp))
Button(
onClick = {
isButtonLoading = true
coroutineScope.launch {
delay(1000) // Simulate network call
onLoginSuccess()
isButtonLoading = false
}
},
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
enabled = !isButtonLoading
) {
if (isButtonLoading) {
CircularProgressIndicator(
color = Color.White,
modifier = Modifier.size(24.dp),
strokeWidth = 2.dp
)
// Password TextField
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
trailingIcon = {
val icon = if (passwordVisible) {
painterResource(id = R.drawable.baseline_visibility_24)
} else {
Text(
text = "Login",
fontSize = 20.sp
)
painterResource(id = R.drawable.baseline_visibility_off_24)
}
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(painter = icon, contentDescription = "Toggle Password Visibility")
}
}
)
Spacer(modifier = Modifier.height(60.dp))
// Login Button
Button(
onClick = {
coroutineScope.launch {
loginViewModel.login(userId.text, password.text, onLoginSuccess)
}
},
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
enabled = !isLoading
) {
if (isLoading) {
CircularProgressIndicator(
color = Color.White,
modifier = Modifier.size(24.dp),
strokeWidth = 2.dp
)
} else {
Text(
text = "Login",
fontSize = 20.sp
)
}
}
}
}
@Preview
@Composable
fun lofpre(){
LoginScreen {
}
}

View File

@@ -0,0 +1,66 @@
package `in`.sminnovations.attendanceapp.screens.login.viewModel
// File: LoginViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import `in`.sminnovations.attendanceapp.screens.login.data.LoginData
import `in`.sminnovations.attendanceapp.screens.login.data.LoginDataStore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
private val loginDataStore: LoginDataStore
) : ViewModel() {
private val _isLoading = MutableStateFlow(false)
val isLoading = _isLoading.asStateFlow()
private val _isLoggedIn = MutableStateFlow(false)
val isLoggedIn = _isLoggedIn.asStateFlow()
private val _loginData = MutableStateFlow(LoginData("", ""))
val loginData = _loginData.asStateFlow()
init {
viewModelScope.launch {
loginDataStore.isLoggedIn.collect { loggedIn ->
_isLoggedIn.value = loggedIn
if (loggedIn) {
loginDataStore.loginId.collect { id ->
loginDataStore.password.collect { password ->
_loginData.value = LoginData(id, password)
}
}
}
}
}
}
fun login(userId: String, password: String, onLoginSuccess: () -> Unit) {
viewModelScope.launch {
_isLoading.value = true
delay(1000) // Simulate network call
if (userId == "1" && password == "2") {
loginDataStore.saveLoginData(userId, password)
_isLoggedIn.value = true
onLoginSuccess()
}
_isLoading.value = false
}
}
fun logout() {
viewModelScope.launch {
loginDataStore.clearLoginData()
_isLoggedIn.value = false
}
}
}

View File

@@ -1,61 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.navigationDrawer.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import `in`.sminnovations.attendanceapp.screens.navigationDrawer.ui.components.BottomComponents
import kotlinx.coroutines.launch
import `in`.sminnovations.attendanceapp.screens.navigationDrawer.ui.components.TopComponents
@Composable
fun NavigationDrawer(){
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Column (
Modifier.fillMaxHeight(),
){
TopComponents()
BottomComponents(appVersion = "")
}
}
},
) {
Scaffold(
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show drawer") },
icon = { Icon(Icons.Filled.Add, contentDescription = "") },
onClick = {
scope.launch {
drawerState.apply {
if (isClosed) open() else close()
}
}
}
)
}
) { contentPadding ->
Column (modifier = Modifier.padding(contentPadding)){
`in`.sminnovations.attendanceapp.screens.home.ui.Home()
}
}
}
}

View File

@@ -1,4 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.navigationDrawer.viewModel
class navigationViewModel {
}

View File

@@ -2,10 +2,10 @@ package `in`.sminnovations.attendanceapp.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Purple80 = Color(0xFFFF9800)
val PurpleGrey80 = Color(0xFFFFCB7E)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Purple40 = Color(0xFFB86E02)
val PurpleGrey40 = Color(0xFF865307)
val Pink40 = Color(0xFF7D5260)

View File

@@ -11,7 +11,7 @@ val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
fontSize = 18.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
</vector>

View File

@@ -2,4 +2,5 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
id("com.google.dagger.hilt.android") version "2.51.1" apply false
}

View File

@@ -8,6 +8,8 @@ espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.6"
activityCompose = "1.9.2"
composeBom = "2023.08.00"
datastoreCoreAndroid = "1.1.1"
datastorePreferencesCoreJvm = "1.1.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -24,6 +26,8 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-datastore-core-android = { group = "androidx.datastore", name = "datastore-core-android", version.ref = "datastoreCoreAndroid" }
androidx-datastore-preferences-core-jvm = { group = "androidx.datastore", name = "datastore-preferences-core-jvm", version.ref = "datastorePreferencesCoreJvm" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }