QR code Full Screen added and connected to firebase
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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<BottomNavItem>,
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> = dataStore.data.map { preferences ->
|
||||
preferences[QR_ID] ?: ""
|
||||
}
|
||||
|
||||
val qr_key: Flow<String> = 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Bitmap?>(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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Bitmap?>(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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Bitmap?>(null)
|
||||
val qrCodeBitmap: StateFlow<Bitmap?> = _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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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(){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user