QR Generator added

This commit is contained in:
chandrashekhar reddy
2024-10-18 12:16:02 +05:30
parent f6b74c4743
commit 6c963683d5
49 changed files with 1724 additions and 25 deletions

View File

@@ -0,0 +1,41 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -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")
}

48
app/google-services.json Normal file
View File

@@ -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"
}

View File

@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MyApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@@ -13,7 +14,7 @@
android:theme="@style/Theme.SMIAdminAttendance"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name="in.sminnovations.smi_attendance_admin.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.SMIAdminAttendance">

View File

@@ -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(){
}

View File

@@ -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()
}
}
}

View File

@@ -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")
}
}

View File

@@ -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)
}
},
)
}

View File

@@ -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 = ""
)

View File

@@ -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()
// }
}

View File

@@ -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<BottomNavItem>,
) {
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
}
})
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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<BottomNavItem>,
) {
NavHost(navController, startDestination = items[0].route) {
composable(items[0].route) {
HomeScreen()
}
composable(items[1].route) {
SettingsScreen()
}
}
}

View File

@@ -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")
)

View File

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

View File

@@ -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<Bitmap?>(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
}
}

View File

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

View File

@@ -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
)
}
}

View File

@@ -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))
}
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}
}
}

View File

@@ -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<String?>(null)
val qrCodeValue: StateFlow<String?> = _qrCodeValue.asStateFlow()
private val _isScanning = MutableStateFlow(false)
val isScanning: StateFlow<Boolean> = _isScanning.asStateFlow()
fun startScanning() {
_isScanning.value = true
}
fun stopScanning() {
_isScanning.value = false
}
fun setQRCodeValue(value: String) {
viewModelScope.launch {
_qrCodeValue.emit(value)
stopScanning()
}
}
fun getEmployeeData(){
}
}

View File

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

View File

@@ -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<String> = dataStore.data.map { preferences ->
preferences[LOGIN_ID_KEY] ?: ""
}
val password: Flow<String> = dataStore.data.map { preferences ->
preferences[PASSWORD_KEY] ?: ""
}
val isLoggedIn: Flow<Boolean> = dataStore.data.map { preferences ->
preferences[IS_LOGGED_IN_KEY] ?: false
}
suspend fun saveLoginData(id: String, password: String) {
dataStore.edit { preferences ->
preferences[LOGIN_ID_KEY] = id
preferences[PASSWORD_KEY] = password
preferences[IS_LOGGED_IN_KEY] = true
}
}
suspend fun clearLoginData() {
dataStore.edit { preferences ->
preferences.remove(LOGIN_ID_KEY)
preferences.remove(PASSWORD_KEY)
preferences[IS_LOGGED_IN_KEY] = false
}
}
}

View File

@@ -0,0 +1,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))
}
}

View File

@@ -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)
)
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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,
)
}
}
}

View File

@@ -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")
}
}
}
}

View File

@@ -0,0 +1,7 @@
package `in`.sminnovations.smi_attendance_admin.screens.settings.data
class EmployeeDataSettings (
val name : String = "",
val id : String = "",
val designation : String = ""
)

View File

@@ -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<Preferences> 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<Boolean?> = 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<Boolean?> = 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<Boolean?> = 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<String> = 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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

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

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M18.06,2.83c-1.15,-0.77 -2.46,-1.32 -3.86,-1.61C13.58,1.1 13,1.57 13,2.21v0c0,0.46 0.31,0.88 0.76,0.97c1.17,0.23 2.26,0.7 3.21,1.34c0.39,0.26 0.9,0.19 1.23,-0.14l0,0C18.66,3.93 18.59,3.18 18.06,2.83z"/>
<path android:fillColor="@android:color/white" android:pathData="M11,2.21L11,2.21c0,-0.64 -0.58,-1.11 -1.2,-0.99c-1.4,0.29 -2.71,0.84 -3.86,1.61c-0.52,0.35 -0.59,1.1 -0.15,1.54l0,0c0.33,0.33 0.84,0.4 1.23,0.14c0.96,-0.64 2.04,-1.1 3.21,-1.34C10.69,3.09 11,2.67 11,2.21z"/>
<path android:fillColor="@android:color/white" android:pathData="M4.38,5.79L4.38,5.79C3.93,5.34 3.18,5.42 2.84,5.94C2.07,7.09 1.51,8.39 1.23,9.8C1.1,10.42 1.58,11 2.21,11h0c0.46,0 0.88,-0.31 0.97,-0.76c0.23,-1.17 0.7,-2.26 1.34,-3.22C4.77,6.64 4.7,6.12 4.38,5.79z"/>
<path android:fillColor="@android:color/white" android:pathData="M21.79,11L21.79,11c0.63,0 1.11,-0.58 0.98,-1.2c-0.29,-1.4 -0.84,-2.7 -1.61,-3.86c-0.35,-0.52 -1.1,-0.6 -1.54,-0.15l0,0c-0.33,0.33 -0.4,0.84 -0.14,1.23c0.64,0.96 1.1,2.05 1.34,3.22C20.91,10.69 21.33,11 21.79,11z"/>
<path android:fillColor="@android:color/white" android:pathData="M8,12.46l2.44,1.11L11.54,16c0.18,0.39 0.73,0.39 0.91,0l1.11,-2.44L16,12.46c0.39,-0.18 0.39,-0.73 0,-0.91l-2.44,-1.11L12.46,8c-0.18,-0.39 -0.73,-0.39 -0.91,0l-1.11,2.44L8,11.54C7.61,11.72 7.61,12.28 8,12.46z"/>
<path android:fillColor="@android:color/white" android:pathData="M12,21c-3.11,0 -5.85,-1.59 -7.46,-4H6c0.55,0 1,-0.45 1,-1v0c0,-0.55 -0.45,-1 -1,-1H2c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h0c0.55,0 1,-0.45 1,-1v-1.7c1.99,2.84 5.27,4.7 9,4.7c4.45,0 8.27,-2.64 10,-6.43c0.26,-0.57 -0.08,-1.25 -0.69,-1.39l0,0c-0.45,-0.1 -0.93,0.11 -1.12,0.54C18.77,18.83 15.64,21 12,21z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,22c5.52,0 10,-4.48 10,-10S17.52,2 12,2S2,6.48 2,12S6.48,22 12,22zM13,4.07c3.94,0.49 7,3.85 7,7.93s-3.05,7.44 -7,7.93V4.07z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M16,16h2c0.55,0 1,0.45 1,1s-0.45,1 -1,1h-2c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1zM16,8h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1h-5c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1zM16,12h4c0.55,0 1,0.45 1,1s-0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1zM3,18c0,1.1 0.9,2 2,2h6c1.1,0 2,-0.9 2,-2L13,8L3,8v10zM13,5h-2l-0.71,-0.71c-0.18,-0.18 -0.44,-0.29 -0.7,-0.29L6.41,4c-0.26,0 -0.52,0.11 -0.7,0.29L5,5L3,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h10c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M7,5h10v1c0,0.55 0.45,1 1,1s1,-0.45 1,-1L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v3c0,0.55 0.45,1 1,1s1,-0.45 1,-1L7,5zM16.12,15.88l3.17,-3.17c0.39,-0.39 0.39,-1.02 0,-1.41l-3.17,-3.17c-0.39,-0.39 -1.03,-0.39 -1.42,0 -0.39,0.39 -0.39,1.02 0,1.41L17.17,12l-2.47,2.47c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.03,0.39 1.42,0zM9.29,14.46L6.83,12l2.46,-2.46c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.03,-0.39 -1.42,0L4.7,11.3c-0.39,0.39 -0.39,1.02 0,1.41l3.17,3.17c0.39,0.39 1.03,0.39 1.42,0 0.4,-0.39 0.39,-1.03 0,-1.42zM17,19L7,19v-1c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-3c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v1z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,17c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v4c0,0.55 -0.45,1 -1,1zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM7.68,14.98H6V9h1.71c1.28,0 1.71,1.03 1.71,1.71l0,2.56C9.42,13.95 9,14.98 7.68,14.98zM12.38,11.46v1.07h-1.18v1.39h1.93v1.07h-2.25c-0.4,0.01 -0.74,-0.31 -0.75,-0.71V9.75c-0.01,-0.4 0.31,-0.74 0.71,-0.75h2.28l0,1.07h-1.92v1.39H12.38zM16.88,14.23c-0.48,1.11 -1.33,0.89 -1.71,0L13.77,9h1.18l1.07,4.11L17.09,9h1.18L16.88,14.23z"/>
<path android:fillColor="@android:color/white" android:pathData="M7.77,10.12H7.14v3.77h0.63c0.14,0 0.28,-0.05 0.42,-0.16c0.14,-0.1 0.21,-0.26 0.21,-0.47v-2.52c0,-0.21 -0.07,-0.37 -0.21,-0.47C8.05,10.17 7.91,10.12 7.77,10.12z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12.65,15.67c0.14,-0.36 0.05,-0.77 -0.23,-1.05l-2.09,-2.06 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53h1.94c0.54,0 0.99,-0.45 0.99,-0.99v-0.02c0,-0.54 -0.45,-0.99 -0.99,-0.99L10,4L10,3c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v1L1.99,4c-0.54,0 -0.99,0.45 -0.99,0.99 0,0.55 0.45,0.99 0.99,0.99h10.18C11.5,7.92 10.44,9.75 9,11.35c-0.81,-0.89 -1.49,-1.86 -2.06,-2.88 -0.16,-0.29 -0.45,-0.47 -0.78,-0.47 -0.69,0 -1.13,0.75 -0.79,1.35 0.63,1.13 1.4,2.21 2.3,3.21L3.3,16.87c-0.4,0.39 -0.4,1.03 0,1.42 0.39,0.39 1.02,0.39 1.42,0L9,14l2.02,2.02c0.51,0.51 1.38,0.32 1.63,-0.35zM17.5,10c-0.6,0 -1.14,0.37 -1.35,0.94l-3.67,9.8c-0.24,0.61 0.22,1.26 0.87,1.26 0.39,0 0.74,-0.24 0.88,-0.61l0.89,-2.39h4.75l0.9,2.39c0.14,0.36 0.49,0.61 0.88,0.61 0.65,0 1.11,-0.65 0.88,-1.26l-3.67,-9.8c-0.22,-0.57 -0.76,-0.94 -1.36,-0.94zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

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

View File

@@ -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" }