diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..44ca2d9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 33e1094..d653f13 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,10 @@ plugins { + kotlin("kapt") + id("com.google.dagger.hilt.android") + id("com.google.gms.google-services") alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) + } android { @@ -8,7 +12,7 @@ android { compileSdk = 34 defaultConfig { - applicationId = "in.sminnovations.smi_attendance_admin" + applicationId = "in.sminnovations.attendanceapp.admin" minSdk = 26 targetSdk = 34 versionCode = 1 @@ -47,6 +51,10 @@ android { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } + // Allow references to generated code + kapt { + correctErrorTypes = true + } } dependencies { @@ -59,6 +67,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 +76,32 @@ 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") + + // Import the Firebase BoM + implementation(platform("com.google.firebase:firebase-bom:33.4.0")) + // When using the BoM, you don't specify versions in Firebase library dependencies + // Add the dependency for the Firebase SDK for Google Analytics + implementation("com.google.firebase:firebase-analytics") + implementation("com.google.firebase:firebase-firestore") + //Qr code generator + implementation("com.journeyapps:zxing-android-embedded:4.3.0") } \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..7b39403 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,48 @@ +{ + "project_info": { + "project_number": "482294935697", + "project_id": "attendenceapp-57022", + "storage_bucket": "attendenceapp-57022.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:482294935697:android:07559f83f8f740be6c8f30", + "android_client_info": { + "package_name": "in.sminnovations.attendanceapp.admin" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyA0H9rJ8rML0JeBvN8rx0V2ijSA0GEfKFo" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:482294935697:android:89783c6f1e52abd76c8f30", + "android_client_info": { + "package_name": "in.sminnovations.attendanceapp.user" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyA0H9rJ8rML0JeBvN8rx0V2ijSA0GEfKFo" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1002775..b0d955e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/AppClass.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/AppClass.kt new file mode 100644 index 0000000..856a55c --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/AppClass.kt @@ -0,0 +1,12 @@ +package `in`.sminnovations.smi_attendance_admin +// 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/smi_attendance_admin/AppNavigation.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/AppNavigation.kt new file mode 100644 index 0000000..99fe862 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/AppNavigation.kt @@ -0,0 +1,44 @@ +package `in`.sminnovations.smi_attendance_admin + +import android.os.Build +import androidx.annotation.RequiresApi +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.smi_attendance_admin.screens.login.ui.LoginScreen + +@Composable +fun AppNavigation() { + val navController = rememberNavController() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + NavigationHost(navController = navController) + } +} + +annotation class Composable + +@RequiresApi(Build.VERSION_CODES.O_MR1) +@Composable +fun NavigationHost(navController: NavHostController) { + NavHost(navController = navController, startDestination = "login") { + composable("login") { + LoginScreen( + navController, + loginViewModel = hiltViewModel(), + onLoginSuccess = { + navController.navigate("main") { + popUpTo("login") { inclusive = true } + } + } + ) + } + + composable("main") { + MainScreen() + } + } +} + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/MainActivity.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/MainActivity.kt index aa2be12..bc6ca27 100644 --- a/app/src/main/java/in/sminnovations/smi_attendance_admin/MainActivity.kt +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/MainActivity.kt @@ -1,6 +1,7 @@ package `in`.sminnovations.smi_attendance_admin import android.os.Bundle +import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -11,37 +12,30 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import dagger.hilt.android.AndroidEntryPoint import `in`.sminnovations.smi_attendance_admin.ui.theme.SMIAdminAttendanceTheme +@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { SMIAdminAttendanceTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Greeting( - name = "Android", - modifier = Modifier.padding(innerPadding) - ) - } + AppNavigation() } } + + } + override fun onResume() { + super.onResume() + // Keep the screen on when the activity is in the foreground + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + override fun onPause() { + super.onPause() + // Remove the flag when the activity is not visible + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } } - -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - SMIAdminAttendanceTheme { - Greeting("Android") - } -} \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/MainScreen.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/MainScreen.kt new file mode 100644 index 0000000..59d6183 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/MainScreen.kt @@ -0,0 +1,31 @@ +package `in`.sminnovations.smi_attendance_admin + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import `in`.sminnovations.smi_attendance_admin.navigation.BottomNavigationBar +import `in`.sminnovations.smi_attendance_admin.navigation.Navigation +import `in`.sminnovations.smi_attendance_admin.navigation.data.navItems + + +@Composable +fun MainScreen( +) { + val navController = rememberNavController() + + Scaffold( + bottomBar = { + BottomNavigationBar(navController, navItems) + }, + content = { padding -> + Box(modifier = Modifier.padding(padding)) { + Navigation(navController = navController, navItems) + } + }, + ) +} + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/data/dataStore/Employee.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/data/dataStore/Employee.kt new file mode 100644 index 0000000..5b7c9fa --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/data/dataStore/Employee.kt @@ -0,0 +1,14 @@ +package `in`.sminnovations.smi_attendance_admin.data.dataStore + +data class Employee( + val androidID: String = "", + val dateOfJoining: String = "", + val dateOfLeaving: String = "", + val designation: String = "Android Developer", + val employeeStatus: Boolean = true, + val id: String = "", + val name: String = "", + val password: String = "", + val photo: String = "", + val dateOfBirth: String = "" +) diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/di/AppModule.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/di/AppModule.kt new file mode 100644 index 0000000..74dc833 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/di/AppModule.kt @@ -0,0 +1,32 @@ +package `in`.sminnovations.smi_attendance_admin.di + +import android.content.Context +import com.google.firebase.firestore.FirebaseFirestore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import `in`.sminnovations.smi_attendance_admin.screens.login.data.LoginDataStore +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + @Provides + @Singleton + fun provideLoginDataStore(@ApplicationContext context: Context): LoginDataStore { + return LoginDataStore(context) + } + @Provides + @Singleton + fun provideFirebase(): FirebaseFirestore { + return FirebaseFirestore.getInstance() + } + +// fun provideFirebase(): FirebaseFirestore { +// return FirebaseFirestore.getInstance() +// } + + +} diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/BottomNavBar.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/BottomNavBar.kt new file mode 100644 index 0000000..652755a --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/BottomNavBar.kt @@ -0,0 +1,48 @@ +package `in`.sminnovations.smi_attendance_admin.navigation + +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.navigation.NavController +import androidx.navigation.compose.currentBackStackEntryAsState +import `in`.sminnovations.smi_attendance_admin.navigation.data.BottomNavItem + + +@Composable +fun BottomNavigationBar( + navController: NavController, + items: List, +) { + NavigationBar { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route + items.forEach { item -> + NavigationBarItem(icon = { + BottomNavAnimatedIcon( + isSelected = currentRoute == item.route, + icon = item.icon, + selectedIcon = item.selectedIcon, + contentDescription = item.label + ) + }, + label = { Text(text = item.label) }, + alwaysShowLabel = true, + selected = currentRoute == item.route, + onClick = { + navController.navigate(item.route) { + navController.graph.startDestinationRoute?.let { route -> + popUpTo(route) { + saveState = true + } + } + launchSingleTop = true + restoreState = true + } + }) + } + } + +} + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/NavAnimation.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/NavAnimation.kt new file mode 100644 index 0000000..b7fcd22 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/NavAnimation.kt @@ -0,0 +1,33 @@ +package `in`.sminnovations.smi_attendance_admin.navigation + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector + + +@Composable +fun BottomNavAnimatedIcon( + isSelected: Boolean, + icon: ImageVector, + selectedIcon: ImageVector, + contentDescription: String? +) { + AnimatedContent( + targetState = isSelected, + transitionSpec = { + fadeIn(animationSpec = tween(300)) togetherWith fadeOut(animationSpec = tween(300)) + }, label = "animated icon" + ) { targetState -> + Icon( + imageVector = if (targetState) selectedIcon else icon, + contentDescription = contentDescription, + tint = if (targetState) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface + ) + } +} diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/Navigation.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/Navigation.kt new file mode 100644 index 0000000..6c38e2e --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/Navigation.kt @@ -0,0 +1,27 @@ +package `in`.sminnovations.smi_attendance_admin.navigation + +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import `in`.sminnovations.smi_attendance_admin.screens.home.ui.HomeScreen +import `in`.sminnovations.smi_attendance_admin.screens.settings.SettingsScreen +import `in`.sminnovations.smi_attendance_admin.navigation.data.BottomNavItem + + +@Composable +fun Navigation( + navController: NavHostController, + items: List, +) { + NavHost(navController, startDestination = items[0].route) { + composable(items[0].route) { + HomeScreen() + } + composable(items[1].route) { + SettingsScreen() + } + } + +} + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/data/NavigationItem.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/data/NavigationItem.kt new file mode 100644 index 0000000..73d1b13 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/data/NavigationItem.kt @@ -0,0 +1,23 @@ +package `in`.sminnovations.smi_attendance_admin.navigation.data + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddCircle +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.AddCircle +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.ui.graphics.vector.ImageVector + +data class BottomNavItem( + val icon: ImageVector, + val selectedIcon: ImageVector, + val label: String, + val route: String +) +val navItems = listOf( + BottomNavItem(Icons.Outlined.Home, Icons.Filled.Home, "Home", "home"), + BottomNavItem(Icons.Outlined.Settings, Icons.Filled.Settings, "Settings", "settings") +) \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/data/EmployeeData.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/data/EmployeeData.kt new file mode 100644 index 0000000..b9b391b --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/data/EmployeeData.kt @@ -0,0 +1,3 @@ +package `in`.sminnovations.smi_attendance_admin.screens.home.data + +class EmployeeData \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/HomeScreen.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/HomeScreen.kt new file mode 100644 index 0000000..360fa66 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/HomeScreen.kt @@ -0,0 +1,158 @@ +package `in`.sminnovations.smi_attendance_admin.screens.home.ui + +import android.Manifest +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.content.MediaType.Companion.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +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 com.google.zxing.BarcodeFormat +import com.journeyapps.barcodescanner.BarcodeEncoder +import `in`.sminnovations.smi_attendance_admin.screens.home.ui.componets.LargeButton +import `in`.sminnovations.smi_attendance_admin.screens.home.viewModel.HomeViewModel + + +@OptIn(ExperimentalPermissionsApi::class) +@Composable +fun HomeScreen(viewModel: HomeViewModel = viewModel()) { + val isScanning by viewModel.isScanning.collectAsState() + val qrCodeValue by viewModel.qrCodeValue.collectAsState() + + val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA) + val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) + + val allPermissionsGranted = remember { + derivedStateOf { + cameraPermissionState.status.isGranted && locationPermissionState.status.isGranted + } + } + + Box(modifier = Modifier.fillMaxSize()) { + + QRCodeGeneratorScreen() + } +} +@Composable +fun QRCodeGeneratorScreen() { + var qrCodeBitmap by remember { mutableStateOf(null) } + var isButtonVisible by remember { mutableStateOf(true) } + var isFullScreen by remember { mutableStateOf(false) } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + // Show the button if it hasn't been clicked yet + if (isButtonVisible) { + Button( + onClick = { + val qrData = "Your Data Here" // Replace this with the data you want to encode + qrCodeBitmap = generateQRCode(qrData) + isButtonVisible = false // Hide the button once QR is generated + }, + modifier = Modifier.padding(bottom = 16.dp) + ) { + Text(text = "Generate QR Code") + } + } + + // Display the QR code if it has been generated + qrCodeBitmap?.let { bitmap -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Generated QR Code", + modifier = Modifier + .size(250.dp) + .padding(16.dp) + .clickable { + isFullScreen = true // Open full-screen mode on click + } + ) + } + } + + // Full-Screen Dialog for QR Code + if (isFullScreen) { + Dialog(onDismissRequest = { isFullScreen = false }) { + Box( + modifier = Modifier + .background(color = MaterialTheme.colorScheme.onBackground, shape = RectangleShape) + .fillMaxSize() + .clickable { isFullScreen = false } // Close full screen when clicked + ) { + qrCodeBitmap?.let { bitmap -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Full Screen QR Code", + modifier = Modifier + .fillMaxSize() // Make the image fill the entire screen + .clickable { + isFullScreen = false // Close full-screen mode on click + } + ) + } + } + } + } +} + +// Function to generate QR Code bitmap with black background and white QR code +fun generateQRCode(data: String): Bitmap? { + return try { + val barcodeEncoder = BarcodeEncoder() + val qrBitmap = barcodeEncoder.encodeBitmap(data, BarcodeFormat.QR_CODE, 1080, 1080) + + // Create a new bitmap with black background + val finalBitmap = Bitmap.createBitmap(qrBitmap.width, qrBitmap.height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(finalBitmap) + val paint = Paint() + + // Set the background to black + paint.color = Color.BLACK + canvas.drawRect(0f, 0f, qrBitmap.width.toFloat(), qrBitmap.height.toFloat(), paint) + + // Draw the original QR code (white pixels) over the black background + canvas.drawBitmap(qrBitmap, 0f, 0f, null) + + finalBitmap + } catch (e: Exception) { + e.printStackTrace() + null + } +} \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/Card.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/Card.kt new file mode 100644 index 0000000..6d569db --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/Card.kt @@ -0,0 +1,2 @@ +package `in`.sminnovations.smi_attendance_admin.screens.home.ui.componets + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/LargeButton.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/LargeButton.kt new file mode 100644 index 0000000..3c5f48b --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/LargeButton.kt @@ -0,0 +1,40 @@ +package `in`.sminnovations.smi_attendance_admin.screens.home.ui.componets + +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun LargeButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Button( + onClick = onClick, + modifier = modifier + .height(56.dp) + .width(200.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ), + shape = RoundedCornerShape(28.dp) + ) { + Text( + text = text, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } +} + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/QRscreen.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/QRscreen.kt new file mode 100644 index 0000000..0526477 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/QRscreen.kt @@ -0,0 +1,29 @@ +package `in`.sminnovations.smi_attendance_admin.screens.home.ui.componets + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + + +@Composable +fun DefaultScreen( + qrCodeValue: String?, +) { + 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)) + } + } +} + + + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/Qrscanner.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/Qrscanner.kt new file mode 100644 index 0000000..de73a5c --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/Qrscanner.kt @@ -0,0 +1,104 @@ +package `in`.sminnovations.smi_attendance_admin.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.material3.OutlinedCard +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +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 + +@Composable +fun QRCodeScannerScreen( + onQRCodeScanned: (String) -> Unit, + lifecycleOwner: LifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current +) { + OutlinedCard( + modifier = Modifier.padding(10.dp) + ) { + 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/smi_attendance_admin/screens/home/ui/componets/RequestPermission.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/RequestPermission.kt new file mode 100644 index 0000000..1b69fb4 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/RequestPermission.kt @@ -0,0 +1,27 @@ +package `in`.sminnovations.smi_attendance_admin.screens.home.ui.componets + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.PermissionState +import com.google.accompanist.permissions.isGranted + +@OptIn(ExperimentalPermissionsApi::class) +@Composable +fun RequestPermissions( + cameraPermissionState: PermissionState, + locationPermissionState: PermissionState +) { + LaunchedEffect(cameraPermissionState.status.isGranted, locationPermissionState.status.isGranted) { + when { + !cameraPermissionState.status.isGranted -> { + cameraPermissionState.launchPermissionRequest() + } + !locationPermissionState.status.isGranted -> { + locationPermissionState.launchPermissionRequest() + } + } + } + +} + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/viewModel/homeVM.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/viewModel/homeVM.kt new file mode 100644 index 0000000..e28d551 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/viewModel/homeVM.kt @@ -0,0 +1,38 @@ +package `in`.sminnovations.smi_attendance_admin.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 + } + + 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/smi_attendance_admin/screens/login/data/LoginData.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/data/LoginData.kt new file mode 100644 index 0000000..23e48fa --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/data/LoginData.kt @@ -0,0 +1,7 @@ +package `in`.sminnovations.smi_attendance_admin.screens.login.data + +data class LoginData( + val id : String ="", + val password: String = "" +) + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/data/LoginDataStore.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/data/LoginDataStore.kt new file mode 100644 index 0000000..29ba605 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/data/LoginDataStore.kt @@ -0,0 +1,54 @@ +package `in`.sminnovations.smi_attendance_admin.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/smi_attendance_admin/screens/login/ui/LoginScreen.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/ui/LoginScreen.kt new file mode 100644 index 0000000..d995cb4 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/ui/LoginScreen.kt @@ -0,0 +1,150 @@ +package `in`.sminnovations.smi_attendance_admin.screens.login.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.Person +import androidx.compose.material3.Button +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 +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.res.painterResource +import androidx.compose.ui.text.input.PasswordVisualTransformation +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 androidx.navigation.NavController +import `in`.sminnovations.smi_attendance_admin.R +import `in`.sminnovations.smi_attendance_admin.screens.login.ui.components.OutlinedTextFieldWithError +import `in`.sminnovations.smi_attendance_admin.screens.login.viewModel.LoginViewModel +import kotlinx.coroutines.launch + +@Composable +fun LoginScreen( + navController: NavController, + loginViewModel: LoginViewModel = hiltViewModel(), + onLoginSuccess: () -> Unit +) { + val coroutineScope = rememberCoroutineScope() + val isPasswordError by loginViewModel.isPasswordError.collectAsState() + val isUserIdError by loginViewModel.isIdError.collectAsState() + val isLoggedIn by loginViewModel.isLoggedIn.collectAsState() + + var userId by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var passwordVisible by remember { mutableStateOf(false) } + + LaunchedEffect(isLoggedIn) { + if (isLoggedIn) onLoginSuccess() + } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp, vertical = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = "Attendance App", style = MaterialTheme.typography.headlineLarge) + Spacer(modifier = Modifier.height(40.dp)) + + Image( + painter = painterResource(id = R.drawable.smi_logo), + contentDescription = "App Logo", + modifier = Modifier + .fillMaxWidth(0.5f) + .aspectRatio(1f) + .padding(8.dp) + ) + + Spacer(modifier = Modifier.height(40.dp)) + Text(text = "Credentials", style = MaterialTheme.typography.headlineMedium) + + Spacer(modifier = Modifier.weight(1f)) + + OutlinedTextFieldWithError( + value = userId, + onValueChange = { userId = it }, + label = "User ID", + isError = isUserIdError, + errorMessage = "Please enter a valid User ID", + modifier = Modifier.fillMaxWidth(), + leadingIcon = { + Icon( + imageVector = Icons.Default.Person, + contentDescription = "User Icon" + ) + } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedTextFieldWithError( + value = password, + onValueChange = { password = it }, + label = "Password", + isError = isPasswordError, + errorMessage = "Please enter a valid password", + modifier = Modifier.fillMaxWidth(), + leadingIcon = { + Icon( + imageVector = Icons.Default.Lock, + contentDescription = "Password Icon" + ) + }, + trailingIcon = { + IconButton(onClick = { passwordVisible = !passwordVisible }) { + Icon( + painter = painterResource( + id = if (passwordVisible) R.drawable.baseline_visibility_24 else R.drawable.baseline_visibility_off_24 + ), + contentDescription = if (passwordVisible) "Hide Password" else "Show Password" + ) + } + }, + visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation() + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Button( + onClick = { + coroutineScope.launch { + loginViewModel.login(userId, password, onLoginSuccess) + } + }, + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + ) { + Text( + text = "Login", + fontSize = 20.sp + ) + } + + Spacer(modifier = Modifier.weight(1f)) + } +} + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/ui/components/OutlinedTextFiledWithErrors.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/ui/components/OutlinedTextFiledWithErrors.kt new file mode 100644 index 0000000..9f30d52 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/ui/components/OutlinedTextFiledWithErrors.kt @@ -0,0 +1,55 @@ +package `in`.sminnovations.smi_attendance_admin.screens.login.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun OutlinedTextFieldWithError( + value: String, + onValueChange: (String) -> Unit, + label: String, + isError: Boolean, + errorMessage: String, + modifier: Modifier = Modifier, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + visualTransformation: VisualTransformation = VisualTransformation.None +) { + Column(modifier = modifier) { + OutlinedTextField( + value = value, + onValueChange = onValueChange, + label = { Text(label) }, + isError = isError, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + visualTransformation = visualTransformation, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, + unfocusedBorderColor = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f), + focusedLabelColor = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, + unfocusedLabelColor = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant + ) + ) + if (isError) { + Text( + text = errorMessage, + color = MaterialTheme.colorScheme.error, + style = TextStyle(fontSize = 12.sp), + modifier = Modifier.padding(start = 16.dp, top = 4.dp) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/viewModel/LoginViewModel.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/viewModel/LoginViewModel.kt new file mode 100644 index 0000000..c55ce0b --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/viewModel/LoginViewModel.kt @@ -0,0 +1,113 @@ +package `in`.sminnovations.smi_attendance_admin.screens.login.viewModel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FirebaseFirestore +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.sminnovations.smi_attendance_admin.data.dataStore.Employee +import `in`.sminnovations.smi_attendance_admin.screens.login.data.LoginData +import `in`.sminnovations.smi_attendance_admin.screens.login.data.LoginDataStore +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +@HiltViewModel +class LoginViewModel @Inject constructor( + private val loginDataStore: LoginDataStore, private val firebaseFirestore: FirebaseFirestore +) : ViewModel() { + + private val _isPasswordError = MutableStateFlow(false) + val isPasswordError = _isPasswordError.asStateFlow() + + + private val _isIDError = MutableStateFlow(false) + val isIdError = _isIDError.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 { + firebaseFirestore.collection("employees").whereEqualTo("Id", userId).get() + .addOnSuccessListener { + if (!it.isEmpty) { + _isIDError.value = false + for (doc in it) { + if (password == doc.getString("Password")) { + viewModelScope.launch { + loginDataStore.saveLoginData(userId, password) + _isLoggedIn.value = true + onLoginSuccess() + + } + } else { + _isPasswordError.value = true + } + } + } else { + _isIDError.value = true + } + }.addOnFailureListener { + _isIDError.value = true + } + } + + } + + fun logout() { + viewModelScope.launch { + loginDataStore.clearLoginData() + _isLoggedIn.value = false + } + } + + + private fun DocumentSnapshot.toEmployee(): Employee { + return Employee( + androidID = getString("AndroidID") ?: "", + dateOfJoining = getString("DateOfJoining") ?: "", + dateOfLeaving = getString("DateOfLeaving") ?: "", + designation = getString("Designation") ?: "Android Developer", + employeeStatus = getBoolean("EmployeeStatus") ?: true, + id = getString("Id") ?: "", + name = getString("Name") ?: "", + password = getString("Password") ?: "", + photo = getString("Photo") ?: "", + dateOfBirth = getString("dateOfBirth") ?: "" + ) + } + + suspend fun fetchEmployeeData(employeeId: String): Employee? { + val db = FirebaseFirestore.getInstance() + return try { + val document = db.collection("employees").document(employeeId).get().await() + document.toEmployee() + } catch (e: Exception) { + e.printStackTrace() + null + } + } +} diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/Settings.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/Settings.kt new file mode 100644 index 0000000..9014157 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/Settings.kt @@ -0,0 +1,46 @@ +package `in`.sminnovations.smi_attendance_admin.screens.settings + + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.OutlinedCard +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import `in`.sminnovations.smi_attendance_admin.screens.login.viewModel.LoginViewModel +import `in`.sminnovations.smi_attendance_admin.screens.settings.components.TopComponents +import `in`.sminnovations.smi_attendance_admin.screens.settings.components.subSetting.AboutSettings +import `in`.sminnovations.smi_attendance_admin.screens.settings.viewModel.SettingsViewModel + +@Composable +fun SettingsScreen(settingsViewModel: SettingsViewModel = hiltViewModel()) { + val context = LocalContext.current + val scrollState = rememberScrollState() + Column( + modifier = Modifier + .verticalScroll(scrollState) + .fillMaxSize(), + ) { + Spacer(modifier = Modifier.height(20.dp)) + TopComponents() + Spacer(modifier = Modifier.height(20.dp)) + OutlinedCard( + modifier = Modifier + .padding(10.dp) + .wrapContentSize() + ) { + AboutSettings(context = context,settingsViewModel = settingsViewModel) + + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/TopComponents.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/TopComponents.kt new file mode 100644 index 0000000..cc47fa9 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/TopComponents.kt @@ -0,0 +1,74 @@ +package `in`.sminnovations.smi_attendance_admin.screens.settings.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 +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.sminnovations.smi_attendance_admin.R + + +@Composable +fun TopComponents( +) { + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = R.drawable.smi_logo), // replace with your image resource + contentDescription = "Profile Photo", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(150.dp) + .clip(RoundedCornerShape(16.dp)) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Sathwik C S", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + color = MaterialTheme.colorScheme.onSurface + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "ID: 69420", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 16.sp + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = "Android Developer", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary, + fontSize = 16.sp + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/settingsComponents/ClickableCard.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/settingsComponents/ClickableCard.kt new file mode 100644 index 0000000..a67b2fd --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/settingsComponents/ClickableCard.kt @@ -0,0 +1,125 @@ +package `in`.sminnovations.smi_attendance_admin.screens.settings.components.settingsComponents + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.indication +import androidx.compose.foundation.interaction.MutableInteractionSource +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + + + +@Composable +fun ClickableCard( + title:String, + subtitle:String, + onClick: (Boolean) -> Unit, + icon: ImageVector +) { + Row( + modifier = Modifier +// .background(MaterialTheme.colorScheme.surfaceContainer) + .clickable{ onClick(true)} + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + Column( + modifier = Modifier + .weight(1f) + ) { + Text( + text = title, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge, + ) + Text( + text = subtitle, + style = MaterialTheme.typography.bodyMedium, + ) + } + } + +} + + +@OptIn( ExperimentalFoundationApi::class) +@Composable +fun CombinedClickableCard( + title:String, + subtitle:String, + onClick: (Boolean) -> Unit, + onDoubleClick: ((Boolean) -> Unit?)? =null, + onLongClick: ((Boolean) -> Unit?)? =null, + icon: ImageVector +) { + val interactionSource = remember { MutableInteractionSource() } + Row( + modifier = Modifier +// .background(MaterialTheme.colorScheme.surfaceContainer) + .combinedClickable ( + onClick = { onClick(true) }, + onDoubleClick = { + if (onDoubleClick != null) { + onDoubleClick(true) + } + }, + onLongClick = { + if (onLongClick != null) { + onLongClick(true) + } + } + ) + .fillMaxWidth() + .padding(16.dp) + .indication(interactionSource,null) + , + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + Column( + modifier = Modifier + .weight(1f) + ) { + Text( + text = title, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge, + ) + Text( + text = subtitle, + style = MaterialTheme.typography.bodyMedium, + ) + } + } + +} diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/subSetting/AboutCard.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/subSetting/AboutCard.kt new file mode 100644 index 0000000..1dfa536 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/subSetting/AboutCard.kt @@ -0,0 +1,91 @@ +package `in`.sminnovations.smi_attendance_admin.screens.settings.components.subSetting + +import android.annotation.SuppressLint +import android.content.Context +import android.widget.Toast +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +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.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import `in`.sminnovations.smi_attendance_admin.R +import `in`.sminnovations.smi_attendance_admin.screens.settings.data.SettingsData +import `in`.sminnovations.smi_attendance_admin.screens.settings.components.settingsComponents.ClickableCard +import `in`.sminnovations.smi_attendance_admin.screens.settings.components.settingsComponents.CombinedClickableCard +import `in`.sminnovations.smi_attendance_admin.screens.settings.viewModel.SettingsViewModel +import kotlinx.coroutines.launch + + +@SuppressLint("CoroutineCreationDuringComposition") +@Composable +fun AboutSettings(context: Context, settingsViewModel : SettingsViewModel) +{ + val scope = rememberCoroutineScope() + val dataStore = SettingsData(context) + var clickCount by remember { mutableIntStateOf(0) } + if (clickCount > 5) { + scope.launch { + dataStore.setDevMode(true) + } + clickCount = 0 + Toast.makeText(context, "Developer mode is On : Scroll Down", Toast.LENGTH_SHORT).show() + } + Column(modifier = Modifier.padding(10.dp,0.dp,10.dp,10.dp)) { + Text( + text = "About", + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier + .align(Alignment.Start) + .padding(14.dp), + color = MaterialTheme.colorScheme.primary + ) + OutlinedCard{ + ClickableCard( + title = "Version", + subtitle = "1.0.04", + onClick = { + clickCount++ + }, + icon = ImageVector.vectorResource(id = R.drawable.round_info_24) + + ) + CombinedClickableCard( + title = "Developer", + subtitle = "SM Innovations Pvt Ltd", + onClick = { + }, onDoubleClick={ + + }, + icon = ImageVector.vectorResource(id = R.drawable.round_logo_dev_24) + ) + Button( + onClick = { + settingsViewModel.logout() // Call your logout function from ViewModel + }, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = 16.dp) + ) { + Text(text = "Logout") + } + + } + } + + +} + diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/data/EmployeeData.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/data/EmployeeData.kt new file mode 100644 index 0000000..260f5fb --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/data/EmployeeData.kt @@ -0,0 +1,7 @@ +package `in`.sminnovations.smi_attendance_admin.screens.settings.data + +class EmployeeDataSettings ( + val name : String = "", + val id : String = "", + val designation : String = "" +) \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/data/datastore.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/data/datastore.kt new file mode 100644 index 0000000..a476775 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/data/datastore.kt @@ -0,0 +1,83 @@ +package `in`.sminnovations.smi_attendance_admin.screens.settings.data + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + + +class SettingsData(private val context: Context) { + + companion object { + private val Context.dataStore: DataStore by preferencesDataStore("Settings") + val DYNAMIC_THEME_KEY = booleanPreferencesKey("dynamic_theme") + val BLACK_THEME_KEY = booleanPreferencesKey("black_theme") + val THEME_MODE_KEY = stringPreferencesKey("app_theme") + val DEV_MODE_TOGGLE_KEY = booleanPreferencesKey("dev_mode_toggle") + } + + + + val getDevModeToggle: Flow = context.dataStore.data + .map { preferences -> + preferences[DEV_MODE_TOGGLE_KEY] ?: false + } + + suspend fun setDevMode(mode: Boolean) { + context.dataStore.edit { preferences -> + preferences[DEV_MODE_TOGGLE_KEY] = mode + } + } + + + + + val getDynamicColorThemeValue: Flow = context.dataStore.data + .map { preferences -> + preferences[DYNAMIC_THEME_KEY] ?: true + } + + suspend fun setDynamicColorThemeValue(value: Boolean) { + context.dataStore.edit { preferences -> + preferences[DYNAMIC_THEME_KEY] = value + } + } + + + + + + + val getBlackThemeValue: Flow = context.dataStore.data + .map { preferences -> + preferences[BLACK_THEME_KEY] ?: false + } + + suspend fun setBlackThemeValue(value: Boolean) { + context.dataStore.edit { preferences -> + preferences[BLACK_THEME_KEY] = value + } + } + + + + + + val getThemeMode: Flow = context.dataStore.data + .map { preferences -> + preferences[THEME_MODE_KEY] ?: "System default" + } + + suspend fun saveThemeMode(theme: String) { + context.dataStore.edit { preferences -> + preferences[THEME_MODE_KEY] = theme + } + } + + +} diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/viewModel/SettingsViewModel.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/viewModel/SettingsViewModel.kt new file mode 100644 index 0000000..8e0120d --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/viewModel/SettingsViewModel.kt @@ -0,0 +1,29 @@ +package `in`.sminnovations.smi_attendance_admin.screens.settings.viewModel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.firestore.FirebaseFirestore +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.sminnovations.smi_attendance_admin.screens.login.data.LoginDataStore +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SettingsViewModel @Inject constructor( + private val loginDataStore: LoginDataStore, + private val firebaseFirestore: FirebaseFirestore +) : ViewModel() { + private val _isLoggedIn = MutableStateFlow(false) + val isLoggedIn = _isLoggedIn.asStateFlow() + + + fun logout() { + viewModelScope.launch { + loginDataStore.clearLoginData() + _isLoggedIn.value = false + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_close_24.xml b/app/src/main/res/drawable/baseline_close_24.xml new file mode 100644 index 0000000..f8ca0c6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_close_24.xml @@ -0,0 +1,5 @@ + + + + + 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/app/src/main/res/drawable/baseline_visibility_24.xml b/app/src/main/res/drawable/baseline_visibility_24.xml new file mode 100644 index 0000000..f843e29 --- /dev/null +++ b/app/src/main/res/drawable/baseline_visibility_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_visibility_off_24.xml b/app/src/main/res/drawable/baseline_visibility_off_24.xml new file mode 100644 index 0000000..5993ca3 --- /dev/null +++ b/app/src/main/res/drawable/baseline_visibility_off_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/round_auto_mode_24.xml b/app/src/main/res/drawable/round_auto_mode_24.xml new file mode 100644 index 0000000..ded3ee5 --- /dev/null +++ b/app/src/main/res/drawable/round_auto_mode_24.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/round_color_lens_24.xml b/app/src/main/res/drawable/round_color_lens_24.xml new file mode 100644 index 0000000..2f0ecdf --- /dev/null +++ b/app/src/main/res/drawable/round_color_lens_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/round_contrast_24.xml b/app/src/main/res/drawable/round_contrast_24.xml new file mode 100644 index 0000000..fe34a64 --- /dev/null +++ b/app/src/main/res/drawable/round_contrast_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/round_delete_sweep_24.xml b/app/src/main/res/drawable/round_delete_sweep_24.xml new file mode 100644 index 0000000..0b63d04 --- /dev/null +++ b/app/src/main/res/drawable/round_delete_sweep_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/round_developer_mode_24.xml b/app/src/main/res/drawable/round_developer_mode_24.xml new file mode 100644 index 0000000..2b7442f --- /dev/null +++ b/app/src/main/res/drawable/round_developer_mode_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/round_info_24.xml b/app/src/main/res/drawable/round_info_24.xml new file mode 100644 index 0000000..3218106 --- /dev/null +++ b/app/src/main/res/drawable/round_info_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/round_logo_dev_24.xml b/app/src/main/res/drawable/round_logo_dev_24.xml new file mode 100644 index 0000000..a65322b --- /dev/null +++ b/app/src/main/res/drawable/round_logo_dev_24.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/drawable/round_translate_24.xml b/app/src/main/res/drawable/round_translate_24.xml new file mode 100644 index 0000000..23fbbb0 --- /dev/null +++ b/app/src/main/res/drawable/round_translate_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/smi_logo.png b/app/src/main/res/drawable/smi_logo.png new file mode 100644 index 0000000..e3e2ea6 Binary files /dev/null and b/app/src/main/res/drawable/smi_logo.png differ diff --git a/build.gradle.kts b/build.gradle.kts index f74b04b..4d03a81 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,6 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false -} \ No newline at end of file + id("com.google.dagger.hilt.android") version "2.51.1" apply false + id("com.google.gms.google-services") version "4.4.2" apply false +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e160a8..d60bb8d 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 = "2024.04.01" +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" }