feat: enhance data access objects with additional Room annotations and improve database migration logic
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*.{kt,kts}]
|
||||
ktlint_code_style = ktlint_official
|
||||
indent_size = 4
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
# KtLint >= 0.49: disable specific rules via fully qualified rule ids
|
||||
ktlint_standard_function-naming = disabled
|
||||
ktlint_standard_max-line-length = enabled
|
||||
max_line_length = 180
|
||||
ij_kotlin_name_count_to_use_star_import = 999
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 999
|
||||
@@ -105,3 +105,8 @@ detekt {
|
||||
buildUponDefaultConfig = true
|
||||
allRules = false
|
||||
}
|
||||
|
||||
// Ensure Detekt uses a supported JVM target
|
||||
tasks.withType(io.gitlab.arturbosch.detekt.Detekt).configureEach {
|
||||
jvmTarget = "21"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package com.collabtable.app.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.room.Upsert
|
||||
import com.collabtable.app.data.model.Field
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package com.collabtable.app.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.room.Upsert
|
||||
import com.collabtable.app.data.model.Item
|
||||
import com.collabtable.app.data.model.ItemWithValues
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.collabtable.app.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import androidx.room.Upsert
|
||||
import com.collabtable.app.data.model.ItemValue
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package com.collabtable.app.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.room.Upsert
|
||||
import com.collabtable.app.data.model.CollabList
|
||||
import com.collabtable.app.data.model.ListWithFields
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
+16
-10
@@ -6,10 +6,16 @@ import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.collabtable.app.data.dao.*
|
||||
import com.collabtable.app.data.model.*
|
||||
import com.collabtable.app.data.dao.FieldDao
|
||||
import com.collabtable.app.data.dao.ItemDao
|
||||
import com.collabtable.app.data.dao.ItemValueDao
|
||||
import com.collabtable.app.data.dao.ListDao
|
||||
import com.collabtable.app.data.model.CollabList
|
||||
import com.collabtable.app.data.model.Field
|
||||
import com.collabtable.app.data.model.Item
|
||||
import com.collabtable.app.data.model.ItemValue
|
||||
|
||||
val MIGRATION_1_2 =
|
||||
val migration1To2 =
|
||||
object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE fields ADD COLUMN fieldType TEXT NOT NULL DEFAULT 'STRING'")
|
||||
@@ -17,7 +23,7 @@ val MIGRATION_1_2 =
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_2_3 =
|
||||
val migration2To3 =
|
||||
object : Migration(2, 3) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
// Local-only column for manual reordering; nullable so existing rows remain unaffected
|
||||
@@ -46,28 +52,28 @@ abstract class CollabTableDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: CollabTableDatabase? = null
|
||||
private var dbInstance: CollabTableDatabase? = null
|
||||
|
||||
fun getDatabase(context: Context): CollabTableDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
return dbInstance ?: synchronized(this) {
|
||||
val instance =
|
||||
Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
CollabTableDatabase::class.java,
|
||||
"collab_table_database",
|
||||
)
|
||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
|
||||
.addMigrations(migration1To2, migration2To3)
|
||||
.build()
|
||||
INSTANCE = instance
|
||||
dbInstance = instance
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
fun clearDatabase(context: Context) {
|
||||
synchronized(this) {
|
||||
INSTANCE?.close()
|
||||
dbInstance?.close()
|
||||
context.deleteDatabase("collab_table_database")
|
||||
INSTANCE = null
|
||||
dbInstance = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ data class Field(
|
||||
val listId: String,
|
||||
val name: String,
|
||||
val fieldType: String = "TEXT",
|
||||
val fieldOptions: String = "", // JSON string for dropdown options, currency symbol, etc.
|
||||
// JSON string for dropdown options, currency symbol, etc.
|
||||
val fieldOptions: String = "",
|
||||
val order: Int,
|
||||
val createdAt: Long,
|
||||
val updatedAt: Long,
|
||||
|
||||
+26
-6
@@ -44,7 +44,9 @@ class SyncRepository(context: Context) {
|
||||
if (localTotal > 0) {
|
||||
Logger.i(
|
||||
"Sync",
|
||||
"[OUT] Sending changes (lists=${localLists.size}, fields=${localFields.size}, items=${localItems.size}, values=${localValues.size})",
|
||||
"[OUT] Sending changes " +
|
||||
"(lists=${localLists.size}, fields=${localFields.size}, " +
|
||||
"items=${localItems.size}, values=${localValues.size})",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -78,23 +80,41 @@ class SyncRepository(context: Context) {
|
||||
response.body()!!
|
||||
}
|
||||
// Only log receive when there are actual server changes
|
||||
val inTotal = syncResponse.lists.size + syncResponse.fields.size + syncResponse.items.size + syncResponse.itemValues.size
|
||||
val inTotal =
|
||||
syncResponse.lists.size +
|
||||
syncResponse.fields.size +
|
||||
syncResponse.items.size +
|
||||
syncResponse.itemValues.size
|
||||
if (inTotal > 0) {
|
||||
Logger.i(
|
||||
"Sync",
|
||||
"[IN] Received changes (lists=${syncResponse.lists.size}, fields=${syncResponse.fields.size}, items=${syncResponse.items.size}, values=${syncResponse.itemValues.size})",
|
||||
"[IN] Received changes " +
|
||||
"(lists=${syncResponse.lists.size}, fields=${syncResponse.fields.size}, " +
|
||||
"items=${syncResponse.items.size}, values=${syncResponse.itemValues.size})",
|
||||
)
|
||||
}
|
||||
|
||||
// Apply server changes to local database atomically in correct order
|
||||
database.withTransaction {
|
||||
// 1) Upsert lists first, preserving local orderIndex when present
|
||||
val idOrderMap = database.listDao().getListIdsAndOrder().associate { it.id to it.orderIndex }
|
||||
val listsPreservingOrder = syncResponse.lists.map { incoming ->
|
||||
val localOrder = idOrderMap[incoming.id]
|
||||
val localIdToOrder = database.listDao().getListIdsAndOrder().associate { it.id to it.orderIndex }
|
||||
// Build a map of local updatedAt to avoid overwriting newer local changes with older server data
|
||||
val localUpdatedMap = database.listDao().getListsUpdatedSince(0).associateBy({ it.id }, { it.updatedAt })
|
||||
|
||||
val listsPreservingOrder =
|
||||
syncResponse.lists
|
||||
.filter { incoming ->
|
||||
val localUpdated = localUpdatedMap[incoming.id]
|
||||
localUpdated == null || incoming.updatedAt >= localUpdated
|
||||
}
|
||||
.map { incoming ->
|
||||
val localOrder = localIdToOrder[incoming.id]
|
||||
if (localOrder != null) incoming.copy(orderIndex = localOrder) else incoming
|
||||
}
|
||||
|
||||
if (listsPreservingOrder.isNotEmpty()) {
|
||||
database.listDao().insertLists(listsPreservingOrder)
|
||||
}
|
||||
|
||||
// Build set of existing list ids to guard child inserts
|
||||
val existingListIds = database.listDao().getAllListIds().toSet()
|
||||
|
||||
+82
-16
@@ -1,12 +1,26 @@
|
||||
package com.collabtable.app.ui.screens
|
||||
@file:Suppress("ktlint:standard:no-wildcard-imports")
|
||||
|
||||
package com.collabtable.app.ui.screens
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
@@ -16,14 +30,34 @@ import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.filled.AccountBox
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.filled.List
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.TimePicker
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.material3.rememberTimePickerState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
@@ -45,8 +79,13 @@ import com.collabtable.app.R
|
||||
import com.collabtable.app.data.database.CollabTableDatabase
|
||||
import com.collabtable.app.data.model.Field
|
||||
import com.collabtable.app.data.model.ItemWithValues
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -2464,12 +2503,21 @@ fun ManageColumnsDialog(
|
||||
|
||||
Divider()
|
||||
|
||||
// Column list
|
||||
// Column list with drag-and-drop reordering
|
||||
val reorderState =
|
||||
rememberReorderableLazyListState(onMove = { from, to ->
|
||||
val item = reorderedFields.removeAt(from.index)
|
||||
val target = if (to.index > reorderedFields.size) reorderedFields.size else to.index
|
||||
reorderedFields.add(target, item)
|
||||
})
|
||||
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.reorderable(reorderState),
|
||||
state = reorderState.listState,
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
@@ -2477,6 +2525,7 @@ fun ManageColumnsDialog(
|
||||
items = reorderedFields,
|
||||
key = { _, field -> field.id },
|
||||
) { index, field ->
|
||||
ReorderableItem(reorderState, key = field.id) { _ ->
|
||||
ColumnItem(
|
||||
field = field,
|
||||
onEdit = { fieldToEdit = field },
|
||||
@@ -2495,7 +2544,16 @@ fun ManageColumnsDialog(
|
||||
},
|
||||
canMoveUp = index > 0,
|
||||
canMoveDown = index < reorderedFields.size - 1,
|
||||
dragHandle = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.DragHandle,
|
||||
contentDescription = "Reorder",
|
||||
modifier = Modifier.detectReorder(reorderState),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2587,6 +2645,7 @@ fun ColumnItem(
|
||||
onMoveDown: () -> Unit,
|
||||
canMoveUp: Boolean,
|
||||
canMoveDown: Boolean,
|
||||
dragHandle: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -2604,8 +2663,12 @@ fun ColumnItem(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// Move up/down buttons
|
||||
Column {
|
||||
// Drag handle and optional move buttons
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
if (dragHandle != null) {
|
||||
dragHandle()
|
||||
}
|
||||
Row {
|
||||
IconButton(
|
||||
onClick = onMoveUp,
|
||||
enabled = canMoveUp,
|
||||
@@ -2639,6 +2702,7 @@ fun ColumnItem(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
@@ -2662,8 +2726,10 @@ fun ColumnItem(
|
||||
// Selection types
|
||||
com.collabtable.app.data.model.FieldType.CHECKBOX -> "Checkbox"
|
||||
com.collabtable.app.data.model.FieldType.SWITCH -> "Switch"
|
||||
com.collabtable.app.data.model.FieldType.DROPDOWN -> "Dropdown (${field.getDropdownOptions().size} options)"
|
||||
com.collabtable.app.data.model.FieldType.AUTOCOMPLETE -> "Autocomplete (${field.getAutocompleteOptions().size} options)"
|
||||
com.collabtable.app.data.model.FieldType.DROPDOWN ->
|
||||
"Dropdown (${field.getDropdownOptions().size} options)"
|
||||
com.collabtable.app.data.model.FieldType.AUTOCOMPLETE ->
|
||||
"Autocomplete (${field.getAutocompleteOptions().size} options)"
|
||||
|
||||
// Link types
|
||||
com.collabtable.app.data.model.FieldType.URL -> "URL"
|
||||
|
||||
+5
-1
@@ -5,7 +5,11 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.room.withTransaction
|
||||
import com.collabtable.app.data.database.CollabTableDatabase
|
||||
import com.collabtable.app.data.model.*
|
||||
import com.collabtable.app.data.model.CollabList
|
||||
import com.collabtable.app.data.model.Field
|
||||
import com.collabtable.app.data.model.Item
|
||||
import com.collabtable.app.data.model.ItemValue
|
||||
import com.collabtable.app.data.model.ItemWithValues
|
||||
import com.collabtable.app.data.repository.SyncRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
+49
-11
@@ -15,8 +15,8 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.List
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
@@ -37,6 +37,7 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -144,23 +145,56 @@ fun ListsScreen(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val reorderState = rememberReorderableLazyListState(onMove = { from, to ->
|
||||
val fromIdx = from.index
|
||||
val toIdx = to.index
|
||||
if (fromIdx != toIdx) {
|
||||
viewModel.reorder(fromIdx, toIdx)
|
||||
// Maintain a working copy for smooth drag animations; commit to DB on drag end
|
||||
var working by remember { mutableStateOf(lists) }
|
||||
var dragging by remember { mutableStateOf(false) }
|
||||
// Keep working list in sync when not dragging
|
||||
LaunchedEffect(lists, dragging) {
|
||||
if (!dragging) working = lists
|
||||
}
|
||||
})
|
||||
|
||||
fun MutableList<CollabList>.move(
|
||||
from: Int,
|
||||
to: Int,
|
||||
) {
|
||||
if (from == to) return
|
||||
val item = removeAt(from)
|
||||
add(
|
||||
if (to > size) {
|
||||
size
|
||||
} else if (to < 0) {
|
||||
0
|
||||
} else {
|
||||
to
|
||||
},
|
||||
item,
|
||||
)
|
||||
}
|
||||
|
||||
val reorderState =
|
||||
rememberReorderableLazyListState(
|
||||
onMove = { from, to ->
|
||||
dragging = true
|
||||
val newList = working.toMutableList()
|
||||
newList.move(from.index, to.index)
|
||||
working = newList
|
||||
},
|
||||
onDragEnd = { _, _ ->
|
||||
dragging = false
|
||||
viewModel.commitReorder(working.map { it.id })
|
||||
},
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.reorderable(reorderState),
|
||||
state = reorderState.listState,
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
itemsIndexed(lists, key = { _, it -> it.id }) { index, list ->
|
||||
itemsIndexed(working, key = { _, it -> it.id }) { index, list ->
|
||||
ReorderableItem(reorderState, key = list.id) { isDragging ->
|
||||
ListItem(
|
||||
list = list,
|
||||
@@ -250,7 +284,8 @@ fun ListItem(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.clickable(onClick = onListClick),
|
||||
) {
|
||||
@@ -365,7 +400,10 @@ private fun SortMenu(prefs: PreferencesManager) {
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
@Composable
|
||||
fun ItemOption(label: String, value: String) {
|
||||
fun ItemOption(
|
||||
label: String,
|
||||
value: String,
|
||||
) {
|
||||
androidx.compose.material3.DropdownMenuItem(
|
||||
text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
|
||||
+11
-1
@@ -65,7 +65,10 @@ class ListsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun reorder(fromIndex: Int, toIndex: Int) {
|
||||
fun reorder(
|
||||
fromIndex: Int,
|
||||
toIndex: Int,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val current = _lists.value.toMutableList()
|
||||
if (fromIndex !in current.indices || toIndex !in current.indices) return@launch
|
||||
@@ -88,6 +91,13 @@ class ListsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun commitReorder(newOrderIds: List<String>) {
|
||||
viewModelScope.launch {
|
||||
val pairs = newOrderIds.mapIndexed { idx, id -> id to idx.toLong() }
|
||||
database.listDao().updateOrderIndexes(pairs)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startPeriodicSync() {
|
||||
while (true) {
|
||||
kotlinx.coroutines.delay(5000) // Wait 5 seconds before next sync
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
package com.collabtable.app.ui.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@@ -12,8 +20,30 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.FilterList
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
+27
-3
@@ -1,14 +1,38 @@
|
||||
package com.collabtable.app.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Error
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
+36
-4
@@ -1,14 +1,44 @@
|
||||
package com.collabtable.app.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.DarkMode
|
||||
import androidx.compose.material.icons.filled.LightMode
|
||||
import androidx.compose.material.icons.filled.SettingsBrightness
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -232,7 +262,9 @@ fun SettingsScreen(
|
||||
title = { Text("Leave Server?") },
|
||||
text = {
|
||||
Text(
|
||||
"All your data will be synced to the server before disconnecting. After leaving, all local data will be deleted and you'll need to set up a new connection. This action cannot be undone.",
|
||||
"All your data will be synced to the server before disconnecting. " +
|
||||
"After leaving, all local data will be deleted and you'll need to set up a new connection. " +
|
||||
"This action cannot be undone.",
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
|
||||
@@ -5,7 +5,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
data class LogEntry(
|
||||
val timestamp: Long,
|
||||
|
||||
Reference in New Issue
Block a user