package net.tinsae.clocked.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDialog 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 import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable 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.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime import net.tinsae.clocked.R import net.tinsae.clocked.data.EntryType import net.tinsae.clocked.ui.theme.ClockedTheme import kotlin.time.Clock import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes import kotlin.time.Instant @OptIn(ExperimentalMaterial3Api::class) @Composable fun AddLogDialog( onDismiss: () -> Unit, onSave: (timestamp: Instant, duration: Duration, reason: String?) -> Unit ) { 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(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 = { TextButton(onClick = { datePickerState.selectedDateMillis?.let { millis -> selectedInstant = Instant.fromEpochMilliseconds(millis) } showDatePicker = false }) { Text(stringResource(id = R.string.ok)) } }, dismissButton = { TextButton(onClick = { showDatePicker = false }) { Text(stringResource(id = R.string.cancel)) } } ) { DatePicker(state = datePickerState) } } ModalBottomSheet( onDismissRequest = onDismiss, sheetState = sheetState ) { Column( modifier = Modifier .padding(16.dp) .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( 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(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Text(stringResource(id = R.string.date), style = MaterialTheme.typography.bodyLarge) TextButton( onClick = { showDatePicker = true }, ) { Text(formattedDate, 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 ) { OutlinedTextField( value = hours, onValueChange = { hours = it.filter(Char::isDigit) }, label = { Text(stringResource(id = R.string.hours)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.weight(0.5f) ) Spacer(modifier = Modifier.width(8.dp)) OutlinedTextField( value = minutes, onValueChange = { val filtered = it.filter(Char::isDigit) if (filtered.isEmpty() || filtered.toInt() < 60) { minutes = filtered } }, label = { Text(stringResource(id = R.string.minutes)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.weight(0.5f) ) } // Reason Field OutlinedTextField( value = reason, onValueChange = { reason = it }, label = { Text(stringResource(id = R.string.reason)) }, modifier = Modifier.fillMaxWidth() ) // Action Buttons Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End, ) { TextButton(onClick = onDismiss) { Text(stringResource(id = R.string.cancel)) } 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 // 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) } ) { Text(stringResource(id = R.string.save)) } } } } } @Preview(showBackground = true) @Composable fun AddLogDialogPreview(){ ClockedTheme { AddLogDialog( onDismiss = {}, onSave = { _, _, _ -> } ) } }