diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..fe03d1a 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -5,6 +5,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1299f51..a0c3469 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b061744..c666a81 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,8 +1,12 @@
-
+
+
+
+
-
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/AppClass.kt b/app/src/main/java/in/sminnovations/attendanceapp/AppClass.kt
new file mode 100644
index 0000000..ca74c74
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/AppClass.kt
@@ -0,0 +1,13 @@
+package `in`.sminnovations.attendanceapp
+
+// File: MyApp.kt
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MyApp : Application(){
+
+}
+
+
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/AppNavigation.kt b/app/src/main/java/in/sminnovations/attendanceapp/AppNavigation.kt
new file mode 100644
index 0000000..ac11a04
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/AppNavigation.kt
@@ -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()
+ }
+ }
+}
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/MainActivity.kt b/app/src/main/java/in/sminnovations/attendanceapp/MainActivity.kt
index 184a991..d7df789 100644
--- a/app/src/main/java/in/sminnovations/attendanceapp/MainActivity.kt
+++ b/app/src/main/java/in/sminnovations/attendanceapp/MainActivity.kt
@@ -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()
}
}
}
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/data/dataStore/datastore.kt b/app/src/main/java/in/sminnovations/attendanceapp/data/dataStore/datastore.kt
new file mode 100644
index 0000000..6cbec44
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/data/dataStore/datastore.kt
@@ -0,0 +1,2 @@
+package `in`.sminnovations.attendanceapp.data.dataStore
+
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/data/EmployeeData.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/data/EmployeeData.kt
new file mode 100644
index 0000000..c550d8f
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/data/EmployeeData.kt
@@ -0,0 +1,3 @@
+package `in`.sminnovations.attendanceapp.screens.home.data
+
+class EmployeeData
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/components/BottomComponents.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/components/BottomComponents.kt
similarity index 93%
rename from app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/components/BottomComponents.kt
rename to app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/components/BottomComponents.kt
index 4fb3bb0..08e6830 100644
--- a/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/components/BottomComponents.kt
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/components/BottomComponents.kt
@@ -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
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/components/TopComponents.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/components/TopComponents.kt
similarity index 69%
rename from app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/components/TopComponents.kt
rename to app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/components/TopComponents.kt
index edccda3..1fc566a 100644
--- a/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/components/TopComponents.kt
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/components/TopComponents.kt
@@ -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,
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/navigation.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/navigation.kt
new file mode 100644
index 0000000..484a2d3
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/ui/navigation.kt
@@ -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()
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/viewModel/navigationViewModel.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/viewModel/navigationViewModel.kt
new file mode 100644
index 0000000..af45e96
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/navigationDrawer/viewModel/navigationViewModel.kt
@@ -0,0 +1,4 @@
+package `in`.sminnovations.attendanceapp.screens.home.navigationDrawer.viewModel
+
+class navigationViewModel {
+}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/QRscreen.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/QRscreen.kt
new file mode 100644
index 0000000..5c683e5
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/QRscreen.kt
@@ -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()
+ }
+}
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/Qrscanner.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/Qrscanner.kt
new file mode 100644
index 0000000..178582f
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/Qrscanner.kt
@@ -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()
+ }
+}
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/asd.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/asd.kt
new file mode 100644
index 0000000..77c11d8
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/componets/asd.kt
@@ -0,0 +1,2 @@
+package `in`.sminnovations.attendanceapp.screens.home.ui.componets
+
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/home.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/home.kt
index 8633d99..d726f2f 100644
--- a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/home.kt
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/home.kt
@@ -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() }
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/viewModel/homeVM.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/viewModel/homeVM.kt
new file mode 100644
index 0000000..681182e
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/viewModel/homeVM.kt
@@ -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(null)
+ val qrCodeValue: StateFlow = _qrCodeValue.asStateFlow()
+
+ private val _isScanning = MutableStateFlow(false)
+ val isScanning: StateFlow = _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(){
+
+ }
+}
+
+
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/AppModule.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/AppModule.kt
new file mode 100644
index 0000000..827f0d0
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/AppModule.kt
@@ -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)
+ }
+}
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/LoginDataStore.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/LoginDataStore.kt
new file mode 100644
index 0000000..736ba07
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/LoginDataStore.kt
@@ -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 = dataStore.data.map { preferences ->
+ preferences[LOGIN_ID_KEY] ?: ""
+ }
+
+ val password: Flow = dataStore.data.map { preferences ->
+ preferences[PASSWORD_KEY] ?: ""
+ }
+
+ val isLoggedIn: Flow = 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
+ }
+ }
+}
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/loginData.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/loginData.kt
new file mode 100644
index 0000000..8fa54fa
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/data/loginData.kt
@@ -0,0 +1,6 @@
+package `in`.sminnovations.attendanceapp.screens.login.data
+
+data class LoginData(
+ val id : String ="",
+ val password: String = ""
+)
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/login/ui/login.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/ui/login.kt
index 64b04a8..8c705de 100644
--- a/app/src/main/java/in/sminnovations/attendanceapp/screens/login/ui/login.kt
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/ui/login.kt
@@ -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 {
-
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/login/viewModel/loginVM.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/viewModel/loginVM.kt
new file mode 100644
index 0000000..f7eb504
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/login/viewModel/loginVM.kt
@@ -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
+ }
+ }
+}
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/navigation.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/navigation.kt
deleted file mode 100644
index 3a09844..0000000
--- a/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/ui/navigation.kt
+++ /dev/null
@@ -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()
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/viewModel/navigationViewModel.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/viewModel/navigationViewModel.kt
deleted file mode 100644
index 2961c42..0000000
--- a/app/src/main/java/in/sminnovations/attendanceapp/screens/navigationDrawer/viewModel/navigationViewModel.kt
+++ /dev/null
@@ -1,4 +0,0 @@
-package `in`.sminnovations.attendanceapp.screens.navigationDrawer.viewModel
-
-class navigationViewModel {
-}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/ui/theme/Color.kt b/app/src/main/java/in/sminnovations/attendanceapp/ui/theme/Color.kt
index b049187..43ae79b 100644
--- a/app/src/main/java/in/sminnovations/attendanceapp/ui/theme/Color.kt
+++ b/app/src/main/java/in/sminnovations/attendanceapp/ui/theme/Color.kt
@@ -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)
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/attendanceapp/ui/theme/Type.kt b/app/src/main/java/in/sminnovations/attendanceapp/ui/theme/Type.kt
index 1361ad3..d299a71 100644
--- a/app/src/main/java/in/sminnovations/attendanceapp/ui/theme/Type.kt
+++ b/app/src/main/java/in/sminnovations/attendanceapp/ui/theme/Type.kt
@@ -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
)
diff --git a/app/src/main/res/drawable/baseline_menu_24.xml b/app/src/main/res/drawable/baseline_menu_24.xml
new file mode 100644
index 0000000..90a85a6
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_menu_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index f74b04b..5b2b703 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8b34422..1734bbc 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -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" }