feat(item): DatePicker durch Monat/Jahr-Picker ersetzen

- Ablaufdatum wird jetzt als MM/yyyy gespeichert (letzter Tag des Monats)
- DatePickerDialog -> AlertDialog mit zwei Dropdowns (Monat, Jahr)
- Anzeige in ItemListScreen ebenfalls auf MM/yyyy umgestellt
This commit is contained in:
Jens Reinemann 2026-05-16 14:37:54 +02:00
parent fcc7142ea1
commit b7f27b6f81
2 changed files with 136 additions and 50 deletions

View file

@ -1,6 +1,9 @@
package de.krisenvorrat.app.ui.item
import androidx.compose.foundation.clickable
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@ -13,8 +16,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
@ -27,7 +29,6 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -39,9 +40,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.YearMonth
import java.time.format.DateTimeFormatter
@OptIn(ExperimentalMaterial3Api::class)
@ -311,8 +311,8 @@ private fun ExpiryDateField(
expiryDate: LocalDate?,
onDateSelected: (LocalDate?) -> Unit
) {
var isDatePickerVisible by remember { mutableStateOf(false) }
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
var isPickerVisible by remember { mutableStateOf(false) }
val formatter = remember { DateTimeFormatter.ofPattern("MM/yyyy") }
val displayValue = expiryDate?.format(formatter) ?: ""
OutlinedTextField(
@ -321,51 +321,140 @@ private fun ExpiryDateField(
readOnly = true,
label = { Text("Ablaufdatum (MHD)") },
trailingIcon = {
IconButton(onClick = { isDatePickerVisible = true }) {
IconButton(onClick = { isPickerVisible = true }) {
Icon(
imageVector = Icons.Default.DateRange,
contentDescription = "Datum wählen"
)
}
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.clickable { isPickerVisible = true }
)
if (isDatePickerVisible) {
val initialMillis = expiryDate
?.atStartOfDay(ZoneId.of("UTC"))
?.toInstant()
?.toEpochMilli()
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = initialMillis)
DatePickerDialog(
onDismissRequest = { isDatePickerVisible = false },
confirmButton = {
TextButton(
onClick = {
val selectedMillis = datePickerState.selectedDateMillis
if (selectedMillis != null) {
val selectedDate = Instant.ofEpochMilli(selectedMillis)
.atZone(ZoneId.of("UTC"))
.toLocalDate()
onDateSelected(selectedDate)
}
isDatePickerVisible = false
}
) {
Text("OK")
}
if (isPickerVisible) {
MonthYearPickerDialog(
initialDate = expiryDate,
onConfirm = { yearMonth ->
onDateSelected(yearMonth.atEndOfMonth())
isPickerVisible = false
},
dismissButton = {
TextButton(onClick = {
onDateSelected(null)
isDatePickerVisible = false
}) {
Text("Entfernen")
}
}
) {
DatePicker(state = datePickerState)
}
onRemove = {
onDateSelected(null)
isPickerVisible = false
},
onDismiss = { isPickerVisible = false }
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MonthYearPickerDialog(
initialDate: LocalDate?,
onConfirm: (YearMonth) -> Unit,
onRemove: () -> Unit,
onDismiss: () -> Unit
) {
val today = remember { LocalDate.now() }
val initialYearMonth = if (initialDate != null) YearMonth.from(initialDate) else YearMonth.from(today)
var selectedMonth by remember { mutableStateOf(initialYearMonth.monthValue) }
var selectedYear by remember { mutableStateOf(initialYearMonth.year) }
val monthNames = remember {
listOf(
"Januar", "Februar", "März", "April", "Mai", "Juni",
"Juli", "August", "September", "Oktober", "November", "Dezember"
)
}
val years = remember(today) { (today.year - 1..today.year + 20).toList() }
var monthExpanded by remember { mutableStateOf(false) }
var yearExpanded by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Ablaufdatum wählen") },
text = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
ExposedDropdownMenuBox(
expanded = monthExpanded,
onExpandedChange = { monthExpanded = it },
modifier = Modifier.weight(1f)
) {
OutlinedTextField(
value = monthNames[selectedMonth - 1],
onValueChange = {},
readOnly = true,
label = { Text("Monat") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = monthExpanded) },
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable)
)
ExposedDropdownMenu(
expanded = monthExpanded,
onDismissRequest = { monthExpanded = false }
) {
monthNames.forEachIndexed { index, name ->
DropdownMenuItem(
text = { Text(name) },
onClick = {
selectedMonth = index + 1
monthExpanded = false
}
)
}
}
}
ExposedDropdownMenuBox(
expanded = yearExpanded,
onExpandedChange = { yearExpanded = it },
modifier = Modifier.weight(1f)
) {
OutlinedTextField(
value = selectedYear.toString(),
onValueChange = {},
readOnly = true,
label = { Text("Jahr") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = yearExpanded) },
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable)
)
ExposedDropdownMenu(
expanded = yearExpanded,
onDismissRequest = { yearExpanded = false }
) {
years.forEach { year ->
DropdownMenuItem(
text = { Text(year.toString()) },
onClick = {
selectedYear = year
yearExpanded = false
}
)
}
}
}
}
},
confirmButton = {
TextButton(onClick = { onConfirm(YearMonth.of(selectedYear, selectedMonth)) }) {
Text("OK")
}
},
dismissButton = {
Row {
TextButton(onClick = onRemove) {
Text("Entfernen")
}
TextButton(onClick = onDismiss) {
Text("Abbrechen")
}
}
}
)
}

View file

@ -33,6 +33,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -231,18 +232,14 @@ private fun ItemCard(
@Composable
private fun ExpiryDateText(item: ItemUiModel) {
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
val formatter = remember { DateTimeFormatter.ofPattern("MM/yyyy") }
val formattedDate = item.expiryDate?.format(formatter).orEmpty()
val color = when {
item.isExpired -> MaterialTheme.colorScheme.error
item.isExpiringSoon -> MaterialTheme.colorScheme.tertiary
else -> MaterialTheme.colorScheme.onSurfaceVariant
}
val prefix = when {
item.isExpired -> "Abgelaufen: "
item.isExpiringSoon -> "MHD: "
else -> "MHD: "
}
val prefix = if (item.isExpired) "Abgelaufen: " else "MHD: "
Text(
text = "$prefix$formattedDate",
style = MaterialTheme.typography.bodySmall,