feat: add alignment property to fields and update related database and UI handling

This commit is contained in:
2025-11-12 00:15:16 +01:00
parent cb5880f861
commit f3181116d4
7 changed files with 100 additions and 47 deletions
@@ -38,7 +38,7 @@ val migration2To3 =
Item::class,
ItemValue::class,
],
version = 4,
version = 5,
exportSchema = false,
)
abstract class CollabTableDatabase : RoomDatabase() {
@@ -62,7 +62,7 @@ abstract class CollabTableDatabase : RoomDatabase() {
context.applicationContext,
CollabTableDatabase::class.java,
"collab_table_database",
).addMigrations(migration1To2, migration2To3, migration3To4)
).addMigrations(migration1To2, migration2To3, migration3To4, migration4To5)
.build()
dbInstance = instance
instance
@@ -85,3 +85,11 @@ val migration3To4 =
database.execSQL("CREATE INDEX IF NOT EXISTS index_items_listId_createdAt ON items(listId, createdAt)")
}
}
// Add alignment column to fields with default 'start'
val migration4To5 =
object : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE fields ADD COLUMN alignment TEXT NOT NULL DEFAULT 'start'")
}
}
@@ -63,6 +63,8 @@ data class Field(
val fieldType: String = "TEXT",
// JSON string for dropdown options, currency symbol, etc.
val fieldOptions: String = "",
// Content alignment for cells in this column: start | center | end
val alignment: String = "start",
val order: Int,
val createdAt: Long,
val updatedAt: Long,
@@ -162,16 +162,15 @@ fun ListDetailScreen(
val widthDp = (savedWidth ?: 150f).dp
fieldWidths[field.id] = widthDp
}
// Load saved alignments for this listId
// Initialize alignments from fields (server/DB source of truth); fallback to prefs if missing
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"
}
val raw = (field.alignment.ifBlank { savedAlign[field.id] ?: "start" }).lowercase()
columnAlignments[field.id] = when (raw) {
"center" -> "center"
"end", "right" -> "end"
else -> "start"
}
}
}
@@ -556,7 +555,9 @@ fun ListDetailScreen(
onHeaderClick = { showManageColumnsDialog = true },
alignment = columnAlignments[field.id] ?: "start",
onAlignmentChange = { newAlign ->
// Update local state and persist via ViewModel + cache to prefs for backward-compat
columnAlignments[field.id] = newAlign
viewModel.updateFieldAlignment(field.id, newAlign)
prefs.setColumnAlignments(listId, columnAlignments.toMap())
},
)
@@ -609,16 +610,14 @@ fun ListDetailScreen(
fields = stableFields,
onDismiss = {
showManageColumnsDialog = false
// Refresh alignments from preferences in case they changed while editing columns
val savedAlign = prefs.getColumnAlignments(listId)
// Refresh alignments from current fields (DB-backed)
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"
}
val a = field.alignment.lowercase().ifBlank { columnAlignments[field.id] ?: "start" }
columnAlignments[field.id] = when (a) {
"center" -> "center"
"end", "right" -> "end"
else -> "start"
}
}
},
onAddField = { name, fieldType, fieldOptions ->
@@ -627,6 +626,9 @@ fun ListDetailScreen(
onUpdateField = { fieldId, name, fieldType, fieldOptions ->
viewModel.updateField(fieldId, name, fieldType, fieldOptions)
},
onUpdateAlignment = { fieldId, alignment ->
viewModel.updateFieldAlignment(fieldId, alignment)
},
onDeleteField = { fieldId ->
viewModel.deleteField(fieldId)
},
@@ -1852,7 +1854,7 @@ fun AddFieldDialog(
fun EditFieldDialog(
field: Field,
onDismiss: () -> Unit,
onUpdate: (String, String, String) -> Unit,
onUpdate: (String, String, String, String) -> Unit,
) {
var name by remember { mutableStateOf(field.name) }
var selectedFieldType by remember { mutableStateOf(field.fieldType.uppercase()) }
@@ -2082,24 +2084,6 @@ fun EditFieldDialog(
}
}
// Content alignment (single-select) using a connected Material3 SegmentedButton group
Text(
text = "Content Alignment",
style = MaterialTheme.typography.titleSmall,
)
val alignmentOptions = listOf("start" to "Left", "center" to "Center", "end" to "Right")
SingleChoiceSegmentedButtonRow(modifier = Modifier.padding(top = 4.dp)) {
alignmentOptions.forEachIndexed { index, (value, label) ->
SegmentedButton(
selected = selectedAlignment == value,
onClick = { selectedAlignment = value },
shape = SegmentedButtonDefaults.itemShape(index = index, count = alignmentOptions.size),
icon = { if (selectedAlignment == value) Icon(Icons.Default.Check, contentDescription = null) },
label = { Text(label) },
)
}
}
// Currency-specific options
if (selectedFieldType == "CURRENCY" || selectedFieldType == "PRICE") {
OutlinedTextField(
@@ -2148,6 +2132,24 @@ fun EditFieldDialog(
supportingText = { Text("Maximum number of stars (default: 5)") },
)
}
// Content alignment (single-select) using a connected Material3 SegmentedButton group
Text(
text = "Content Alignment",
style = MaterialTheme.typography.titleSmall,
)
val alignmentOptions = listOf("start" to "Left", "center" to "Center", "end" to "Right")
SingleChoiceSegmentedButtonRow(modifier = Modifier.padding(top = 4.dp)) {
alignmentOptions.forEachIndexed { index, (value, label) ->
SegmentedButton(
selected = selectedAlignment == value,
onClick = { selectedAlignment = value },
shape = SegmentedButtonDefaults.itemShape(index = index, count = alignmentOptions.size),
icon = { if (selectedAlignment == value) Icon(Icons.Default.Check, contentDescription = null) },
label = { Text(label) },
)
}
}
}
},
confirmButton = {
@@ -2172,7 +2174,7 @@ fun EditFieldDialog(
"RATING" -> currency.trim().ifBlank { "5" }
else -> ""
}
onUpdate(name.trim(), normalizedType, options)
onUpdate(name.trim(), normalizedType, options, selectedAlignment)
// Persist alignment selection for this field
val current = prefs.getColumnAlignments(field.listId).toMutableMap()
current[field.id] =
@@ -2847,6 +2849,7 @@ fun ManageColumnsDialog(
onDismiss: () -> Unit,
onAddField: (String, String, String) -> Unit,
onUpdateField: (String, String, String, String) -> Unit,
onUpdateAlignment: (String, String) -> Unit,
onDeleteField: (String) -> Unit,
onReorderFields: (List<Field>) -> Unit,
) {
@@ -3034,8 +3037,9 @@ fun ManageColumnsDialog(
EditFieldDialog(
field = field,
onDismiss = { fieldToEdit = null },
onUpdate = { name, fieldType, fieldOptions ->
onUpdate = { name, fieldType, fieldOptions, selectedAlignment ->
onUpdateField(field.id, name, fieldType, fieldOptions)
onUpdateAlignment(field.id, selectedAlignment)
fieldToEdit = null
},
)
@@ -254,6 +254,32 @@ class ListDetailViewModel(
}
}
fun updateFieldAlignment(fieldId: String, alignment: String) {
viewModelScope.launch {
val field = database.fieldDao().getFieldById(fieldId)
if (field != null) {
val ts = System.currentTimeMillis()
val normalized = when (alignment.lowercase()) {
"center" -> "center"
"end", "right" -> "end"
else -> "start"
}
database.withTransaction {
database.fieldDao().updateField(
field.copy(
alignment = normalized,
updatedAt = ts,
),
)
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = ts))
}
}
performSync()
}
}
}
fun addItem() {
viewModelScope.launch {
val timestamp = System.currentTimeMillis()