feat: add persistent column width management and horizontal scrolling enhancements in ListDetailScreen
This commit is contained in:
+34
@@ -2,6 +2,7 @@ package com.collabtable.app.data.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import org.json.JSONObject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -140,6 +141,38 @@ class PreferencesManager(context: Context) {
|
||||
_syncPollIntervalMs.value = clamped
|
||||
}
|
||||
|
||||
// Persist per-list column widths (fieldId -> widthDp)
|
||||
// Stored as a JSON object string in SharedPreferences under key: COLUMN_WIDTHS_PREFIX + listId
|
||||
fun getColumnWidths(listId: String): Map<String, Float> {
|
||||
val key = COLUMN_WIDTHS_PREFIX + listId
|
||||
val raw = prefs.getString(key, null) ?: return emptyMap()
|
||||
return try {
|
||||
val json = JSONObject(raw)
|
||||
val map = mutableMapOf<String, Float>()
|
||||
val it = json.keys()
|
||||
while (it.hasNext()) {
|
||||
val fieldId = it.next()
|
||||
val width = json.optDouble(fieldId, Double.NaN)
|
||||
if (!width.isNaN()) {
|
||||
map[fieldId] = width.toFloat()
|
||||
}
|
||||
}
|
||||
map
|
||||
} catch (e: Exception) {
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
fun setColumnWidths(listId: String, widthsDp: Map<String, Float>) {
|
||||
val key = COLUMN_WIDTHS_PREFIX + listId
|
||||
val json = JSONObject()
|
||||
widthsDp.forEach { (fieldId, width) ->
|
||||
// Persist as double for precision, value is in dp units
|
||||
json.put(fieldId, width.toDouble())
|
||||
}
|
||||
prefs.edit().putString(key, json.toString()).apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_SERVER_URL = "server_url"
|
||||
private const val KEY_FIRST_RUN = "first_run"
|
||||
@@ -150,6 +183,7 @@ class PreferencesManager(context: Context) {
|
||||
private const val KEY_AMOLED_DARK = "amoled_dark"
|
||||
private const val KEY_SORT_ORDER = "sort_order"
|
||||
private const val KEY_SYNC_POLL_INTERVAL_MS = "sync_poll_interval_ms"
|
||||
private const val COLUMN_WIDTHS_PREFIX = "column_widths_" // + listId
|
||||
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 MIN_SYNC_POLL_INTERVAL_MS = 250L
|
||||
|
||||
+35
-4
@@ -103,6 +103,7 @@ fun ListDetailScreen(
|
||||
val context = LocalContext.current
|
||||
val database = remember { CollabTableDatabase.getDatabase(context) }
|
||||
val viewModel = remember { ListDetailViewModel(database, listId, context) }
|
||||
val prefs = remember { PreferencesManager.getInstance(context) }
|
||||
|
||||
val list by viewModel.list.collectAsState()
|
||||
val fields by viewModel.fields.collectAsState()
|
||||
@@ -129,16 +130,19 @@ fun ListDetailScreen(
|
||||
|
||||
// Shared scroll state for synchronized horizontal scrolling
|
||||
val horizontalScrollState = rememberScrollState()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// Field widths state (resizable columns)
|
||||
val fieldWidths = remember { mutableStateMapOf<String, Dp>() }
|
||||
|
||||
// Initialize field widths
|
||||
// Initialize field widths (load persisted per-list widths, fallback to default)
|
||||
LaunchedEffect(stableFields) {
|
||||
// Load saved widths for this listId
|
||||
val saved = prefs.getColumnWidths(listId)
|
||||
stableFields.forEach { field ->
|
||||
if (!fieldWidths.containsKey(field.id)) {
|
||||
fieldWidths[field.id] = 150.dp
|
||||
}
|
||||
val savedWidth = saved[field.id]?.coerceAtLeast(100f)
|
||||
val widthDp = (savedWidth ?: 150f).dp
|
||||
fieldWidths[field.id] = widthDp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,6 +415,11 @@ fun ListDetailScreen(
|
||||
// Enforce only a minimum width; remove the previous max (400dp)
|
||||
val newWidth = (currentWidth.value + delta).coerceAtLeast(100f)
|
||||
fieldWidths[field.id] = newWidth.dp
|
||||
// Persist updated widths for this listId
|
||||
prefs.setColumnWidths(
|
||||
listId,
|
||||
fieldWidths.mapValues { it.value.value },
|
||||
)
|
||||
},
|
||||
scrollState = horizontalScrollState,
|
||||
isLast = (field.id == stableFields.lastOrNull()?.id),
|
||||
@@ -487,6 +496,28 @@ fun ListDetailScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Filler area that occupies remaining viewport height and enables horizontal dragging
|
||||
// to scroll the table even when tapping below the last row on small tables.
|
||||
item(key = "hscroll_filler") {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillParentMaxHeight()
|
||||
.pointerInput(horizontalScrollState) {
|
||||
detectDragGestures { change, dragAmount ->
|
||||
change.consume()
|
||||
val max = horizontalScrollState.maxValue.toFloat()
|
||||
val current = horizontalScrollState.value.toFloat()
|
||||
val target = (current - dragAmount.x).coerceIn(0f, max)
|
||||
// Drag right (positive x) should reveal right side (increase scroll)
|
||||
// We subtract dragAmount.x because scrolling value increases when content moves left.
|
||||
scope.launch { horizontalScrollState.scrollTo(target.toInt()) }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user