home screen
This commit is contained in:
@@ -8,13 +8,20 @@ import androidx.compose.animation.fadeIn
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.layout.Box
|
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.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
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.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedCard
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -23,8 +30,11 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.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.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
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
|
import `in`.sminnovations.attendanceapp.screens.home.viewModel.HomeViewModel
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(viewModel: HomeViewModel = viewModel()) {
|
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
|
||||||
val isScanning by viewModel.isScanning.collectAsState()
|
val isScanning by viewModel.isScanning.collectAsState()
|
||||||
val qrCodeValue by viewModel.qrCodeValue.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 cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
|
||||||
val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
|
val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
@@ -78,20 +92,77 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LargeButton(
|
OutlinedCard(modifier = Modifier
|
||||||
text = if (isScanning) "Close" else "Scan",
|
.fillMaxWidth()
|
||||||
onClick = if (isScanning) {
|
.fillMaxHeight(.47f)
|
||||||
{ viewModel.stopScanning() }
|
.align(Alignment.BottomCenter)
|
||||||
} else {
|
.padding(10.dp), onClick = { /*TODO*/ }
|
||||||
{ viewModel.startScanning() }
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.padding(bottom = 24.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
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() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,61 @@
|
|||||||
package `in`.sminnovations.attendanceapp.screens.home.viewModel
|
package `in`.sminnovations.attendanceapp.screens.home.viewModel
|
||||||
|
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
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<String?>(null)
|
private val _qrCodeValue = MutableStateFlow<String?>(null)
|
||||||
val qrCodeValue: StateFlow<String?> = _qrCodeValue.asStateFlow()
|
val qrCodeValue: StateFlow<String?> = _qrCodeValue.asStateFlow()
|
||||||
|
|
||||||
private val _isScanning = MutableStateFlow(false)
|
private val _isScanning = MutableStateFlow(false)
|
||||||
val isScanning: StateFlow<Boolean> = _isScanning.asStateFlow()
|
val isScanning: StateFlow<Boolean> = _isScanning.asStateFlow()
|
||||||
|
|
||||||
|
private val _checkedIn = MutableStateFlow<String>("")
|
||||||
|
val checkedIn : StateFlow<String> = _checkedIn.asStateFlow()
|
||||||
|
|
||||||
|
private val _checkedOut = MutableStateFlow<String>("N /A ")
|
||||||
|
val checkedOut : StateFlow<String> = _checkedOut.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
private val _isCheckedIn = MutableStateFlow<Boolean>(false)
|
||||||
|
val isCheckedIn : StateFlow<Boolean> = _isCheckedIn.asStateFlow()
|
||||||
|
|
||||||
|
private val _isCheckedOut = MutableStateFlow<Boolean>(false)
|
||||||
|
val isCheckedOut : StateFlow<Boolean> = _isCheckedOut.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun startScanning() {
|
fun startScanning() {
|
||||||
_isScanning.value = true
|
_isScanning.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopScanning() {
|
fun stopScanning() {
|
||||||
_isScanning.value = false
|
_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<String, Any>
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user