From 4c2e12f0753371c4cd9e2d3cd57d3d6fd0e834bb Mon Sep 17 00:00:00 2001 From: sathwikcs <85106054+sathwikcs@users.noreply.github.com> Date: Sat, 19 Oct 2024 11:46:03 +0530 Subject: [PATCH] home screen --- .../attendanceapp/screens/home/ui/home.kt | 105 +++++++-- .../screens/home/viewModel/homeVM.kt | 202 +++++++++++++++++- 2 files changed, 287 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/home.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/home.kt index 436d88d..e5638ac 100644 --- a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/home.kt +++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/ui/home.kt @@ -8,13 +8,20 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight 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.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -23,8 +30,11 @@ import androidx.compose.runtime.getValue 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.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState @@ -35,11 +45,15 @@ import `in`.sminnovations.attendanceapp.screens.home.ui.componets.RequestPermiss import `in`.sminnovations.attendanceapp.screens.home.viewModel.HomeViewModel -@OptIn(ExperimentalPermissionsApi::class) +@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class) @Composable -fun HomeScreen(viewModel: HomeViewModel = viewModel()) { +fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) { val isScanning by viewModel.isScanning.collectAsState() val qrCodeValue by viewModel.qrCodeValue.collectAsState() + val checkInTime by viewModel.checkedIn.collectAsState() + val checkOutTime by viewModel.checkedOut.collectAsState() + val isCheckedIn by viewModel.isCheckedIn.collectAsState() + val isCheckedOut by viewModel.isCheckedOut.collectAsState() val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA) val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) @@ -78,20 +92,77 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel()) { } } - LargeButton( - text = if (isScanning) "Close" else "Scan", - onClick = if (isScanning) { - { viewModel.stopScanning() } - } else { - { viewModel.startScanning() } - }, - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(bottom = 24.dp) - ) + OutlinedCard(modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(.47f) + .align(Alignment.BottomCenter) + .padding(10.dp), onClick = { /*TODO*/ } - if (isScanning) { - BackHandler { viewModel.stopScanning() } + ) { + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .padding(20.dp) + .align(Alignment.TopCenter) + .fillMaxWidth() + ) { + AnimatedVisibility(visible = isCheckedIn) { + Text( + text = "You already checked it Scanning Again will logout", + letterSpacing = 2.sp, + lineHeight = 40.sp, + fontSize = 30.sp, + color = MaterialTheme.colorScheme.error, + fontWeight = FontWeight.Bold + ) + } + androidx.compose.animation.AnimatedVisibility(visible = !isCheckedIn) { + Text( + text = "Please Check In", + letterSpacing = 2.sp, + lineHeight = 40.sp, + fontSize = 30.sp, + color = Color.Green, + fontWeight = FontWeight.Bold + ) + } + Column(Modifier.padding(15.dp)) { + androidx.compose.animation.AnimatedVisibility(visible = !isCheckedIn) { + Text(text = "Check in $checkInTime") + Spacer(modifier = Modifier.height(10.dp)) + } + androidx.compose.animation.AnimatedVisibility(visible = isCheckedIn) { + Text(text = "Check Out$checkOutTime") + Spacer(modifier = Modifier.height(10.dp)) + } + } + } + + Column(Modifier.align(Alignment.BottomCenter)) { + LargeButton(text = if (isScanning) "Close" else "Scan", + onClick = if (isScanning) { + { viewModel.stopScanning() } + } else { + { viewModel.startScanning() } + }, + modifier = Modifier + .padding(bottom = 24.dp)) + if (isScanning) { + BackHandler { viewModel.stopScanning() } + } + + LargeButton( + text = "Login", onClick = { + viewModel.loginNow() + }, modifier = Modifier + .padding(bottom = 24.dp) + ) + if (isScanning) { + BackHandler { viewModel.stopScanning() } + } + + } + } } } } diff --git a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/viewModel/homeVM.kt b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/viewModel/homeVM.kt index d7c91fa..c05c32d 100644 --- a/app/src/main/java/in/sminnovations/attendanceapp/screens/home/viewModel/homeVM.kt +++ b/app/src/main/java/in/sminnovations/attendanceapp/screens/home/viewModel/homeVM.kt @@ -1,25 +1,61 @@ package `in`.sminnovations.attendanceapp.screens.home.viewModel +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.firebase.Timestamp +import com.google.firebase.firestore.FirebaseFirestore +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.sminnovations.attendanceapp.data.dataStore.EmployeeRepository +import `in`.sminnovations.attendanceapp.screens.home.data.CheckInCheckOutDataStore +import `in`.sminnovations.attendanceapp.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.Format +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.LocalTime +import java.util.Date +import java.util.Locale +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val loginDataStore: LoginDataStore, + private val checkInCheckOutDataStore: CheckInCheckOutDataStore, + private val firebaseFirestore: FirebaseFirestore, + private val employeeRepository: EmployeeRepository +) : ViewModel() { -class HomeViewModel : ViewModel() { private val _qrCodeValue = MutableStateFlow(null) val qrCodeValue: StateFlow = _qrCodeValue.asStateFlow() private val _isScanning = MutableStateFlow(false) val isScanning: StateFlow = _isScanning.asStateFlow() + private val _checkedIn = MutableStateFlow("") + val checkedIn : StateFlow = _checkedIn.asStateFlow() + + private val _checkedOut = MutableStateFlow("N /A ") + val checkedOut : StateFlow = _checkedOut.asStateFlow() + + + private val _isCheckedIn = MutableStateFlow(false) + val isCheckedIn : StateFlow = _isCheckedIn.asStateFlow() + + private val _isCheckedOut = MutableStateFlow(false) + val isCheckedOut : StateFlow = _isCheckedOut.asStateFlow() + + + fun startScanning() { _isScanning.value = true } - fun stopScanning() { + fun stopScanning() { _isScanning.value = false } @@ -30,9 +66,169 @@ class HomeViewModel : ViewModel() { } } - fun getEmployeeData(){ + private fun formatTimestamp(timestamp: Timestamp): String { + val sdf = SimpleDateFormat("dd MMMM yyyy hh:mm:ss a", Locale.getDefault()) // Format with AM/PM + return sdf.format(timestamp.toDate()) + } + + init { + viewModelScope.launch { + checkInCheckOutDataStore.checkInId.collect { checkIn -> + checkInCheckOutDataStore.checkOutId.collect { checkOut -> + _checkedIn.value= checkIn + _checkedOut.value = checkOut + } + } + } } + + private fun getIsCheckInCheckOut(){ + viewModelScope.launch { + checkInCheckOutDataStore.isCheckedIn.collect { isCheckIn -> + checkInCheckOutDataStore.isCheckedOut.collect { isCheckOut -> + _isCheckedIn.value = isCheckIn + _isCheckedOut.value = isCheckOut + } + } + } + } + + private fun getFormattedDate(): String { + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + return dateFormat.format(Date()) + } + + fun loginNow() { + viewModelScope.launch { + checkInCheckOutDataStore.checkInDate.collect { checkOutDate -> + loginDataStore.loginId.collect { _ -> + firebaseFirestore.collection("qr_key").whereEqualTo("id", getFormattedDate()) + .get().addOnSuccessListener { querySnapshot -> + if (!querySnapshot.isEmpty) { + for (document in querySnapshot) { + if (qrCodeValue.value == document.getString("key")) { + viewModelScope.launch { + if (checkOutDate != LocalDate.now().toString() || checkOutDate =="") { + putCheckInAttendance("") + } else { + putCheckOutAttendance() + } + } + } else { + Log.d("loginNow", "Invalid QR code scanned") + } + } + } else { + Log.d("loginNow", "No matching QR key found for today's date") + } + }.addOnFailureListener { exception -> + Log.e("Firestore", "Error fetching QR key: ", exception) + } + } + } + } + } + + + private fun putCheckInAttendance(location: String) { + viewModelScope.launch { + loginDataStore.loginId.collect { id -> + val currentDate = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date()) + val attendanceRef = firebaseFirestore.collection("attendance_$id").document(currentDate) + + attendanceRef.get().addOnSuccessListener { document -> + if (!document.exists()) { + val checkInData = hashMapOf( + "Status" to false, + "checkIn" to Timestamp.now(), + "location" to location + ) + + attendanceRef.set(checkInData).addOnSuccessListener { + // Save the check-in data to a local datastore if needed + viewModelScope.launch { + checkInCheckOutDataStore.saveLoginData( + formatTimestamp(Timestamp.now()), + "Not yet Done" + ) + checkInCheckOutDataStore.saveLoginDate(LocalDate.now().toString()) + checkInCheckOutDataStore.saveIsLogIn(true) + } + Log.d("Firestore", "Check-in successful") + _isCheckedIn.value = true + }.addOnFailureListener { e -> + Log.e("Firestore", "Error adding check-in", e) + } + } else { + // Document exists, check the checkOut field + val checkOut = document.getTimestamp("checkout") + if (checkOut == null) { + Log.d( + "Firestore", + "Check-in already exists for today. Cannot check in again." + ) + } else { + Log.d("Firestore", "Attendance already completed for today.") + } + } + }.addOnFailureListener { exception -> + Log.e("Firestore", "Error fetching document: ", exception) + } + } + } + } + + + fun putCheckOutAttendance() { + viewModelScope.launch { + loginDataStore.loginId.collect { id -> + val currentDate = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date()) + val attendanceRef = + firebaseFirestore.collection("attendance_$id").document(currentDate) + + attendanceRef.get().addOnSuccessListener { document -> + if (document.exists()) { + + val checkIn = document.getTimestamp("checkIn") + val checkOut = document.getTimestamp("checkout") + + if (checkIn != null && checkOut == null) { + + val checkOutData = hashMapOf( + "checkout" to Timestamp.now(), "Status" to true + ) as Map + + attendanceRef.update(checkOutData).addOnSuccessListener { + viewModelScope.launch { + checkInCheckOutDataStore.saveLoginData(checkedIn.value,formatTimestamp(Timestamp.now())) + checkInCheckOutDataStore.saveIsLogIn(false) + } + Log.d("Firestore", "Check-out successful") + }.addOnFailureListener { e -> + Log.e("Firestore", "Error adding check-out", e) + } + } else if (checkOut != null) { + Log.d("Firestore", "Already checked out for today.") + } else { + Log.d("Firestore", "Cannot check out without checking in.") + } + } else { + Log.d( + "Firestore", "No attendance found for today. Please check in first." + ) + } + }.addOnFailureListener { exception -> + Log.e("Firestore", "Error fetching document: ", exception) + } + } + } + } + + + + + }