This commit is contained in:
2025-10-24 22:14:20 +02:00
parent 66ac428139
commit eede176e5e
10 changed files with 1176 additions and 59 deletions
@@ -6,13 +6,42 @@ import androidx.room.Index
import androidx.room.PrimaryKey
enum class FieldType {
STRING,
PRICE,
// Text types
TEXT,
MULTILINE_TEXT,
// Number types
NUMBER,
CURRENCY,
PERCENTAGE,
// Selection types
DROPDOWN,
AUTOCOMPLETE,
CHECKBOX,
SWITCH,
// Link types
URL,
EMAIL,
PHONE,
// Date/Time types
DATE,
TIME,
DATETIME
DATETIME,
DURATION,
// Media types
IMAGE,
FILE,
BARCODE,
SIGNATURE,
// Other types
RATING,
COLOR,
LOCATION
}
@Entity(
@@ -31,8 +60,8 @@ data class Field(
@PrimaryKey val id: String,
val listId: String,
val name: String,
val fieldType: String = "STRING", // STRING, PRICE, DROPDOWN
val fieldOptions: String = "", // JSON string for dropdown options or currency for price
val fieldType: String = "TEXT",
val fieldOptions: String = "", // JSON string for dropdown options, currency symbol, etc.
val order: Int,
val createdAt: Long,
val updatedAt: Long,
@@ -42,7 +71,14 @@ data class Field(
return try {
FieldType.valueOf(fieldType)
} catch (e: Exception) {
FieldType.STRING
// Handle legacy field types
when (fieldType) {
"STRING" -> FieldType.TEXT
"PRICE" -> FieldType.CURRENCY
"AMOUNT" -> FieldType.NUMBER
"SIZE" -> FieldType.TEXT
else -> FieldType.TEXT
}
}
}
@@ -55,10 +91,26 @@ data class Field(
}
fun getCurrency(): String {
return if (fieldType == "PRICE" && fieldOptions.isNotBlank()) {
return if ((fieldType == "CURRENCY" || fieldType == "PRICE") && fieldOptions.isNotBlank()) {
fieldOptions
} else {
"$"
"CHF"
}
}
fun getMaxRating(): Int {
return if (fieldType == "RATING" && fieldOptions.isNotBlank()) {
fieldOptions.toIntOrNull() ?: 5
} else {
5
}
}
fun getAutocompleteOptions(): List<String> {
return if (fieldType == "AUTOCOMPLETE" && fieldOptions.isNotBlank()) {
fieldOptions.split("|")
} else {
emptyList()
}
}
}
@@ -57,15 +57,29 @@ class SyncRepository(context: Context) {
// Log details of lists being received
syncResponse.lists.forEach { list ->
Logger.d("Sync", " Inserting list: ${list.id} - ${list.name} (updated: ${list.updatedAt})")
Logger.d("Sync", " Received list: ${list.id} - ${list.name} (updated: ${list.updatedAt}, deleted: ${list.isDeleted})")
}
// Apply server changes to local database
Logger.d("Sync", "Inserting ${syncResponse.lists.size} lists into local database")
database.listDao().insertLists(syncResponse.lists)
Logger.d("Sync", "Inserting ${syncResponse.fields.size} fields into local database")
database.fieldDao().insertFields(syncResponse.fields)
Logger.d("Sync", "Inserting ${syncResponse.items.size} items into local database")
database.itemDao().insertItems(syncResponse.items)
Logger.d("Sync", "Inserting ${syncResponse.itemValues.size} item values into local database")
database.itemValueDao().insertValues(syncResponse.itemValues)
// Verify what was actually saved
val savedLists = database.listDao().getListsUpdatedSince(0)
Logger.d("Sync", "After insert, total lists in DB: ${savedLists.size}")
savedLists.forEach { list ->
Logger.d("Sync", " DB list: ${list.id} - ${list.name} (deleted: ${list.isDeleted})")
}
// Update last sync timestamp
setLastSyncTimestamp(syncResponse.serverTimestamp)
@@ -91,6 +91,22 @@ class ListDetailViewModel(
updatedAt = timestamp
)
database.fieldDao().insertField(newField)
// Create empty ItemValue entries for this new field for all existing items
val existingItems = _items.value
if (existingItems.isNotEmpty()) {
val newValues = existingItems.map { itemWithValues ->
ItemValue(
id = UUID.randomUUID().toString(),
itemId = itemWithValues.item.id,
fieldId = newField.id,
value = "",
updatedAt = timestamp
)
}
database.itemValueDao().insertValues(newValues)
}
performSync()
}
}
@@ -7,6 +7,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.List
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings
@@ -39,6 +40,7 @@ fun ListsScreen(
var showCreateDialog by remember { mutableStateOf(false) }
var listToDelete by remember { mutableStateOf<CollabList?>(null) }
var listToEdit by remember { mutableStateOf<CollabList?>(null) }
Scaffold(
topBar = {
@@ -94,6 +96,7 @@ fun ListsScreen(
ListItem(
list = list,
onListClick = { onNavigateToList(list.id) },
onEditClick = { listToEdit = list },
onDeleteClick = { listToDelete = list }
)
}
@@ -111,6 +114,17 @@ fun ListsScreen(
)
}
listToEdit?.let { list ->
RenameListDialog(
currentName = list.name,
onDismiss = { listToEdit = null },
onRename = { newName ->
viewModel.renameList(list.id, newName)
listToEdit = null
}
)
}
listToDelete?.let { list ->
AlertDialog(
onDismissRequest = { listToDelete = null },
@@ -139,6 +153,7 @@ fun ListsScreen(
fun ListItem(
list: CollabList,
onListClick: () -> Unit,
onEditClick: () -> Unit,
onDeleteClick: () -> Unit
) {
Card(
@@ -165,12 +180,21 @@ fun ListItem(
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
IconButton(onClick = onDeleteClick) {
Icon(
Icons.Default.Delete,
contentDescription = stringResource(R.string.delete),
tint = MaterialTheme.colorScheme.error
)
Row {
IconButton(onClick = onEditClick) {
Icon(
Icons.Default.Edit,
contentDescription = "Edit",
tint = MaterialTheme.colorScheme.primary
)
}
IconButton(onClick = onDeleteClick) {
Icon(
Icons.Default.Delete,
contentDescription = stringResource(R.string.delete),
tint = MaterialTheme.colorScheme.error
)
}
}
}
}
@@ -210,6 +234,41 @@ fun CreateListDialog(
)
}
@Composable
private fun RenameListDialog(
currentName: String,
onDismiss: () -> Unit,
onRename: (String) -> Unit
) {
var listName by remember { mutableStateOf(currentName) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Rename List") },
text = {
OutlinedTextField(
value = listName,
onValueChange = { listName = it },
label = { Text(stringResource(R.string.list_name)) },
singleLine = true
)
},
confirmButton = {
TextButton(
onClick = { if (listName.isNotBlank()) onRename(listName.trim()) },
enabled = listName.isNotBlank() && listName.trim() != currentName
) {
Text("Rename")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
}
}
)
}
private fun formatDate(timestamp: Long): String {
val sdf = SimpleDateFormat("MMM dd, yyyy HH:mm", Locale.getDefault())
return sdf.format(Date(timestamp))
@@ -69,6 +69,23 @@ class ListsViewModel(
}
}
fun renameList(listId: String, newName: String) {
viewModelScope.launch {
Logger.i("ListsViewModel", "Renaming list: $listId to $newName")
val list = database.listDao().getListById(listId)
if (list != null && newName.isNotBlank()) {
database.listDao().updateList(
list.copy(
name = newName.trim(),
updatedAt = System.currentTimeMillis()
)
)
// Sync immediately after renaming
performSync()
}
}
}
fun deleteList(listId: String) {
viewModelScope.launch {
Logger.i("ListsViewModel", "Deleting list: $listId")