From 6c963683d54a63a0087e5e85dc08680f3463ba25 Mon Sep 17 00:00:00 2001 From: chandrashekhar reddy Date: Fri, 18 Oct 2024 12:16:02 +0530 Subject: [PATCH] QR Generator added --- .idea/inspectionProfiles/Project_Default.xml | 41 +++++ app/build.gradle.kts | 40 ++++- app/google-services.json | 48 ++++++ app/src/main/AndroidManifest.xml | 3 +- .../smi_attendance_admin/AppClass.kt | 12 ++ .../smi_attendance_admin/AppNavigation.kt | 44 +++++ .../smi_attendance_admin/MainActivity.kt | 38 ++--- .../smi_attendance_admin/MainScreen.kt | 31 ++++ .../data/dataStore/Employee.kt | 14 ++ .../smi_attendance_admin/di/AppModule.kt | 32 ++++ .../navigation/BottomNavBar.kt | 48 ++++++ .../navigation/NavAnimation.kt | 33 ++++ .../navigation/Navigation.kt | 27 +++ .../navigation/data/NavigationItem.kt | 23 +++ .../screens/home/data/EmployeeData.kt | 3 + .../screens/home/ui/HomeScreen.kt | 158 ++++++++++++++++++ .../screens/home/ui/componets/Card.kt | 2 + .../screens/home/ui/componets/LargeButton.kt | 40 +++++ .../screens/home/ui/componets/QRscreen.kt | 29 ++++ .../screens/home/ui/componets/Qrscanner.kt | 104 ++++++++++++ .../home/ui/componets/RequestPermission.kt | 27 +++ .../screens/home/viewModel/homeVM.kt | 38 +++++ .../screens/login/data/LoginData.kt | 7 + .../screens/login/data/LoginDataStore.kt | 54 ++++++ .../screens/login/ui/LoginScreen.kt | 150 +++++++++++++++++ .../components/OutlinedTextFiledWithErrors.kt | 55 ++++++ .../screens/login/viewModel/LoginViewModel.kt | 113 +++++++++++++ .../screens/settings/Settings.kt | 46 +++++ .../settings/components/TopComponents.kt | 74 ++++++++ .../settingsComponents/ClickableCard.kt | 125 ++++++++++++++ .../components/subSetting/AboutCard.kt | 91 ++++++++++ .../screens/settings/data/EmployeeData.kt | 7 + .../screens/settings/data/datastore.kt | 83 +++++++++ .../settings/viewModel/SettingsViewModel.kt | 29 ++++ .../main/res/drawable/baseline_close_24.xml | 5 + .../main/res/drawable/baseline_menu_24.xml | 5 + .../res/drawable/baseline_visibility_24.xml | 5 + .../drawable/baseline_visibility_off_24.xml | 5 + .../main/res/drawable/round_auto_mode_24.xml | 15 ++ .../main/res/drawable/round_color_lens_24.xml | 5 + .../main/res/drawable/round_contrast_24.xml | 5 + .../res/drawable/round_delete_sweep_24.xml | 5 + .../res/drawable/round_developer_mode_24.xml | 5 + app/src/main/res/drawable/round_info_24.xml | 5 + .../main/res/drawable/round_logo_dev_24.xml | 7 + .../main/res/drawable/round_translate_24.xml | 5 + app/src/main/res/drawable/smi_logo.png | Bin 0 -> 25267 bytes build.gradle.kts | 4 +- gradle/libs.versions.toml | 4 + 49 files changed, 1724 insertions(+), 25 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 app/google-services.json create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/AppClass.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/AppNavigation.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/MainScreen.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/data/dataStore/Employee.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/di/AppModule.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/BottomNavBar.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/NavAnimation.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/Navigation.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/navigation/data/NavigationItem.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/data/EmployeeData.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/HomeScreen.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/Card.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/LargeButton.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/QRscreen.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/Qrscanner.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/componets/RequestPermission.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/viewModel/homeVM.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/data/LoginData.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/data/LoginDataStore.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/ui/LoginScreen.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/ui/components/OutlinedTextFiledWithErrors.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/viewModel/LoginViewModel.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/Settings.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/TopComponents.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/settingsComponents/ClickableCard.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/components/subSetting/AboutCard.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/data/EmployeeData.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/data/datastore.kt create mode 100644 app/src/main/java/in/sminnovations/smi_attendance_admin/screens/settings/viewModel/SettingsViewModel.kt create mode 100644 app/src/main/res/drawable/baseline_close_24.xml create mode 100644 app/src/main/res/drawable/baseline_menu_24.xml create mode 100644 app/src/main/res/drawable/baseline_visibility_24.xml create mode 100644 app/src/main/res/drawable/baseline_visibility_off_24.xml create mode 100644 app/src/main/res/drawable/round_auto_mode_24.xml create mode 100644 app/src/main/res/drawable/round_color_lens_24.xml create mode 100644 app/src/main/res/drawable/round_contrast_24.xml create mode 100644 app/src/main/res/drawable/round_delete_sweep_24.xml create mode 100644 app/src/main/res/drawable/round_developer_mode_24.xml create mode 100644 app/src/main/res/drawable/round_info_24.xml create mode 100644 app/src/main/res/drawable/round_logo_dev_24.xml create mode 100644 app/src/main/res/drawable/round_translate_24.xml create mode 100644 app/src/main/res/drawable/smi_logo.png 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 0000000000000000000000000000000000000000..e3e2ea6516488655047e6808bdd8e9ba3ac20b3a GIT binary patch literal 25267 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 H>B?DX1f*_KLfrt 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 literal 0 HcmV?d00001 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" }