QR code Full Screen added and connected to firebase

This commit is contained in:
chandrashekhar reddy
2024-10-18 16:45:24 +05:30
parent 86447958ad
commit 327782ca4a
8 changed files with 256 additions and 82 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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