feat: enhance data access objects with additional Room annotations and improve database migration logic

This commit is contained in:
2025-10-25 20:19:51 +02:00
parent 56be50646b
commit aecee4420d
18 changed files with 387 additions and 123 deletions
@@ -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
@@ -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,
@@ -132,17 +132,17 @@ class PreferencesManager(context: Context) {
private const val KEY_THEME_MODE = "theme_mode" // system|light|dark
private const val KEY_DYNAMIC_COLOR = "dynamic_color"
private const val KEY_AMOLED_DARK = "amoled_dark"
private const val KEY_SORT_ORDER = "sort_order"
private const val KEY_SORT_ORDER = "sort_order"
private const val DEFAULT_SERVER_URL = "http://10.0.2.2:3000/api/"
const val THEME_MODE_SYSTEM = "system"
const val THEME_MODE_LIGHT = "light"
const val THEME_MODE_DARK = "dark"
// Sort orders
const val SORT_UPDATED_DESC = "updated_desc" // Newest first (default)
const val SORT_UPDATED_ASC = "updated_asc" // Oldest first
const val SORT_NAME_ASC = "name_asc" // A-Z
const val SORT_NAME_DESC = "name_desc" // Z-A
// Sort orders
const val SORT_UPDATED_DESC = "updated_desc" // Newest first (default)
const val SORT_UPDATED_ASC = "updated_asc" // Oldest first
const val SORT_NAME_ASC = "name_asc" // A-Z
const val SORT_NAME_DESC = "name_desc" // Z-A
@Volatile
private var instance: PreferencesManager? = null
@@ -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]
if (localOrder != null) incoming.copy(orderIndex = localOrder) else incoming
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)
}
database.listDao().insertLists(listsPreservingOrder)
// Build set of existing list ids to guard child inserts
val existingListIds = database.listDao().getAllListIds().toSet()
@@ -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,25 +2525,35 @@ fun ManageColumnsDialog(
items = reorderedFields,
key = { _, field -> field.id },
) { index, field ->
ColumnItem(
field = field,
onEdit = { fieldToEdit = field },
onDelete = { fieldToDelete = field },
onMoveUp = {
if (index > 0) {
val item = reorderedFields.removeAt(index)
reorderedFields.add(index - 1, item)
}
},
onMoveDown = {
if (index < reorderedFields.size - 1) {
val item = reorderedFields.removeAt(index)
reorderedFields.add(index + 1, item)
}
},
canMoveUp = index > 0,
canMoveDown = index < reorderedFields.size - 1,
)
ReorderableItem(reorderState, key = field.id) { _ ->
ColumnItem(
field = field,
onEdit = { fieldToEdit = field },
onDelete = { fieldToDelete = field },
onMoveUp = {
if (index > 0) {
val item = reorderedFields.removeAt(index)
reorderedFields.add(index - 1, item)
}
},
onMoveDown = {
if (index < reorderedFields.size - 1) {
val item = reorderedFields.removeAt(index)
reorderedFields.add(index + 1, item)
}
},
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,39 +2663,44 @@ fun ColumnItem(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
// Move up/down buttons
Column {
IconButton(
onClick = onMoveUp,
enabled = canMoveUp,
modifier = Modifier.size(32.dp),
) {
Icon(
Icons.Default.KeyboardArrowUp,
contentDescription = "Move up",
tint =
if (canMoveUp) {
MaterialTheme.colorScheme.onSurfaceVariant
} else {
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
},
)
// Drag handle and optional move buttons
Column(horizontalAlignment = Alignment.CenterHorizontally) {
if (dragHandle != null) {
dragHandle()
}
IconButton(
onClick = onMoveDown,
enabled = canMoveDown,
modifier = Modifier.size(32.dp),
) {
Icon(
Icons.Default.KeyboardArrowDown,
contentDescription = "Move down",
tint =
if (canMoveDown) {
MaterialTheme.colorScheme.onSurfaceVariant
} else {
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
},
)
Row {
IconButton(
onClick = onMoveUp,
enabled = canMoveUp,
modifier = Modifier.size(32.dp),
) {
Icon(
Icons.Default.KeyboardArrowUp,
contentDescription = "Move up",
tint =
if (canMoveUp) {
MaterialTheme.colorScheme.onSurfaceVariant
} else {
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
},
)
}
IconButton(
onClick = onMoveDown,
enabled = canMoveDown,
modifier = Modifier.size(32.dp),
) {
Icon(
Icons.Default.KeyboardArrowDown,
contentDescription = "Move down",
tint =
if (canMoveDown) {
MaterialTheme.colorScheme.onSurfaceVariant
} else {
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
},
)
}
}
}
@@ -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,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
@@ -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
.fillMaxSize()
.reorderable(reorderState),
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,9 +284,10 @@ fun ListItem(
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier
.weight(1f)
.clickable(onClick = onListClick),
modifier =
Modifier
.weight(1f)
.clickable(onClick = onListClick),
) {
Text(text = list.name, style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(2.dp))
@@ -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) {
@@ -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
@@ -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
@@ -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,