feat: refactor ListDetailScreen layout for improved header and content alignment; enhance empty state messaging

This commit is contained in:
2025-11-13 18:17:13 +01:00
parent a8c7b859a8
commit d7f10c8887
@@ -495,93 +495,94 @@ fun ListDetailScreen(
pendingScrollToBottom = false pendingScrollToBottom = false
} }
} }
Column( // Header: horizontally scrollable across the table width
Row(
modifier = modifier =
Modifier Modifier
.fillMaxSize() .horizontalScroll(horizontalScrollState)
.horizontalScroll(horizontalScrollState), .background(MaterialTheme.colorScheme.surface),
verticalAlignment = Alignment.CenterVertically,
) { ) {
// Fixed header (always visible even with zero items) stableFields.forEach { field ->
Row( key(field.id) {
modifier = FieldHeader(
Modifier field = field,
.fillMaxWidth() width = fieldWidths[field.id] ?: 150.dp,
.background(MaterialTheme.colorScheme.surface), onWidthChange = { delta ->
verticalAlignment = Alignment.CenterVertically, val currentWidth = fieldWidths[field.id] ?: 150.dp
) { val newWidth = (currentWidth.value + delta).coerceAtLeast(100f)
stableFields.forEach { field -> fieldWidths[field.id] = newWidth.dp
key(field.id) { prefs.setColumnWidths(
FieldHeader( listId,
field = field, fieldWidths.mapValues { it.value.value },
width = fieldWidths[field.id] ?: 150.dp, )
onWidthChange = { delta -> },
val currentWidth = fieldWidths[field.id] ?: 150.dp scrollState = horizontalScrollState,
val newWidth = (currentWidth.value + delta).coerceAtLeast(100f) isLast = (field.id == stableFields.lastOrNull()?.id),
fieldWidths[field.id] = newWidth.dp onHeaderClick = { showManageColumnsDialog = true },
prefs.setColumnWidths( alignment = columnAlignments[field.id] ?: "start",
listId, onAlignmentChange = { newAlign ->
fieldWidths.mapValues { it.value.value }, columnAlignments[field.id] = newAlign
) viewModel.updateFieldAlignment(field.id, newAlign)
}, prefs.setColumnAlignments(listId, columnAlignments.toMap())
scrollState = horizontalScrollState, },
isLast = (field.id == stableFields.lastOrNull()?.id), )
onHeaderClick = { showManageColumnsDialog = true }, }
alignment = columnAlignments[field.id] ?: "start", }
onAlignmentChange = { newAlign -> }
columnAlignments[field.id] = newAlign
viewModel.updateFieldAlignment(field.id, newAlign) // Content area: if empty, center message; else render horizontally scrollable rows
prefs.setColumnAlignments(listId, columnAlignments.toMap()) when {
}, isLoading && stableItems.isEmpty() -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) { androidx.compose.material3.CircularProgressIndicator() }
}
stableItems.isEmpty() -> {
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text(
text = stringResource(R.string.no_items),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp),
textAlign = TextAlign.Center,
)
}
}
processedItems.isEmpty() -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "No items match the current filter",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
) )
Spacer(modifier = Modifier.height(8.dp))
TextButton(onClick = {
filterField = null
filterValue = ""
}) { Text(stringResource(R.string.clear_filter)) }
} }
} }
} }
else -> {
// Render content states outside the horizontalScroll so empty state centers relative to viewport // Content list in a horizontally scrollable container to stay aligned with header
when { Column(
isLoading && stableItems.isEmpty() -> { modifier =
Box( Modifier
modifier = Modifier.fillMaxSize(), .fillMaxSize()
contentAlignment = Alignment.Center, .horizontalScroll(horizontalScrollState),
) { androidx.compose.material3.CircularProgressIndicator() } ) {
}
stableItems.isEmpty() -> {
// Use full-width Box + centered text (header remains above inside scroll container)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text(
text = stringResource(R.string.no_items),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp),
textAlign = TextAlign.Center,
)
}
}
processedItems.isEmpty() -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "No items match the current filter",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(modifier = Modifier.height(8.dp))
TextButton(onClick = {
filterField = null
filterValue = ""
}) { Text(stringResource(R.string.clear_filter)) }
}
}
}
else -> {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
state = listState, state = listState,