Biometric authentication implemented and integrated in dashboard

This commit is contained in:
2025-12-30 20:46:37 +01:00
parent d07d369546
commit 66e3bbb004
21 changed files with 540 additions and 250 deletions

View File

@@ -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 = { _, _, _ -> }
)