feat: add per-list column alignment persistence and UI controls in ListDetailScreen
This commit is contained in:
+40
@@ -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
|
||||||
|
|||||||
+130
-14
@@ -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,6 +1099,7 @@ fun ItemRow(
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val urlValue = value?.value ?: ""
|
val urlValue = value?.value ?: ""
|
||||||
if (urlValue.isNotBlank()) {
|
if (urlValue.isNotBlank()) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text =
|
text =
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
@@ -1047,16 +1120,14 @@ fun ItemRow(
|
|||||||
// Handle invalid URL
|
// Handle invalid URL
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
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,6 +1140,7 @@ fun ItemRow(
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val emailValue = value?.value ?: ""
|
val emailValue = value?.value ?: ""
|
||||||
if (emailValue.isNotBlank()) {
|
if (emailValue.isNotBlank()) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text =
|
text =
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
@@ -1089,16 +1161,14 @@ fun ItemRow(
|
|||||||
// Handle invalid email
|
// Handle invalid email
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
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,6 +1181,7 @@ fun ItemRow(
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val phoneValue = value?.value ?: ""
|
val phoneValue = value?.value ?: ""
|
||||||
if (phoneValue.isNotBlank()) {
|
if (phoneValue.isNotBlank()) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text =
|
text =
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
@@ -1131,16 +1202,14 @@ fun ItemRow(
|
|||||||
// Handle invalid phone
|
// Handle invalid phone
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
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,10 +1297,10 @@ 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()
|
||||||
|
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 8.dp),
|
.padding(vertical = 8.dp),
|
||||||
) {
|
) {
|
||||||
repeat(maxRating) { index ->
|
repeat(maxRating) { index ->
|
||||||
@@ -1246,6 +1318,7 @@ fun ItemRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
com.collabtable.app.data.model.FieldType.COLOR -> {
|
com.collabtable.app.data.model.FieldType.COLOR -> {
|
||||||
Row(
|
Row(
|
||||||
@@ -1279,17 +1352,20 @@ fun ItemRow(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
|
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = alignment) {
|
||||||
Text(
|
Text(
|
||||||
text = value?.value ?: "",
|
text = value?.value ?: "",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
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,
|
||||||
|
textAlign = textAlign,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -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()),
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user