feat: introduce shared minimum height for header and row cells in ListDetailScreen; simplify state management by using direct state values

This commit is contained in:
2025-11-13 18:36:52 +01:00
parent d7f10c8887
commit 9d537e0de6
@@ -103,6 +103,10 @@ import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
// Independent minimum heights for header and rows
private val HeaderMinHeight = 44.dp
private val RowMinHeight = 48.dp
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun ListDetailScreen( fun ListDetailScreen(
@@ -123,9 +127,9 @@ fun ListDetailScreen(
val items by viewModel.items.collectAsState() val items by viewModel.items.collectAsState()
val isLoading by viewModel.isLoading.collectAsState() val isLoading by viewModel.isLoading.collectAsState()
// Use derivedStateOf to create stable references // Use direct state values; avoid derivedStateOf without state reads (caused staleness)
val stableFields by remember { derivedStateOf { fields } } val stableFields = fields
val stableItems by remember { derivedStateOf { items } } val stableItems = items
var itemToEdit by remember { mutableStateOf<ItemWithValues?>(null) } var itemToEdit by remember { mutableStateOf<ItemWithValues?>(null) }
var showSortDialog by remember { mutableStateOf(false) } var showSortDialog by remember { mutableStateOf(false) }
var showGroupDialog by remember { mutableStateOf(false) } var showGroupDialog by remember { mutableStateOf(false) }
@@ -495,38 +499,43 @@ fun ListDetailScreen(
pendingScrollToBottom = false pendingScrollToBottom = false
} }
} }
// Header: horizontally scrollable across the table width // Header: horizontally scrollable across the table width with equal-height header cells
Row( val headerWidths = stableFields.map { field -> fieldWidths[field.id] ?: 150.dp }
Box(
modifier = modifier =
Modifier Modifier
.horizontalScroll(horizontalScrollState) .horizontalScroll(horizontalScrollState)
.background(MaterialTheme.colorScheme.surface), .background(MaterialTheme.colorScheme.surface),
verticalAlignment = Alignment.CenterVertically,
) { ) {
stableFields.forEach { field -> EqualHeightRow(
key(field.id) { widths = headerWidths,
FieldHeader( modifier = Modifier.fillMaxWidth(),
field = field, ) {
width = fieldWidths[field.id] ?: 150.dp, stableFields.forEach { field ->
onWidthChange = { delta -> key(field.id) {
val currentWidth = fieldWidths[field.id] ?: 150.dp FieldHeader(
val newWidth = (currentWidth.value + delta).coerceAtLeast(100f) field = field,
fieldWidths[field.id] = newWidth.dp width = fieldWidths[field.id] ?: 150.dp,
prefs.setColumnWidths( onWidthChange = { delta ->
listId, val currentWidth = fieldWidths[field.id] ?: 150.dp
fieldWidths.mapValues { it.value.value }, val newWidth = (currentWidth.value + delta).coerceAtLeast(100f)
) fieldWidths[field.id] = newWidth.dp
}, prefs.setColumnWidths(
scrollState = horizontalScrollState, listId,
isLast = (field.id == stableFields.lastOrNull()?.id), fieldWidths.mapValues { it.value.value },
onHeaderClick = { showManageColumnsDialog = true }, )
alignment = columnAlignments[field.id] ?: "start", },
onAlignmentChange = { newAlign -> scrollState = horizontalScrollState,
columnAlignments[field.id] = newAlign isLast = (field.id == stableFields.lastOrNull()?.id),
viewModel.updateFieldAlignment(field.id, newAlign) onHeaderClick = { showManageColumnsDialog = true },
prefs.setColumnAlignments(listId, columnAlignments.toMap()) alignment = columnAlignments[field.id] ?: "start",
}, onAlignmentChange = { newAlign ->
) columnAlignments[field.id] = newAlign
viewModel.updateFieldAlignment(field.id, newAlign)
prefs.setColumnAlignments(listId, columnAlignments.toMap())
},
)
}
} }
} }
} }
@@ -883,13 +892,17 @@ fun FieldHeader(
.border(1.dp, MaterialTheme.colorScheme.outline), .border(1.dp, MaterialTheme.colorScheme.outline),
) { ) {
Surface( Surface(
modifier = Modifier.fillMaxWidth(), modifier =
Modifier
.fillMaxWidth()
.heightIn(min = HeaderMinHeight),
color = MaterialTheme.colorScheme.primaryContainer, color = MaterialTheme.colorScheme.primaryContainer,
) { ) {
Row( Row(
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = HeaderMinHeight)
.padding(12.dp), .padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -1005,7 +1018,7 @@ fun ItemRow(
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = 48.dp) .heightIn(min = RowMinHeight)
.clickable(onClick = onClick), .clickable(onClick = onClick),
) { ) {
fields.forEach { field -> fields.forEach { field ->