fix: correct indentation and formatting in PreferencesManager and ListDetailScreen for improved readability
This commit is contained in:
+1
-1
@@ -271,7 +271,7 @@ class PreferencesManager(
|
|||||||
private const val KEY_NOTIFY_LIST_ADDED = "notify_list_added"
|
private const val KEY_NOTIFY_LIST_ADDED = "notify_list_added"
|
||||||
private const val KEY_NOTIFY_LIST_EDITED = "notify_list_edited"
|
private const val KEY_NOTIFY_LIST_EDITED = "notify_list_edited"
|
||||||
private const val KEY_NOTIFY_LIST_REMOVED = "notify_list_removed"
|
private const val KEY_NOTIFY_LIST_REMOVED = "notify_list_removed"
|
||||||
private const val KEY_NOTIFY_LIST_CONTENT_UPDATED = "notify_list_content_updated"
|
private const val KEY_NOTIFY_LIST_CONTENT_UPDATED = "notify_list_content_updated"
|
||||||
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 COLUMN_ALIGN_PREFIX = "column_align_" // + listId
|
||||||
|
|||||||
+434
-431
@@ -166,11 +166,12 @@ fun ListDetailScreen(
|
|||||||
val savedAlign = prefs.getColumnAlignments(listId)
|
val savedAlign = prefs.getColumnAlignments(listId)
|
||||||
stableFields.forEach { field ->
|
stableFields.forEach { field ->
|
||||||
val raw = (field.alignment.ifBlank { savedAlign[field.id] ?: "start" }).lowercase()
|
val raw = (field.alignment.ifBlank { savedAlign[field.id] ?: "start" }).lowercase()
|
||||||
columnAlignments[field.id] = when (raw) {
|
columnAlignments[field.id] =
|
||||||
"center" -> "center"
|
when (raw) {
|
||||||
"end", "right" -> "end"
|
"center" -> "center"
|
||||||
else -> "start"
|
"end", "right" -> "end"
|
||||||
}
|
else -> "start"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,333 +270,335 @@ fun ListDetailScreen(
|
|||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
// Filter/Sort/Group Controls - Always visible above table
|
// Filter/Sort/Group Controls - Always visible above table
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.horizontalScroll(rememberScrollState())
|
.horizontalScroll(rememberScrollState())
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
|
||||||
// Sort button
|
|
||||||
FilterChip(
|
|
||||||
selected = sortField != null,
|
|
||||||
onClick = { showSortDialog = true },
|
|
||||||
label = {
|
|
||||||
Text(
|
|
||||||
if (sortField != null) {
|
|
||||||
"Sort: ${sortField!!.name} ${if (sortAscending) "↑" else "↓"}"
|
|
||||||
} else {
|
|
||||||
"Sort"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Search,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailingIcon =
|
|
||||||
if (sortField != null) {
|
|
||||||
{
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
sortField = null
|
|
||||||
sortAscending = true
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Close,
|
|
||||||
contentDescription = "Clear",
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Group button
|
|
||||||
FilterChip(
|
|
||||||
selected = groupByField != null,
|
|
||||||
onClick = { showGroupDialog = true },
|
|
||||||
label = {
|
|
||||||
Text(
|
|
||||||
if (groupByField != null) {
|
|
||||||
"Group: ${groupByField!!.name}"
|
|
||||||
} else {
|
|
||||||
"Group"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
Icons.AutoMirrored.Filled.List,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailingIcon =
|
|
||||||
if (groupByField != null) {
|
|
||||||
{
|
|
||||||
IconButton(
|
|
||||||
onClick = { groupByField = null },
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Close,
|
|
||||||
contentDescription = "Clear",
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter button
|
|
||||||
FilterChip(
|
|
||||||
selected = filterField != null,
|
|
||||||
onClick = { showFilterDialog = true },
|
|
||||||
label = {
|
|
||||||
Text(
|
|
||||||
if (filterField != null) {
|
|
||||||
"Filter: ${filterField!!.name}"
|
|
||||||
} else {
|
|
||||||
"Filter"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Add,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailingIcon =
|
|
||||||
if (filterField != null) {
|
|
||||||
{
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
filterField = null
|
|
||||||
filterValue = ""
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Close,
|
|
||||||
contentDescription = "Clear",
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Auto-resize button (chip style)
|
|
||||||
// Auto-fit cache keyed by fieldId with a compact signature based on timestamps and counts
|
|
||||||
val autoFitCache = remember { mutableStateMapOf<String, Pair<AutoFitSignature, Float>>() }
|
|
||||||
FilterChip(
|
|
||||||
selected = false,
|
|
||||||
onClick = {
|
|
||||||
stableFields.forEach { field ->
|
|
||||||
// Build a compact signature: field name, field.updatedAt, item count, max updatedAt among values
|
|
||||||
var maxValUpdated = 0L
|
|
||||||
stableItems.forEach { item ->
|
|
||||||
val v = item.values.find { it.fieldId == field.id }
|
|
||||||
if (v != null && v.updatedAt > maxValUpdated) maxValUpdated = v.updatedAt
|
|
||||||
}
|
|
||||||
val signature =
|
|
||||||
AutoFitSignature(
|
|
||||||
name = field.name,
|
|
||||||
fieldUpdatedAt = field.updatedAt,
|
|
||||||
itemCount = stableItems.size,
|
|
||||||
maxValueUpdatedAt = maxValUpdated,
|
|
||||||
)
|
|
||||||
|
|
||||||
val cached = autoFitCache[field.id]
|
|
||||||
if (cached != null && cached.first == signature) {
|
|
||||||
// Use cached width
|
|
||||||
fieldWidths[field.id] = cached.second.dp
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
// Measure header and max content width
|
|
||||||
val headerPx =
|
|
||||||
textMeasurer
|
|
||||||
.measure(AnnotatedString(field.name), style = headerTextStyle)
|
|
||||||
.size.width
|
|
||||||
.toFloat()
|
|
||||||
var maxContentPx = 0f
|
|
||||||
stableItems.forEach { itemWithValues ->
|
|
||||||
val v = itemWithValues.values.find { it.fieldId == field.id }?.value
|
|
||||||
val display = getDisplayTextForMeasure(field, v)
|
|
||||||
if (display.isNotEmpty()) {
|
|
||||||
val w =
|
|
||||||
textMeasurer
|
|
||||||
.measure(AnnotatedString(display), style = bodyTextStyle)
|
|
||||||
.size.width
|
|
||||||
.toFloat()
|
|
||||||
if (w > maxContentPx) maxContentPx = w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val widthDpValue =
|
|
||||||
with(density) {
|
|
||||||
val headerDp = headerPx.toDp() + 12.dp + 12.dp + 24.dp + 2.dp
|
|
||||||
val contentDp = maxContentPx.toDp() + 8.dp + 8.dp + 2.dp
|
|
||||||
val base = maxOf(headerDp, contentDp, 100.dp)
|
|
||||||
(base + 6.dp).value
|
|
||||||
}
|
|
||||||
fieldWidths[field.id] = widthDpValue.dp
|
|
||||||
autoFitCache[field.id] = signature to widthDpValue
|
|
||||||
}
|
|
||||||
prefs.setColumnWidths(listId, fieldWidths.mapValues { it.value.value })
|
|
||||||
},
|
|
||||||
label = { Text("Auto-fit") },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.FitScreen,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header will be rendered as a stickyHeader inside the LazyColumn below
|
|
||||||
|
|
||||||
// Items list with synchronized scrolling
|
|
||||||
if (stableItems.isEmpty()) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
) {
|
||||||
Text(
|
// Sort button
|
||||||
text = stringResource(R.string.no_items),
|
FilterChip(
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
selected = sortField != null,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
onClick = { showSortDialog = true },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
if (sortField != null) {
|
||||||
|
"Sort: ${sortField!!.name} ${if (sortAscending) "↑" else "↓"}"
|
||||||
|
} else {
|
||||||
|
"Sort"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Search,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon =
|
||||||
|
if (sortField != null) {
|
||||||
|
{
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
sortField = null
|
||||||
|
sortAscending = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Close,
|
||||||
|
contentDescription = "Clear",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Group button
|
||||||
|
FilterChip(
|
||||||
|
selected = groupByField != null,
|
||||||
|
onClick = { showGroupDialog = true },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
if (groupByField != null) {
|
||||||
|
"Group: ${groupByField!!.name}"
|
||||||
|
} else {
|
||||||
|
"Group"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Filled.List,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon =
|
||||||
|
if (groupByField != null) {
|
||||||
|
{
|
||||||
|
IconButton(
|
||||||
|
onClick = { groupByField = null },
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Close,
|
||||||
|
contentDescription = "Clear",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter button
|
||||||
|
FilterChip(
|
||||||
|
selected = filterField != null,
|
||||||
|
onClick = { showFilterDialog = true },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
if (filterField != null) {
|
||||||
|
"Filter: ${filterField!!.name}"
|
||||||
|
} else {
|
||||||
|
"Filter"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Add,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon =
|
||||||
|
if (filterField != null) {
|
||||||
|
{
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
filterField = null
|
||||||
|
filterValue = ""
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Close,
|
||||||
|
contentDescription = "Clear",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto-resize button (chip style)
|
||||||
|
// Auto-fit cache keyed by fieldId with a compact signature based on timestamps and counts
|
||||||
|
val autoFitCache = remember { mutableStateMapOf<String, Pair<AutoFitSignature, Float>>() }
|
||||||
|
FilterChip(
|
||||||
|
selected = false,
|
||||||
|
onClick = {
|
||||||
|
stableFields.forEach { field ->
|
||||||
|
// Build a compact signature: field name, field.updatedAt, item count, max updatedAt among values
|
||||||
|
var maxValUpdated = 0L
|
||||||
|
stableItems.forEach { item ->
|
||||||
|
val v = item.values.find { it.fieldId == field.id }
|
||||||
|
if (v != null && v.updatedAt > maxValUpdated) maxValUpdated = v.updatedAt
|
||||||
|
}
|
||||||
|
val signature =
|
||||||
|
AutoFitSignature(
|
||||||
|
name = field.name,
|
||||||
|
fieldUpdatedAt = field.updatedAt,
|
||||||
|
itemCount = stableItems.size,
|
||||||
|
maxValueUpdatedAt = maxValUpdated,
|
||||||
|
)
|
||||||
|
|
||||||
|
val cached = autoFitCache[field.id]
|
||||||
|
if (cached != null && cached.first == signature) {
|
||||||
|
// Use cached width
|
||||||
|
fieldWidths[field.id] = cached.second.dp
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure header and max content width
|
||||||
|
val headerPx =
|
||||||
|
textMeasurer
|
||||||
|
.measure(AnnotatedString(field.name), style = headerTextStyle)
|
||||||
|
.size.width
|
||||||
|
.toFloat()
|
||||||
|
var maxContentPx = 0f
|
||||||
|
stableItems.forEach { itemWithValues ->
|
||||||
|
val v = itemWithValues.values.find { it.fieldId == field.id }?.value
|
||||||
|
val display = getDisplayTextForMeasure(field, v)
|
||||||
|
if (display.isNotEmpty()) {
|
||||||
|
val w =
|
||||||
|
textMeasurer
|
||||||
|
.measure(AnnotatedString(display), style = bodyTextStyle)
|
||||||
|
.size.width
|
||||||
|
.toFloat()
|
||||||
|
if (w > maxContentPx) maxContentPx = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val widthDpValue =
|
||||||
|
with(density) {
|
||||||
|
val headerDp = headerPx.toDp() + 12.dp + 12.dp + 24.dp + 2.dp
|
||||||
|
val contentDp = maxContentPx.toDp() + 8.dp + 8.dp + 2.dp
|
||||||
|
val base = maxOf(headerDp, contentDp, 100.dp)
|
||||||
|
(base + 6.dp).value
|
||||||
|
}
|
||||||
|
fieldWidths[field.id] = widthDpValue.dp
|
||||||
|
autoFitCache[field.id] = signature to widthDpValue
|
||||||
|
}
|
||||||
|
prefs.setColumnWidths(listId, fieldWidths.mapValues { it.value.value })
|
||||||
|
},
|
||||||
|
label = { Text("Auto-fit") },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.FitScreen,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (processedItems.isEmpty()) {
|
|
||||||
Box(
|
// Header will be rendered as a stickyHeader inside the LazyColumn below
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center,
|
// Items list with synchronized scrolling
|
||||||
) {
|
if (stableItems.isEmpty()) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "No items match the current filter",
|
text = stringResource(R.string.no_items),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
}
|
||||||
TextButton(onClick = {
|
} else if (processedItems.isEmpty()) {
|
||||||
filterField = null
|
Box(
|
||||||
filterValue = ""
|
modifier = Modifier.fillMaxSize(),
|
||||||
}) {
|
contentAlignment = Alignment.Center,
|
||||||
Text("Clear Filter")
|
) {
|
||||||
|
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("Clear Filter")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
val listState = rememberLazyListState()
|
||||||
val listState = rememberLazyListState()
|
// If a new item was just added, scroll to bottom after composition
|
||||||
// If a new item was just added, scroll to bottom after composition
|
LaunchedEffect(processedItems.size, pendingScrollToBottom) {
|
||||||
LaunchedEffect(processedItems.size, pendingScrollToBottom) {
|
if (pendingScrollToBottom) {
|
||||||
if (pendingScrollToBottom) {
|
// Scroll to the last real item index
|
||||||
// Scroll to the last real item index
|
val lastIndex = (listState.layoutInfo.totalItemsCount - 1).coerceAtLeast(0)
|
||||||
val lastIndex = (listState.layoutInfo.totalItemsCount - 1).coerceAtLeast(0)
|
try {
|
||||||
try {
|
listState.animateScrollToItem(lastIndex)
|
||||||
listState.animateScrollToItem(lastIndex)
|
} catch (_: Exception) {
|
||||||
} catch (_: Exception) {
|
listState.scrollToItem(lastIndex)
|
||||||
listState.scrollToItem(lastIndex)
|
}
|
||||||
|
pendingScrollToBottom = false
|
||||||
}
|
}
|
||||||
pendingScrollToBottom = false
|
|
||||||
}
|
}
|
||||||
}
|
Column(
|
||||||
Column(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.horizontalScroll(horizontalScrollState),
|
|
||||||
) {
|
|
||||||
// Fixed header (does not participate in vertical scroll)
|
|
||||||
Row(
|
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.surface),
|
.horizontalScroll(horizontalScrollState),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
) {
|
||||||
stableFields.forEach { field ->
|
// Fixed header (does not participate in vertical scroll)
|
||||||
key(field.id) {
|
Row(
|
||||||
FieldHeader(
|
modifier =
|
||||||
field = field,
|
Modifier
|
||||||
width = fieldWidths[field.id] ?: 150.dp,
|
.fillMaxWidth()
|
||||||
onWidthChange = { delta ->
|
.background(MaterialTheme.colorScheme.surface),
|
||||||
val currentWidth = fieldWidths[field.id] ?: 150.dp
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
val newWidth = (currentWidth.value + delta).coerceAtLeast(100f)
|
) {
|
||||||
fieldWidths[field.id] = newWidth.dp
|
stableFields.forEach { field ->
|
||||||
prefs.setColumnWidths(
|
key(field.id) {
|
||||||
listId,
|
FieldHeader(
|
||||||
fieldWidths.mapValues { it.value.value },
|
field = field,
|
||||||
)
|
width = fieldWidths[field.id] ?: 150.dp,
|
||||||
},
|
onWidthChange = { delta ->
|
||||||
scrollState = horizontalScrollState,
|
val currentWidth = fieldWidths[field.id] ?: 150.dp
|
||||||
isLast = (field.id == stableFields.lastOrNull()?.id),
|
val newWidth = (currentWidth.value + delta).coerceAtLeast(100f)
|
||||||
onHeaderClick = { showManageColumnsDialog = true },
|
fieldWidths[field.id] = newWidth.dp
|
||||||
alignment = columnAlignments[field.id] ?: "start",
|
prefs.setColumnWidths(
|
||||||
onAlignmentChange = { newAlign ->
|
listId,
|
||||||
// Update local state and persist via ViewModel + cache to prefs for backward-compat
|
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 ->
|
||||||
|
// 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())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading overlay only while nothing is available to render yet
|
||||||
|
if (isLoading && stableFields.isEmpty() && stableItems.isEmpty()) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.CircularProgressIndicator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading overlay only while nothing is available to render yet
|
// Items list below the fixed header
|
||||||
if (isLoading && stableFields.isEmpty() && stableItems.isEmpty()) {
|
LazyColumn(
|
||||||
Box(
|
modifier =
|
||||||
modifier = Modifier.fillMaxSize(),
|
Modifier
|
||||||
contentAlignment = Alignment.Center,
|
.fillMaxSize(),
|
||||||
) {
|
state = listState,
|
||||||
androidx.compose.material3.CircularProgressIndicator()
|
contentPadding = PaddingValues(0.dp),
|
||||||
}
|
) {
|
||||||
}
|
groupedItems.entries.forEach { (_, groupItems) ->
|
||||||
}
|
// Show items in the group
|
||||||
|
items(
|
||||||
// Items list below the fixed header
|
items = groupItems,
|
||||||
LazyColumn(
|
key = { it.item.id },
|
||||||
modifier = Modifier
|
contentType = { "row" },
|
||||||
.fillMaxSize(),
|
) { itemWithValues ->
|
||||||
state = listState,
|
ItemRow(
|
||||||
contentPadding = PaddingValues(0.dp),
|
fields = stableFields,
|
||||||
) {
|
fieldWidths = fieldWidths,
|
||||||
groupedItems.entries.forEach { (_, groupItems) ->
|
fieldAlignments = columnAlignments,
|
||||||
// Show items in the group
|
itemWithValues = itemWithValues,
|
||||||
items(
|
onClick = { itemToEdit = itemWithValues },
|
||||||
items = groupItems,
|
)
|
||||||
key = { it.item.id },
|
}
|
||||||
contentType = { "row" },
|
|
||||||
) { itemWithValues ->
|
|
||||||
ItemRow(
|
|
||||||
fields = stableFields,
|
|
||||||
fieldWidths = fieldWidths,
|
|
||||||
fieldAlignments = columnAlignments,
|
|
||||||
itemWithValues = itemWithValues,
|
|
||||||
onClick = { itemToEdit = itemWithValues },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -603,129 +606,129 @@ fun ListDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (showManageColumnsDialog) {
|
if (showManageColumnsDialog) {
|
||||||
ManageColumnsDialog(
|
ManageColumnsDialog(
|
||||||
fields = stableFields,
|
fields = stableFields,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
showManageColumnsDialog = false
|
showManageColumnsDialog = false
|
||||||
// Refresh alignments from current fields (DB-backed)
|
// Refresh alignments from current fields (DB-backed)
|
||||||
stableFields.forEach { field ->
|
stableFields.forEach { field ->
|
||||||
val a = field.alignment.lowercase().ifBlank { columnAlignments[field.id] ?: "start" }
|
val a = field.alignment.lowercase().ifBlank { columnAlignments[field.id] ?: "start" }
|
||||||
columnAlignments[field.id] = when (a) {
|
columnAlignments[field.id] =
|
||||||
"center" -> "center"
|
when (a) {
|
||||||
"end", "right" -> "end"
|
"center" -> "center"
|
||||||
else -> "start"
|
"end", "right" -> "end"
|
||||||
|
else -> "start"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
onAddField = { name, fieldType, fieldOptions ->
|
||||||
onAddField = { name, fieldType, fieldOptions ->
|
viewModel.addField(name, fieldType, fieldOptions)
|
||||||
viewModel.addField(name, fieldType, fieldOptions)
|
},
|
||||||
},
|
onUpdateField = { fieldId, name, fieldType, fieldOptions ->
|
||||||
onUpdateField = { fieldId, name, fieldType, fieldOptions ->
|
viewModel.updateField(fieldId, name, fieldType, fieldOptions)
|
||||||
viewModel.updateField(fieldId, name, fieldType, fieldOptions)
|
},
|
||||||
},
|
onUpdateAlignment = { fieldId, alignment ->
|
||||||
onUpdateAlignment = { fieldId, alignment ->
|
viewModel.updateFieldAlignment(fieldId, alignment)
|
||||||
viewModel.updateFieldAlignment(fieldId, alignment)
|
},
|
||||||
},
|
onDeleteField = { fieldId ->
|
||||||
onDeleteField = { fieldId ->
|
viewModel.deleteField(fieldId)
|
||||||
viewModel.deleteField(fieldId)
|
},
|
||||||
},
|
onReorderFields = { reorderedFields ->
|
||||||
onReorderFields = { reorderedFields ->
|
viewModel.reorderFields(reorderedFields)
|
||||||
viewModel.reorderFields(reorderedFields)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showAddItemDialog) {
|
|
||||||
AddItemDialog(
|
|
||||||
fields = stableFields,
|
|
||||||
onDismiss = { showAddItemDialog = false },
|
|
||||||
onAdd = { fieldValues ->
|
|
||||||
viewModel.addItemWithValues(fieldValues)
|
|
||||||
showAddItemDialog = false
|
|
||||||
pendingScrollToBottom = true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort Dialog
|
|
||||||
if (showSortDialog) {
|
|
||||||
SortDialog(
|
|
||||||
fields = stableFields,
|
|
||||||
currentSortField = sortField,
|
|
||||||
currentSortAscending = sortAscending,
|
|
||||||
onDismiss = { showSortDialog = false },
|
|
||||||
onApply = { newSortField, newSortAscending ->
|
|
||||||
sortField = newSortField
|
|
||||||
sortAscending = newSortAscending
|
|
||||||
showSortDialog = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group Dialog
|
|
||||||
if (showGroupDialog) {
|
|
||||||
GroupDialog(
|
|
||||||
fields = stableFields,
|
|
||||||
currentGroupByField = groupByField,
|
|
||||||
onDismiss = { showGroupDialog = false },
|
|
||||||
onApply = { newGroupByField ->
|
|
||||||
groupByField = newGroupByField
|
|
||||||
showGroupDialog = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter Dialog
|
|
||||||
if (showFilterDialog) {
|
|
||||||
FilterDialog(
|
|
||||||
fields = stableFields,
|
|
||||||
currentFilterField = filterField,
|
|
||||||
currentFilterValue = filterValue,
|
|
||||||
onDismiss = { showFilterDialog = false },
|
|
||||||
onApply = { newFilterField, newFilterValue ->
|
|
||||||
filterField = newFilterField
|
|
||||||
filterValue = newFilterValue
|
|
||||||
showFilterDialog = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename List Dialog
|
|
||||||
if (showRenameListDialog) {
|
|
||||||
list?.let { currentList ->
|
|
||||||
RenameListDialog(
|
|
||||||
currentName = currentList.name,
|
|
||||||
onDismiss = { showRenameListDialog = false },
|
|
||||||
onRename = { newName ->
|
|
||||||
viewModel.renameList(newName)
|
|
||||||
showRenameListDialog = false
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
itemToEdit?.let { itemWithValues ->
|
if (showAddItemDialog) {
|
||||||
EditItemDialog(
|
AddItemDialog(
|
||||||
fields = stableFields,
|
fields = stableFields,
|
||||||
itemWithValues = itemWithValues,
|
onDismiss = { showAddItemDialog = false },
|
||||||
onDismiss = { itemToEdit = null },
|
onAdd = { fieldValues ->
|
||||||
onUpdate = { fieldValues ->
|
viewModel.addItemWithValues(fieldValues)
|
||||||
fieldValues.forEach { (valueId, newValue) ->
|
showAddItemDialog = false
|
||||||
viewModel.updateItemValue(valueId, newValue)
|
pendingScrollToBottom = true
|
||||||
}
|
},
|
||||||
itemToEdit = null
|
)
|
||||||
},
|
}
|
||||||
onDelete = {
|
|
||||||
viewModel.deleteItem(itemWithValues.item.id)
|
// Sort Dialog
|
||||||
itemToEdit = null
|
if (showSortDialog) {
|
||||||
},
|
SortDialog(
|
||||||
)
|
fields = stableFields,
|
||||||
|
currentSortField = sortField,
|
||||||
|
currentSortAscending = sortAscending,
|
||||||
|
onDismiss = { showSortDialog = false },
|
||||||
|
onApply = { newSortField, newSortAscending ->
|
||||||
|
sortField = newSortField
|
||||||
|
sortAscending = newSortAscending
|
||||||
|
showSortDialog = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group Dialog
|
||||||
|
if (showGroupDialog) {
|
||||||
|
GroupDialog(
|
||||||
|
fields = stableFields,
|
||||||
|
currentGroupByField = groupByField,
|
||||||
|
onDismiss = { showGroupDialog = false },
|
||||||
|
onApply = { newGroupByField ->
|
||||||
|
groupByField = newGroupByField
|
||||||
|
showGroupDialog = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter Dialog
|
||||||
|
if (showFilterDialog) {
|
||||||
|
FilterDialog(
|
||||||
|
fields = stableFields,
|
||||||
|
currentFilterField = filterField,
|
||||||
|
currentFilterValue = filterValue,
|
||||||
|
onDismiss = { showFilterDialog = false },
|
||||||
|
onApply = { newFilterField, newFilterValue ->
|
||||||
|
filterField = newFilterField
|
||||||
|
filterValue = newFilterValue
|
||||||
|
showFilterDialog = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename List Dialog
|
||||||
|
if (showRenameListDialog) {
|
||||||
|
list?.let { currentList ->
|
||||||
|
RenameListDialog(
|
||||||
|
currentName = currentList.name,
|
||||||
|
onDismiss = { showRenameListDialog = false },
|
||||||
|
onRename = { newName ->
|
||||||
|
viewModel.renameList(newName)
|
||||||
|
showRenameListDialog = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemToEdit?.let { itemWithValues ->
|
||||||
|
EditItemDialog(
|
||||||
|
fields = stableFields,
|
||||||
|
itemWithValues = itemWithValues,
|
||||||
|
onDismiss = { itemToEdit = null },
|
||||||
|
onUpdate = { fieldValues ->
|
||||||
|
fieldValues.forEach { (valueId, newValue) ->
|
||||||
|
viewModel.updateItemValue(valueId, newValue)
|
||||||
|
}
|
||||||
|
itemToEdit = null
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
viewModel.deleteItem(itemWithValues.item.id)
|
||||||
|
itemToEdit = null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// END ListDetailScreen composable
|
||||||
}
|
}
|
||||||
// END ListDetailScreen composable
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extra closing brace to properly terminate ListDetailScreen; previous edits removed one
|
// Extra closing brace to properly terminate ListDetailScreen; previous edits removed one
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-10
@@ -1,10 +1,10 @@
|
|||||||
package com.collabtable.app.ui.screens
|
package com.collabtable.app.ui.screens
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import com.collabtable.app.data.database.CollabTableDatabase
|
import com.collabtable.app.data.database.CollabTableDatabase
|
||||||
import com.collabtable.app.data.model.CollabList
|
import com.collabtable.app.data.model.CollabList
|
||||||
@@ -51,6 +51,7 @@ class ListDetailViewModel(
|
|||||||
var hasList = false
|
var hasList = false
|
||||||
var hasFields = false
|
var hasFields = false
|
||||||
var hasItems = false
|
var hasItems = false
|
||||||
|
|
||||||
fun maybeLoaded() {
|
fun maybeLoaded() {
|
||||||
if (hasFields && hasItems) {
|
if (hasFields && hasItems) {
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
@@ -128,10 +129,13 @@ class ListDetailViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeNotifyListContentUpdated() {
|
private fun maybeNotifyListContentUpdated() {
|
||||||
val prefs = com.collabtable.app.data.preferences.PreferencesManager.getInstance(context)
|
val prefs =
|
||||||
|
com.collabtable.app.data.preferences.PreferencesManager
|
||||||
|
.getInstance(context)
|
||||||
if (prefs.notifyListContentUpdated.value && !isInForeground()) {
|
if (prefs.notifyListContentUpdated.value && !isInForeground()) {
|
||||||
val name = _list.value?.name ?: "Table"
|
val name = _list.value?.name ?: "Table"
|
||||||
com.collabtable.app.notifications.NotificationHelper.showListContentUpdated(context, listId, name)
|
com.collabtable.app.notifications.NotificationHelper
|
||||||
|
.showListContentUpdated(context, listId, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,16 +258,20 @@ class ListDetailViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFieldAlignment(fieldId: String, alignment: String) {
|
fun updateFieldAlignment(
|
||||||
|
fieldId: String,
|
||||||
|
alignment: String,
|
||||||
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val field = database.fieldDao().getFieldById(fieldId)
|
val field = database.fieldDao().getFieldById(fieldId)
|
||||||
if (field != null) {
|
if (field != null) {
|
||||||
val ts = System.currentTimeMillis()
|
val ts = System.currentTimeMillis()
|
||||||
val normalized = when (alignment.lowercase()) {
|
val normalized =
|
||||||
"center" -> "center"
|
when (alignment.lowercase()) {
|
||||||
"end", "right" -> "end"
|
"center" -> "center"
|
||||||
else -> "start"
|
"end", "right" -> "end"
|
||||||
}
|
else -> "start"
|
||||||
|
}
|
||||||
database.withTransaction {
|
database.withTransaction {
|
||||||
database.fieldDao().updateField(
|
database.fieldDao().updateField(
|
||||||
field.copy(
|
field.copy(
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ package com.collabtable.app.ui.screens
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -50,10 +53,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import android.os.Build
|
|
||||||
import com.collabtable.app.R
|
import com.collabtable.app.R
|
||||||
import com.collabtable.app.data.database.CollabTableDatabase
|
import com.collabtable.app.data.database.CollabTableDatabase
|
||||||
import com.collabtable.app.data.model.CollabList
|
import com.collabtable.app.data.model.CollabList
|
||||||
@@ -117,7 +117,10 @@ fun ListsScreen(
|
|||||||
prefsLocal.setNotifyListAddedEnabled(granted)
|
prefsLocal.setNotifyListAddedEnabled(granted)
|
||||||
prefsLocal.setNotifyListEditedEnabled(granted)
|
prefsLocal.setNotifyListEditedEnabled(granted)
|
||||||
prefsLocal.setNotifyListRemovedEnabled(granted)
|
prefsLocal.setNotifyListRemovedEnabled(granted)
|
||||||
try { prefsLocal.setNotifyListContentUpdatedEnabled(granted) } catch (_: Throwable) {}
|
try {
|
||||||
|
prefsLocal.setNotifyListContentUpdatedEnabled(granted)
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionStatusAction(
|
ConnectionStatusAction(
|
||||||
|
|||||||
+11
-4
@@ -1,8 +1,8 @@
|
|||||||
package com.collabtable.app.ui.screens
|
package com.collabtable.app.ui.screens
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -46,8 +46,8 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.collabtable.app.data.preferences.PreferencesManager
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.collabtable.app.data.preferences.PreferencesManager
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -64,6 +64,7 @@ fun ServerSetupScreen(onSetupComplete: () -> Unit) {
|
|||||||
val validationError by viewModel.validationError.collectAsState()
|
val validationError by viewModel.validationError.collectAsState()
|
||||||
|
|
||||||
var completed by remember { mutableStateOf(false) }
|
var completed by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
fun finishOnce() {
|
fun finishOnce() {
|
||||||
if (!completed) {
|
if (!completed) {
|
||||||
completed = true
|
completed = true
|
||||||
@@ -99,7 +100,10 @@ fun ServerSetupScreen(onSetupComplete: () -> Unit) {
|
|||||||
preferencesManager.setNotifyListAddedEnabled(true)
|
preferencesManager.setNotifyListAddedEnabled(true)
|
||||||
preferencesManager.setNotifyListEditedEnabled(true)
|
preferencesManager.setNotifyListEditedEnabled(true)
|
||||||
preferencesManager.setNotifyListRemovedEnabled(true)
|
preferencesManager.setNotifyListRemovedEnabled(true)
|
||||||
try { preferencesManager.setNotifyListContentUpdatedEnabled(true) } catch (_: Throwable) {}
|
try {
|
||||||
|
preferencesManager.setNotifyListContentUpdatedEnabled(true)
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
finishOnce()
|
finishOnce()
|
||||||
} else {
|
} else {
|
||||||
// Request permission; callback will complete
|
// Request permission; callback will complete
|
||||||
@@ -109,7 +113,10 @@ fun ServerSetupScreen(onSetupComplete: () -> Unit) {
|
|||||||
preferencesManager.setNotifyListAddedEnabled(true)
|
preferencesManager.setNotifyListAddedEnabled(true)
|
||||||
preferencesManager.setNotifyListEditedEnabled(true)
|
preferencesManager.setNotifyListEditedEnabled(true)
|
||||||
preferencesManager.setNotifyListRemovedEnabled(true)
|
preferencesManager.setNotifyListRemovedEnabled(true)
|
||||||
try { preferencesManager.setNotifyListContentUpdatedEnabled(true) } catch (_: Throwable) {}
|
try {
|
||||||
|
preferencesManager.setNotifyListContentUpdatedEnabled(true)
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
finishOnce()
|
finishOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ type Param = any;
|
|||||||
|
|
||||||
type ExecResult = { changes: number };
|
type ExecResult = { changes: number };
|
||||||
|
|
||||||
type TxClient = {
|
|
||||||
query: (text: string, params?: any[]) => Promise<QueryResult<any>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface DBAdapter {
|
interface DBAdapter {
|
||||||
queryAll(sql: string, params?: Param[]): Promise<any[]>;
|
queryAll(sql: string, params?: Param[]): Promise<any[]>;
|
||||||
@@ -66,7 +63,6 @@ class SqliteAdapter implements DBAdapter {
|
|||||||
fieldType TEXT NOT NULL,
|
fieldType TEXT NOT NULL,
|
||||||
fieldOptions TEXT,
|
fieldOptions TEXT,
|
||||||
alignment TEXT NOT NULL DEFAULT 'start',
|
alignment TEXT NOT NULL DEFAULT 'start',
|
||||||
alignment TEXT NOT NULL DEFAULT 'start',
|
|
||||||
listId TEXT NOT NULL,
|
listId TEXT NOT NULL,
|
||||||
"order" INTEGER NOT NULL,
|
"order" INTEGER NOT NULL,
|
||||||
createdAt INTEGER NOT NULL,
|
createdAt INTEGER NOT NULL,
|
||||||
@@ -222,8 +218,7 @@ class PostgresAdapter implements DBAdapter {
|
|||||||
private async runQuery(sql: string, params: Param[] = [], client?: PoolClient): Promise<QueryResult<any>> {
|
private async runQuery(sql: string, params: Param[] = [], client?: PoolClient): Promise<QueryResult<any>> {
|
||||||
// Replace backticks and convert ? to $1..$n
|
// Replace backticks and convert ? to $1..$n
|
||||||
const text = convertQMarksToPg(replaceQuotedIdentifiers(sql));
|
const text = convertQMarksToPg(replaceQuotedIdentifiers(sql));
|
||||||
const runner = client || this.pool;
|
const runner: Pool | PoolClient = client || this.pool;
|
||||||
// @ts-ignore - Pool and PoolClient share query signature
|
|
||||||
return runner.query(text, params);
|
return runner.query(text, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user