Biometric authentication implemented and integrated in dashboard
This commit is contained in:
@@ -15,6 +15,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
@@ -43,24 +46,30 @@ import kotlin.time.Instant
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddLogDialog(
|
||||
type: EntryType,
|
||||
onDismiss: () -> Unit,
|
||||
onSave: (timestamp: Instant, duration: Duration, reason: String?) -> Unit
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
var hours by remember { mutableStateOf("") }
|
||||
var minutes by remember { mutableStateOf("") }
|
||||
var reason by remember { mutableStateOf("") }
|
||||
// error if submit button is clicked with mandatory values not filled (duration)
|
||||
val error = remember { mutableStateOf<String?>(null) }
|
||||
|
||||
var selectedInstant by remember { mutableStateOf(Clock.System.now()) }
|
||||
var showDatePicker by remember { mutableStateOf(false) }
|
||||
|
||||
// State for the new EntryType selection
|
||||
var selectedType by remember { mutableStateOf(EntryType.OVERTIME) }
|
||||
val entryTypes = listOf(EntryType.OVERTIME, EntryType.TIME_OFF)
|
||||
|
||||
// Custom date formatting logic using kotlinx-datetime
|
||||
val localDateTime = selectedInstant.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
val formattedDate = "${localDateTime.month.name.take(3).lowercase().replaceFirstChar { it.uppercase() }} ${localDateTime.day}, ${localDateTime.year}"
|
||||
|
||||
if (showDatePicker) {
|
||||
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = selectedInstant.toEpochMilliseconds())
|
||||
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { showDatePicker = false },
|
||||
confirmButton = {
|
||||
@@ -94,10 +103,27 @@ fun AddLogDialog(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (type == EntryType.OVERTIME) stringResource(id = R.string.add_overtime_title) else stringResource(id = R.string.add_time_off_title),
|
||||
text = stringResource(R.string.add_entry),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
// Entry Type Selector
|
||||
PrimaryTabRow(selectedTabIndex = entryTypes.indexOf(selectedType)) {
|
||||
entryTypes.forEachIndexed { index, type ->
|
||||
Tab(
|
||||
selected = index == entryTypes.indexOf(selectedType),
|
||||
onClick = { selectedType = type },
|
||||
text = {
|
||||
when(type) {
|
||||
EntryType.OVERTIME -> Text(stringResource(R.string.overtime))
|
||||
EntryType.TIME_OFF -> Text(stringResource(R.string.time_off))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Date Row
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -113,11 +139,24 @@ fun AddLogDialog(
|
||||
}
|
||||
}
|
||||
|
||||
// Duration Row
|
||||
Text(
|
||||
text = stringResource(id = R.string.duration),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Duration Row
|
||||
Text(
|
||||
text = stringResource(id = R.string.duration),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
if (error.value != null) {
|
||||
Text(
|
||||
text = error.value!!,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
@@ -163,11 +202,18 @@ fun AddLogDialog(
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
if(hours.isEmpty() || minutes.isEmpty()) { // duration values cannot be empty
|
||||
error.value = "Please enter a duration or cancel"
|
||||
return@Button
|
||||
}
|
||||
// Convert hours and minutes to)
|
||||
val h = hours.toLongOrNull() ?: 0L
|
||||
val m = minutes.toLongOrNull() ?: 0L
|
||||
val totalMinutes = if (type == EntryType.OVERTIME) h * 60 + m else -(h * 60 + m)
|
||||
// Use the selectedType state to determine the sign
|
||||
val totalMinutes = if (selectedType == EntryType.OVERTIME) h * 60 + m else -(h * 60 + m)
|
||||
val duration = totalMinutes.minutes
|
||||
onSave(selectedInstant, duration, reason.takeIf(String::isNotBlank))
|
||||
|
||||
onSave(selectedInstant, duration, reason)
|
||||
}
|
||||
) {
|
||||
Text(stringResource(id = R.string.save))
|
||||
@@ -181,7 +227,6 @@ fun AddLogDialog(
|
||||
fun AddLogDialogPreview(){
|
||||
ClockedTheme {
|
||||
AddLogDialog(
|
||||
type = EntryType.OVERTIME,
|
||||
onDismiss = {},
|
||||
onSave = { _, _, _ -> }
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user