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 index 74dc833..c1816a7 100644 --- 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 @@ -7,6 +7,7 @@ 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.home.data.QRCodeDataStore import `in`.sminnovations.smi_attendance_admin.screens.login.data.LoginDataStore import javax.inject.Singleton @@ -20,6 +21,11 @@ object AppModule { } @Provides @Singleton + fun provideQRDataStore(@ApplicationContext context: Context): QRCodeDataStore { + return QRCodeDataStore(context) + } + @Provides + @Singleton fun provideFirebase(): FirebaseFirestore { return FirebaseFirestore.getInstance() } 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 index e1f8704..88e7740 100644 --- 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 @@ -1,6 +1,7 @@ package `in`.sminnovations.smi_attendance_admin.navigation 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 @@ -12,22 +13,27 @@ import `in`.sminnovations.smi_attendance_admin.screens.home.ui.FullScreenQRCodeS @Composable fun Navigation( - navController: NavHostController, items: List, - mainNavHostController : NavHostController, + mainNavHostController: NavHostController, ) { + // Ensure items is not empty before accessing indices + if (items.isEmpty()) return + NavHost(navController, startDestination = items[0].route) { composable(items[0].route) { - HomeScreen(navController) + // It's good practice to check if homeViewModel is needed + HomeScreen(navController, homeViewModel = hiltViewModel()) } composable(items[1].route) { - SettingsScreen(mainNavHostController = mainNavHostController) + // Check if items[1] exists before accessing + if (items.size > 1) { + SettingsScreen(mainNavHostController = mainNavHostController) + } } composable("FullScreenQRCode") { - FullScreenQRCodeScreen(navController) + FullScreenQRCodeScreen(homeViewModel = hiltViewModel()) } } - } diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/data/QRCodeDataStore.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/data/QRCodeDataStore.kt new file mode 100644 index 0000000..0e3ebc1 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/data/QRCodeDataStore.kt @@ -0,0 +1,46 @@ +package `in`.sminnovations.smi_attendance_admin.screens.home.data + +import android.content.Context +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +private const val DATASTORE_NAME = "qr_prefs" +private val Context.dataStore by preferencesDataStore(name = DATASTORE_NAME) + +@Singleton +class QRCodeDataStore @Inject constructor(@ApplicationContext context: Context) { + + private val dataStore = context.dataStore + + private val QR_ID = stringPreferencesKey("qr_id") + private val QR_KEY = stringPreferencesKey("qr_key") + + val qr_id: Flow = dataStore.data.map { preferences -> + preferences[QR_ID] ?: "" + } + + val qr_key: Flow = dataStore.data.map { preferences -> + preferences[QR_KEY] ?: "" + } + + suspend fun saveQRData(id: String, key: String) { + dataStore.edit { preferences -> + preferences[QR_ID] = id + preferences[QR_KEY] = key + } + } + + suspend fun getQRCodeData(): String { + val preferences = dataStore.data.first() + return preferences[QR_ID] ?: "SMI@123" // Return default if not found + } + +} \ No newline at end of file diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/FullScreenQrCodeScreen.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/FullScreenQrCodeScreen.kt index 78ad093..564349e 100644 --- a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/FullScreenQrCodeScreen.kt +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/ui/FullScreenQrCodeScreen.kt @@ -1,47 +1,48 @@ package `in`.sminnovations.smi_attendance_admin.screens.home.ui -import android.graphics.Bitmap import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState 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.Color import androidx.compose.ui.graphics.asImageBitmap -import androidx.navigation.NavController +import androidx.hilt.navigation.compose.hiltViewModel +import `in`.sminnovations.smi_attendance_admin.screens.home.viewModel.HomeViewModel @Composable -fun FullScreenQRCodeScreen(navController: NavController) { - var qrCodeBitmap by remember { mutableStateOf(null) } - - // Simulate QR code being passed or generated here - qrCodeBitmap = generateQRCode("SMI") +fun FullScreenQRCodeScreen(homeViewModel: HomeViewModel = hiltViewModel()) { + val qrCodeBitmap by homeViewModel.qrCodeBitmap.collectAsState() + homeViewModel.checkAndGenerateQRCode() Box( modifier = Modifier .fillMaxSize() - .background(color = MaterialTheme.colorScheme.onBackground) + .background(color = MaterialTheme.colorScheme.onBackground) // Changed to background .clickable { // Navigate back when clicked - //navController.popBackStack() + // navController.popBackStack() } ) { qrCodeBitmap?.let { bitmap -> Image( bitmap = bitmap.asImageBitmap(), contentDescription = "Full Screen QR Code", - modifier = Modifier - .fillMaxSize() - .clickable { - // Navigate back when clicked - //navController.popBackStack() - } + modifier = Modifier.fillMaxSize() + ) + } ?: run { + // Show a placeholder or loading indicator while the bitmap is null + Text( + text = "Loading QR Code...", + color = Color.White, // Set the text color to white + modifier = Modifier.align(Alignment.Center) ) } } 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 index 415a6a0..35855f3 100644 --- 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 @@ -14,8 +14,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator 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 @@ -25,17 +27,19 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController 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.viewModel.HomeViewModel @OptIn(ExperimentalPermissionsApi::class) @Composable -fun HomeScreen(navController: NavController) { +fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel = hiltViewModel()) { val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA) val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) @@ -48,13 +52,14 @@ fun HomeScreen(navController: NavController) { Box(modifier = Modifier.fillMaxSize()) { - QRCodeGeneratorScreen(navController = navController) + QRCodeGeneratorScreen(navController = navController, homeViewModel = homeViewModel) } } @Composable -fun QRCodeGeneratorScreen(navController: NavController) { - var qrCodeBitmap by remember { mutableStateOf(null) } +fun QRCodeGeneratorScreen(navController: NavController, homeViewModel: HomeViewModel) { + val qrCodeBitmap by homeViewModel.qrCodeBitmap.collectAsState() // Observe the QR code bitmap var isButtonVisible by remember { mutableStateOf(true) } + val loading by homeViewModel.isLoading.collectAsState() Column( modifier = Modifier @@ -64,11 +69,13 @@ fun QRCodeGeneratorScreen(navController: NavController) { verticalArrangement = Arrangement.Center ) { // Show the button if it hasn't been clicked yet + if(qrCodeBitmap != null) { + isButtonVisible = false + } if (isButtonVisible) { Button( onClick = { - val qrData = "SMI" - qrCodeBitmap = generateQRCode(qrData) + homeViewModel.checkAndGenerateQRCode() isButtonVisible = false // Hide the button once QR is generated }, modifier = Modifier.padding(bottom = 16.dp) @@ -76,7 +83,9 @@ fun QRCodeGeneratorScreen(navController: NavController) { Text(text = "Generate QR Code") } } - + if (loading) { + CircularProgressIndicator(modifier = Modifier.padding(top = 16.dp)) + } // Display the QR code if it has been generated qrCodeBitmap?.let { bitmap -> Image( @@ -89,6 +98,11 @@ fun QRCodeGeneratorScreen(navController: NavController) { navController.navigate("FullScreenQRCode") } ) + }?: run { + // Optional: Show a placeholder or message if QR code is not yet generated + if (!loading) { + Text(text = "QR Code will be shown here") + } } } } diff --git a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/viewModel/HomeViewModel.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/viewModel/HomeViewModel.kt new file mode 100644 index 0000000..0478579 --- /dev/null +++ b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/viewModel/HomeViewModel.kt @@ -0,0 +1,119 @@ +package `in`.sminnovations.smi_attendance_admin.screens.home.viewModel + + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.firestore.FirebaseFirestore +import com.google.zxing.BarcodeFormat +import com.journeyapps.barcodescanner.BarcodeEncoder +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.sminnovations.smi_attendance_admin.screens.home.data.QRCodeDataStore +import `in`.sminnovations.smi_attendance_admin.screens.login.data.LoginDataStore +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val qrCodeDataStore: QRCodeDataStore, private val firebaseFirestore: FirebaseFirestore +) : ViewModel() { + private val _qrCodeBitmap = MutableStateFlow(null) + val qrCodeBitmap: StateFlow = _qrCodeBitmap + var isLoading = MutableStateFlow(false) + + fun checkAndGenerateQRCode() { + val today = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date()) + val documentRef = firebaseFirestore.collection("qr_key").document(today) + + isLoading.value = true // Start loading + + // Check if today's document exists + documentRef.get().addOnSuccessListener { document -> + if (document.exists()) { + // If the document already exists + viewModelScope.launch { + val existingId = document.getString("id") ?: "" + val existingKey = document.getString("key") ?: "" + + qrCodeDataStore.saveQRData(id = existingId, key = existingKey) + _qrCodeBitmap.value = generateQRCode(existingKey) + // Optionally, notify user that the document is present + isLoading.value = false + } + // Show a toast from your Composable + // Example: Toast.makeText(context, "Document already present: $existingKey", Toast.LENGTH_SHORT).show() + } else { + // If the document doesn't exist, create it + val randomNumber = (1000..9999).random() // Generate a random 4-digit number + val newKey = "SMI$randomNumber${SimpleDateFormat("MM", Locale.getDefault()).format(Date())}${SimpleDateFormat("dd", Locale.getDefault()).format(Date())}" + + val newDocument = hashMapOf( + "id" to today, + "key" to newKey // Add your key here + ) + + // Create the document + documentRef.set(newDocument).addOnSuccessListener { + // Set the QR code bitmap + viewModelScope.launch { + + qrCodeDataStore.saveQRData(id = today, key = newKey) + + _qrCodeBitmap.value = generateQRCode(newKey) + + isLoading.value = false // Stop loading + } + + }.addOnFailureListener { e -> + // Handle failure + isLoading.value = false // Stop loading + // Show a toast from your Composable + // Example: Toast.makeText(context, "Error creating document: ${e.message}", Toast.LENGTH_SHORT).show() + } + } + }.addOnFailureListener { e -> + // Handle failure + isLoading.value = false // Stop loading + // Show a toast from your Composable + // Example: Toast.makeText(context, "Error checking document: ${e.message}", Toast.LENGTH_SHORT).show() + } + } + + private fun generateQRCode(data: String): Bitmap? { + // Your existing QR code generation logic + 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 + } + } + +} + + 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 deleted file mode 100644 index e28d551..0000000 --- a/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/home/viewModel/homeVM.kt +++ /dev/null @@ -1,38 +0,0 @@ -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/ui/LoginScreen.kt b/app/src/main/java/in/sminnovations/smi_attendance_admin/screens/login/ui/LoginScreen.kt index d995cb4..998c1e2 100644 --- 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 @@ -8,12 +8,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.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.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -47,6 +49,7 @@ fun LoginScreen( onLoginSuccess: () -> Unit ) { val coroutineScope = rememberCoroutineScope() + var loading by remember { mutableStateOf(false) } // Track the loading state val isPasswordError by loginViewModel.isPasswordError.collectAsState() val isUserIdError by loginViewModel.isIdError.collectAsState() val isLoggedIn by loginViewModel.isLoggedIn.collectAsState() @@ -128,20 +131,37 @@ fun LoginScreen( 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 + if (loading) { + CircularProgressIndicator( + modifier = Modifier.size(50.dp) // Show progress indicator when loading is true ) + } else { + Button( + onClick = { + coroutineScope.launch { + loading = true // Start showing the progress bar + try { + // Attempt login + loginViewModel.login(userId, password) { + // On login success, trigger the success callback (like navigating) + onLoginSuccess() // Navigation or further action + loading = false // Hide the progress bar after navigation + } + } catch (e: Exception) { + // Handle login failure (optional) + loading = false // Hide the progress bar on failure + } + } + }, + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + ) { + Text( + text = "Login", + fontSize = 20.sp + ) + } } Spacer(modifier = Modifier.weight(1f))