feat: add per-list column alignment persistence and UI controls in ListDetailScreen

This commit is contained in:
2025-11-11 22:32:01 +01:00
parent 1624da76b2
commit b1e740c396
2 changed files with 259 additions and 103 deletions
@@ -223,6 +223,45 @@ class PreferencesManager(context: Context) {
prefs.edit().putString(key, json.toString()).apply() prefs.edit().putString(key, json.toString()).apply()
} }
// Persist per-list column alignments (fieldId -> alignment: "start" | "center" | "end")
// Stored as a JSON object string under key: COLUMN_ALIGN_PREFIX + listId
fun getColumnAlignments(listId: String): Map<String, String> {
val key = COLUMN_ALIGN_PREFIX + listId
val raw = prefs.getString(key, null) ?: return emptyMap()
return try {
val json = JSONObject(raw)
val map = mutableMapOf<String, String>()
val it = json.keys()
while (it.hasNext()) {
val fieldId = it.next()
val align = json.optString(fieldId, "start")
val normalized = when (align.lowercase()) {
"center" -> "center"
"end", "right" -> "end"
else -> "start"
}
map[fieldId] = normalized
}
map
} catch (e: Exception) {
emptyMap()
}
}
fun setColumnAlignments(listId: String, alignments: Map<String, String>) {
val key = COLUMN_ALIGN_PREFIX + listId
val json = JSONObject()
alignments.forEach { (fieldId, alignment) ->
val normalized = when (alignment.lowercase()) {
"center" -> "center"
"end", "right" -> "end"
else -> "start"
}
json.put(fieldId, normalized)
}
prefs.edit().putString(key, json.toString()).apply()
}
companion object { companion object {
private const val KEY_SERVER_URL = "server_url" private const val KEY_SERVER_URL = "server_url"
private const val KEY_FIRST_RUN = "first_run" private const val KEY_FIRST_RUN = "first_run"
@@ -238,6 +277,7 @@ class PreferencesManager(context: Context) {
private const val KEY_NOTIFY_LIST_REMOVED = "notify_list_removed" private const val KEY_NOTIFY_LIST_REMOVED = "notify_list_removed"
private const val KEY_LAST_LIST_NOTIFY_CHECK_TS = "last_list_notify_check_ts" private const val KEY_LAST_LIST_NOTIFY_CHECK_TS = "last_list_notify_check_ts"
private const val COLUMN_WIDTHS_PREFIX = "column_widths_" // + listId private const val COLUMN_WIDTHS_PREFIX = "column_widths_" // + listId
private const val COLUMN_ALIGN_PREFIX = "column_align_" // + listId
private const val DEFAULT_SERVER_URL = "http://10.0.2.2:3000/api/" private const val DEFAULT_SERVER_URL = "http://10.0.2.2:3000/api/"
private const val DEFAULT_SYNC_POLL_INTERVAL_MS = 250L private const val DEFAULT_SYNC_POLL_INTERVAL_MS = 250L
private const val MIN_SYNC_POLL_INTERVAL_MS = 250L private const val MIN_SYNC_POLL_INTERVAL_MS = 250L
@@ -68,6 +68,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
@@ -146,6 +147,9 @@ fun ListDetailScreen(
// Field widths state (resizable columns) // Field widths state (resizable columns)
val fieldWidths = remember { mutableStateMapOf<String, Dp>() } val fieldWidths = remember { mutableStateMapOf<String, Dp>() }
// Per-column content alignment: "start" | "center" | "end"
val columnAlignments = remember { mutableStateMapOf<String, String>() }
// Initialize field widths (load persisted per-list widths, fallback to default) // Initialize field widths (load persisted per-list widths, fallback to default)
LaunchedEffect(stableFields) { LaunchedEffect(stableFields) {
// Load saved widths for this listId // Load saved widths for this listId
@@ -155,6 +159,16 @@ fun ListDetailScreen(
val widthDp = (savedWidth ?: 150f).dp val widthDp = (savedWidth ?: 150f).dp
fieldWidths[field.id] = widthDp fieldWidths[field.id] = widthDp
} }
// Load saved alignments for this listId
val savedAlign = prefs.getColumnAlignments(listId)
stableFields.forEach { field ->
val a = savedAlign[field.id]?.lowercase() ?: "start"
columnAlignments[field.id] = when (a) {
"center" -> "center"
"end", "right" -> "end"
else -> "start"
}
}
} }
// Scaffold with top bar and FAB // Scaffold with top bar and FAB
@@ -524,6 +538,11 @@ fun ListDetailScreen(
scrollState = horizontalScrollState, scrollState = horizontalScrollState,
isLast = (field.id == stableFields.lastOrNull()?.id), isLast = (field.id == stableFields.lastOrNull()?.id),
onHeaderClick = { showManageColumnsDialog = true }, onHeaderClick = { showManageColumnsDialog = true },
alignment = columnAlignments[field.id] ?: "start",
onAlignmentChange = { newAlign ->
columnAlignments[field.id] = newAlign
prefs.setColumnAlignments(listId, columnAlignments.toMap())
},
) )
} }
} }
@@ -547,6 +566,7 @@ fun ListDetailScreen(
ItemRow( ItemRow(
fields = stableFields, fields = stableFields,
fieldWidths = fieldWidths, fieldWidths = fieldWidths,
fieldAlignments = columnAlignments,
itemWithValues = itemWithValues, itemWithValues = itemWithValues,
onClick = { itemToEdit = itemWithValues }, onClick = { itemToEdit = itemWithValues },
) )
@@ -562,7 +582,19 @@ fun ListDetailScreen(
if (showManageColumnsDialog) { if (showManageColumnsDialog) {
ManageColumnsDialog( ManageColumnsDialog(
fields = stableFields, fields = stableFields,
onDismiss = { showManageColumnsDialog = false }, onDismiss = {
showManageColumnsDialog = false
// Refresh alignments from preferences in case they changed while editing columns
val savedAlign = prefs.getColumnAlignments(listId)
stableFields.forEach { field ->
val a = savedAlign[field.id]?.lowercase() ?: columnAlignments[field.id] ?: "start"
columnAlignments[field.id] = when (a) {
"center" -> "center"
"end", "right" -> "end"
else -> "start"
}
}
},
onAddField = { name, fieldType, fieldOptions -> onAddField = { name, fieldType, fieldOptions ->
viewModel.addField(name, fieldType, fieldOptions) viewModel.addField(name, fieldType, fieldOptions)
}, },
@@ -796,6 +828,8 @@ fun FieldHeader(
scrollState: androidx.compose.foundation.ScrollState, scrollState: androidx.compose.foundation.ScrollState,
isLast: Boolean, isLast: Boolean,
onHeaderClick: () -> Unit, onHeaderClick: () -> Unit,
alignment: String = "start",
onAlignmentChange: (String) -> Unit = {},
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -826,6 +860,11 @@ fun FieldHeader(
Modifier Modifier
.weight(1f) .weight(1f)
.clickable { onHeaderClick() }, .clickable { onHeaderClick() },
textAlign = when (alignment) {
"center" -> TextAlign.Center
"end" -> TextAlign.End
else -> TextAlign.Start
},
) )
// Resize handle // Resize handle
@@ -897,6 +936,21 @@ fun FieldHeader(
tint = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6f), tint = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6f),
) )
} }
// Alignment segmented control (single-select)
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
val opts = listOf("start" to "L", "center" to "C", "end" to "R")
opts.forEach { (value, label) ->
FilterChip(
selected = alignment == value,
onClick = { onAlignmentChange(value) },
label = { Text(label) },
)
}
}
} }
} }
} }
@@ -907,6 +961,7 @@ fun FieldHeader(
fun ItemRow( fun ItemRow(
fields: List<Field>, fields: List<Field>,
fieldWidths: Map<String, Dp>, fieldWidths: Map<String, Dp>,
fieldAlignments: Map<String, String>,
itemWithValues: ItemWithValues, itemWithValues: ItemWithValues,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
@@ -928,6 +983,16 @@ fun ItemRow(
key(field.id) { key(field.id) {
val value = valuesByFieldId[field.id] val value = valuesByFieldId[field.id]
val fieldWidth = fieldWidths[field.id] ?: 150.dp val fieldWidth = fieldWidths[field.id] ?: 150.dp
val alignment = when (fieldAlignments[field.id]?.lowercase()) {
"center" -> Alignment.Center
"end", "right" -> Alignment.CenterEnd
else -> Alignment.CenterStart
}
val textAlign = when (fieldAlignments[field.id]?.lowercase()) {
"center" -> TextAlign.Center
"end", "right" -> TextAlign.End
else -> TextAlign.Start
}
Box( Box(
modifier = modifier =
@@ -945,6 +1010,7 @@ fun ItemRow(
Text( Text(
text = value?.value ?: "", text = value?.value ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -956,6 +1022,7 @@ fun ItemRow(
Text( Text(
text = value?.value ?: "", text = value?.value ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -967,6 +1034,7 @@ fun ItemRow(
Text( Text(
text = value?.value ?: "", text = value?.value ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -983,6 +1051,7 @@ fun ItemRow(
"${field.getCurrency()}${value?.value}" "${field.getCurrency()}${value?.value}"
}, },
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -994,6 +1063,7 @@ fun ItemRow(
Text( Text(
text = if (value?.value.isNullOrBlank()) "" else "${value?.value}%", text = if (value?.value.isNullOrBlank()) "" else "${value?.value}%",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1005,6 +1075,7 @@ fun ItemRow(
Text( Text(
text = value?.value ?: "", text = value?.value ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1016,6 +1087,7 @@ fun ItemRow(
Text( Text(
text = if (value?.value == "true") "" else "", text = if (value?.value == "true") "" else "",
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1027,36 +1099,35 @@ fun ItemRow(
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val urlValue = value?.value ?: "" val urlValue = value?.value ?: ""
if (urlValue.isNotBlank()) { if (urlValue.isNotBlank()) {
ClickableText( Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
text = ClickableText(
buildAnnotatedString { text =
withStyle( buildAnnotatedString {
style = withStyle(
SpanStyle( style =
color = MaterialTheme.colorScheme.primary, SpanStyle(
textDecoration = TextDecoration.Underline, color = MaterialTheme.colorScheme.primary,
), textDecoration = TextDecoration.Underline,
) { ),
append(urlValue) ) {
append(urlValue)
}
},
onClick = {
try {
uriHandler.openUri(urlValue)
} catch (e: Exception) {
// Handle invalid URL
} }
}, },
onClick = { style = MaterialTheme.typography.bodyMedium,
try { )
uriHandler.openUri(urlValue) }
} catch (e: Exception) {
// Handle invalid URL
}
},
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
style = MaterialTheme.typography.bodyMedium,
)
} else { } else {
Text( Text(
text = "", text = "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1069,36 +1140,35 @@ fun ItemRow(
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val emailValue = value?.value ?: "" val emailValue = value?.value ?: ""
if (emailValue.isNotBlank()) { if (emailValue.isNotBlank()) {
ClickableText( Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
text = ClickableText(
buildAnnotatedString { text =
withStyle( buildAnnotatedString {
style = withStyle(
SpanStyle( style =
color = MaterialTheme.colorScheme.primary, SpanStyle(
textDecoration = TextDecoration.Underline, color = MaterialTheme.colorScheme.primary,
), textDecoration = TextDecoration.Underline,
) { ),
append(emailValue) ) {
append(emailValue)
}
},
onClick = {
try {
uriHandler.openUri("mailto:$emailValue")
} catch (e: Exception) {
// Handle invalid email
} }
}, },
onClick = { style = MaterialTheme.typography.bodyMedium,
try { )
uriHandler.openUri("mailto:$emailValue") }
} catch (e: Exception) {
// Handle invalid email
}
},
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
style = MaterialTheme.typography.bodyMedium,
)
} else { } else {
Text( Text(
text = "", text = "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1111,36 +1181,35 @@ fun ItemRow(
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val phoneValue = value?.value ?: "" val phoneValue = value?.value ?: ""
if (phoneValue.isNotBlank()) { if (phoneValue.isNotBlank()) {
ClickableText( Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
text = ClickableText(
buildAnnotatedString { text =
withStyle( buildAnnotatedString {
style = withStyle(
SpanStyle( style =
color = MaterialTheme.colorScheme.primary, SpanStyle(
textDecoration = TextDecoration.Underline, color = MaterialTheme.colorScheme.primary,
), textDecoration = TextDecoration.Underline,
) { ),
append(phoneValue) ) {
append(phoneValue)
}
},
onClick = {
try {
uriHandler.openUri("tel:$phoneValue")
} catch (e: Exception) {
// Handle invalid phone
} }
}, },
onClick = { style = MaterialTheme.typography.bodyMedium,
try { )
uriHandler.openUri("tel:$phoneValue") }
} catch (e: Exception) {
// Handle invalid phone
}
},
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
style = MaterialTheme.typography.bodyMedium,
)
} else { } else {
Text( Text(
text = "", text = "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1153,6 +1222,7 @@ fun ItemRow(
Text( Text(
text = value?.value ?: "", text = value?.value ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1164,6 +1234,7 @@ fun ItemRow(
Text( Text(
text = value?.value ?: "", text = value?.value ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1175,6 +1246,7 @@ fun ItemRow(
Text( Text(
text = value?.value ?: "", text = value?.value ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1225,24 +1297,25 @@ fun ItemRow(
com.collabtable.app.data.model.FieldType.RATING -> { com.collabtable.app.data.model.FieldType.RATING -> {
val rating = value?.value?.toIntOrNull() ?: 0 val rating = value?.value?.toIntOrNull() ?: 0
val maxRating = field.getMaxRating() val maxRating = field.getMaxRating()
Row( Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
modifier = Row(
Modifier modifier =
.fillMaxWidth() Modifier
.padding(vertical = 8.dp), .padding(vertical = 8.dp),
) { ) {
repeat(maxRating) { index -> repeat(maxRating) { index ->
Icon( Icon(
imageVector = Icons.Default.Star, imageVector = Icons.Default.Star,
contentDescription = null, contentDescription = null,
tint = tint =
if (index < rating) { if (index < rating) {
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary
} else { } else {
MaterialTheme.colorScheme.outlineVariant MaterialTheme.colorScheme.outlineVariant
}, },
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
) )
}
} }
} }
} }
@@ -1279,22 +1352,25 @@ fun ItemRow(
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
} }
Text( Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
text = value?.value ?: "", Text(
style = MaterialTheme.typography.bodyMedium, text = value?.value ?: "",
) style = MaterialTheme.typography.bodyMedium,
)
}
} }
} }
com.collabtable.app.data.model.FieldType.LOCATION -> { com.collabtable.app.data.model.FieldType.LOCATION -> {
Text( Text(
text = value?.value ?: "", text = value?.value ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
modifier = textAlign = textAlign,
Modifier modifier =
.fillMaxWidth() Modifier
.padding(vertical = 8.dp), .fillMaxWidth()
) .padding(vertical = 8.dp),
)
} }
com.collabtable.app.data.model.FieldType.IMAGE -> { com.collabtable.app.data.model.FieldType.IMAGE -> {
@@ -1302,6 +1378,7 @@ fun ItemRow(
Text( Text(
text = "🖼️ ${value.value}", text = "🖼️ ${value.value}",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1311,6 +1388,7 @@ fun ItemRow(
Text( Text(
text = "", text = "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1324,6 +1402,7 @@ fun ItemRow(
Text( Text(
text = "📎 ${value.value}", text = "📎 ${value.value}",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1333,6 +1412,7 @@ fun ItemRow(
Text( Text(
text = "", text = "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1348,6 +1428,7 @@ fun ItemRow(
MaterialTheme.typography.bodyMedium.copy( MaterialTheme.typography.bodyMedium.copy(
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace, fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
), ),
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1361,6 +1442,7 @@ fun ItemRow(
text = "✍️ Signed", text = "✍️ Signed",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1370,6 +1452,7 @@ fun ItemRow(
Text( Text(
text = "", text = "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = textAlign,
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1710,6 +1793,14 @@ fun EditFieldDialog(
var dropdownOptions by remember { mutableStateOf(field.getDropdownOptions().joinToString(", ")) } var dropdownOptions by remember { mutableStateOf(field.getDropdownOptions().joinToString(", ")) }
var currency by remember { mutableStateOf(field.getCurrency()) } var currency by remember { mutableStateOf(field.getCurrency()) }
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
// Alignment state persisted per list/field in PreferencesManager
val context = LocalContext.current
val prefs = remember { PreferencesManager.getInstance(context) }
var selectedAlignment by remember {
mutableStateOf(
prefs.getColumnAlignments(field.listId)[field.id]?.lowercase() ?: "start",
)
}
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
@@ -1925,6 +2016,23 @@ fun EditFieldDialog(
} }
} }
// Content alignment (single-select) using FilterChips to support current Material3 version
Text(
text = "Content Alignment",
style = MaterialTheme.typography.titleSmall,
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
val opts = listOf("start" to "Left", "center" to "Center", "end" to "Right")
opts.forEach { (value, label) ->
FilterChip(
selected = selectedAlignment == value,
onClick = { selectedAlignment = value },
label = { Text(label) },
leadingIcon = if (selectedAlignment == value) { { Icon(Icons.Default.Check, contentDescription = null) } } else null,
)
}
}
// Currency-specific options // Currency-specific options
if (selectedFieldType == "CURRENCY" || selectedFieldType == "PRICE") { if (selectedFieldType == "CURRENCY" || selectedFieldType == "PRICE") {
OutlinedTextField( OutlinedTextField(
@@ -1996,6 +2104,14 @@ fun EditFieldDialog(
else -> "" else -> ""
} }
onUpdate(name.trim(), normalizedType, options) onUpdate(name.trim(), normalizedType, options)
// Persist alignment selection for this field
val current = prefs.getColumnAlignments(field.listId).toMutableMap()
current[field.id] = when (selectedAlignment.lowercase()) {
"center" -> "center"
"end", "right" -> "end"
else -> "start"
}
prefs.setColumnAlignments(field.listId, current)
}, },
enabled = name.isNotBlank() && (selectedFieldType !in listOf("DROPDOWN", "AUTOCOMPLETE") || dropdownOptions.isNotBlank()), enabled = name.isNotBlank() && (selectedFieldType !in listOf("DROPDOWN", "AUTOCOMPLETE") || dropdownOptions.isNotBlank()),
) { ) {