feat: Refactor DAO insert methods to use Upsert and add transaction support for field reordering
This commit is contained in:
@@ -12,10 +12,10 @@ interface FieldDao {
|
|||||||
@Query("SELECT * FROM fields WHERE id = :fieldId")
|
@Query("SELECT * FROM fields WHERE id = :fieldId")
|
||||||
suspend fun getFieldById(fieldId: String): Field?
|
suspend fun getFieldById(fieldId: String): Field?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Upsert
|
||||||
suspend fun insertField(field: Field)
|
suspend fun insertField(field: Field)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Upsert
|
||||||
suspend fun insertFields(fields: List<Field>)
|
suspend fun insertFields(fields: List<Field>)
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
@@ -30,4 +30,17 @@ interface FieldDao {
|
|||||||
// Get all fields updated since timestamp, including deleted ones for sync
|
// Get all fields updated since timestamp, including deleted ones for sync
|
||||||
@Query("SELECT * FROM fields WHERE updatedAt >= :since")
|
@Query("SELECT * FROM fields WHERE updatedAt >= :since")
|
||||||
suspend fun getFieldsUpdatedSince(since: Long): List<Field>
|
suspend fun getFieldsUpdatedSince(since: Long): List<Field>
|
||||||
|
|
||||||
|
// Reorder fields in a single transaction for clarity and consistency
|
||||||
|
@Transaction
|
||||||
|
suspend fun reorderFieldsInTransaction(reorderedFields: List<Field>, timestamp: Long) {
|
||||||
|
reorderedFields.forEachIndexed { index, field ->
|
||||||
|
updateField(
|
||||||
|
field.copy(
|
||||||
|
order = index,
|
||||||
|
updatedAt = timestamp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ interface ItemDao {
|
|||||||
@Query("SELECT * FROM items WHERE id = :itemId")
|
@Query("SELECT * FROM items WHERE id = :itemId")
|
||||||
suspend fun getItemById(itemId: String): Item?
|
suspend fun getItemById(itemId: String): Item?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Upsert
|
||||||
suspend fun insertItem(item: Item)
|
suspend fun insertItem(item: Item)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Upsert
|
||||||
suspend fun insertItems(items: List<Item>)
|
suspend fun insertItems(items: List<Item>)
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ interface ItemValueDao {
|
|||||||
@Query("SELECT * FROM item_values WHERE id = :valueId")
|
@Query("SELECT * FROM item_values WHERE id = :valueId")
|
||||||
suspend fun getValueById(valueId: String): ItemValue?
|
suspend fun getValueById(valueId: String): ItemValue?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Upsert
|
||||||
suspend fun insertValue(value: ItemValue)
|
suspend fun insertValue(value: ItemValue)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Upsert
|
||||||
suspend fun insertValues(values: List<ItemValue>)
|
suspend fun insertValues(values: List<ItemValue>)
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ interface ListDao {
|
|||||||
@Query("SELECT * FROM lists WHERE id = :listId")
|
@Query("SELECT * FROM lists WHERE id = :listId")
|
||||||
suspend fun getListById(listId: String): CollabList?
|
suspend fun getListById(listId: String): CollabList?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Upsert
|
||||||
suspend fun insertList(list: CollabList)
|
suspend fun insertList(list: CollabList)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Upsert
|
||||||
suspend fun insertLists(lists: List<CollabList>)
|
suspend fun insertLists(lists: List<CollabList>)
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
|
|||||||
+38
-19
@@ -66,6 +66,10 @@ fun ListDetailScreen(
|
|||||||
val fields by viewModel.fields.collectAsState()
|
val fields by viewModel.fields.collectAsState()
|
||||||
val items by viewModel.items.collectAsState()
|
val items by viewModel.items.collectAsState()
|
||||||
|
|
||||||
|
// Use derivedStateOf to create stable references
|
||||||
|
val stableFields by remember { derivedStateOf { fields } }
|
||||||
|
val stableItems by remember { derivedStateOf { items } }
|
||||||
|
|
||||||
var showManageColumnsDialog by remember { mutableStateOf(false) }
|
var showManageColumnsDialog by remember { mutableStateOf(false) }
|
||||||
var showAddItemDialog by remember { mutableStateOf(false) }
|
var showAddItemDialog by remember { mutableStateOf(false) }
|
||||||
var itemToEdit by remember { mutableStateOf<ItemWithValues?>(null) }
|
var itemToEdit by remember { mutableStateOf<ItemWithValues?>(null) }
|
||||||
@@ -88,8 +92,8 @@ fun ListDetailScreen(
|
|||||||
val fieldWidths = remember { mutableStateMapOf<String, Dp>() }
|
val fieldWidths = remember { mutableStateMapOf<String, Dp>() }
|
||||||
|
|
||||||
// Initialize field widths
|
// Initialize field widths
|
||||||
LaunchedEffect(fields) {
|
LaunchedEffect(stableFields) {
|
||||||
fields.forEach { field ->
|
stableFields.forEach { field ->
|
||||||
if (!fieldWidths.containsKey(field.id)) {
|
if (!fieldWidths.containsKey(field.id)) {
|
||||||
fieldWidths[field.id] = 150.dp
|
fieldWidths[field.id] = 150.dp
|
||||||
}
|
}
|
||||||
@@ -97,8 +101,8 @@ fun ListDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply filtering, sorting, and grouping
|
// Apply filtering, sorting, and grouping
|
||||||
val processedItems = remember(items, filterField, filterValue, sortField, sortAscending, groupByField) {
|
val processedItems = remember(stableItems, stableFields, filterField, filterValue, sortField, sortAscending, groupByField) {
|
||||||
var result = items
|
var result = stableItems
|
||||||
|
|
||||||
// Apply filter
|
// Apply filter
|
||||||
if (filterField != null && filterValue.isNotBlank()) {
|
if (filterField != null && filterValue.isNotBlank()) {
|
||||||
@@ -174,7 +178,7 @@ fun ListDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
if (fields.isEmpty()) {
|
if (stableFields.isEmpty()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -329,7 +333,8 @@ fun ListDetailScreen(
|
|||||||
.horizontalScroll(horizontalScrollState),
|
.horizontalScroll(horizontalScrollState),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
fields.forEach { field ->
|
stableFields.forEach { field ->
|
||||||
|
key(field.id) {
|
||||||
FieldHeader(
|
FieldHeader(
|
||||||
field = field,
|
field = field,
|
||||||
width = fieldWidths[field.id] ?: 150.dp,
|
width = fieldWidths[field.id] ?: 150.dp,
|
||||||
@@ -341,9 +346,10 @@ fun ListDetailScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Items list with synchronized scrolling
|
// Items list with synchronized scrolling
|
||||||
if (items.isEmpty()) {
|
if (stableItems.isEmpty()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
@@ -378,7 +384,7 @@ fun ListDetailScreen(
|
|||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
groupedItems.forEach { (groupName, groupItems) ->
|
groupedItems.entries.forEach { (groupName, groupItems) ->
|
||||||
// Show group header if grouping is enabled
|
// Show group header if grouping is enabled
|
||||||
if (groupByField != null) {
|
if (groupByField != null) {
|
||||||
item(key = "group_$groupName") {
|
item(key = "group_$groupName") {
|
||||||
@@ -397,9 +403,12 @@ fun ListDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show items in the group
|
// Show items in the group
|
||||||
items(groupItems, key = { it.item.id }) { itemWithValues ->
|
items(
|
||||||
|
items = groupItems,
|
||||||
|
key = { it.item.id }
|
||||||
|
) { itemWithValues ->
|
||||||
ItemRow(
|
ItemRow(
|
||||||
fields = fields,
|
fields = stableFields,
|
||||||
fieldWidths = fieldWidths,
|
fieldWidths = fieldWidths,
|
||||||
itemWithValues = itemWithValues,
|
itemWithValues = itemWithValues,
|
||||||
scrollState = horizontalScrollState,
|
scrollState = horizontalScrollState,
|
||||||
@@ -415,7 +424,7 @@ fun ListDetailScreen(
|
|||||||
|
|
||||||
if (showManageColumnsDialog) {
|
if (showManageColumnsDialog) {
|
||||||
ManageColumnsDialog(
|
ManageColumnsDialog(
|
||||||
fields = fields,
|
fields = stableFields,
|
||||||
onDismiss = { showManageColumnsDialog = false },
|
onDismiss = { showManageColumnsDialog = false },
|
||||||
onAddField = { name, fieldType, fieldOptions ->
|
onAddField = { name, fieldType, fieldOptions ->
|
||||||
viewModel.addField(name, fieldType, fieldOptions)
|
viewModel.addField(name, fieldType, fieldOptions)
|
||||||
@@ -434,7 +443,7 @@ fun ListDetailScreen(
|
|||||||
|
|
||||||
if (showAddItemDialog) {
|
if (showAddItemDialog) {
|
||||||
AddItemDialog(
|
AddItemDialog(
|
||||||
fields = fields,
|
fields = stableFields,
|
||||||
onDismiss = { showAddItemDialog = false },
|
onDismiss = { showAddItemDialog = false },
|
||||||
onAdd = { fieldValues ->
|
onAdd = { fieldValues ->
|
||||||
viewModel.addItemWithValues(fieldValues)
|
viewModel.addItemWithValues(fieldValues)
|
||||||
@@ -446,7 +455,7 @@ fun ListDetailScreen(
|
|||||||
// Sort Dialog
|
// Sort Dialog
|
||||||
if (showSortDialog) {
|
if (showSortDialog) {
|
||||||
SortDialog(
|
SortDialog(
|
||||||
fields = fields,
|
fields = stableFields,
|
||||||
currentSortField = sortField,
|
currentSortField = sortField,
|
||||||
currentSortAscending = sortAscending,
|
currentSortAscending = sortAscending,
|
||||||
onDismiss = { showSortDialog = false },
|
onDismiss = { showSortDialog = false },
|
||||||
@@ -461,7 +470,7 @@ fun ListDetailScreen(
|
|||||||
// Group Dialog
|
// Group Dialog
|
||||||
if (showGroupDialog) {
|
if (showGroupDialog) {
|
||||||
GroupDialog(
|
GroupDialog(
|
||||||
fields = fields,
|
fields = stableFields,
|
||||||
currentGroupByField = groupByField,
|
currentGroupByField = groupByField,
|
||||||
onDismiss = { showGroupDialog = false },
|
onDismiss = { showGroupDialog = false },
|
||||||
onApply = { newGroupByField ->
|
onApply = { newGroupByField ->
|
||||||
@@ -474,7 +483,7 @@ fun ListDetailScreen(
|
|||||||
// Filter Dialog
|
// Filter Dialog
|
||||||
if (showFilterDialog) {
|
if (showFilterDialog) {
|
||||||
FilterDialog(
|
FilterDialog(
|
||||||
fields = fields,
|
fields = stableFields,
|
||||||
currentFilterField = filterField,
|
currentFilterField = filterField,
|
||||||
currentFilterValue = filterValue,
|
currentFilterValue = filterValue,
|
||||||
onDismiss = { showFilterDialog = false },
|
onDismiss = { showFilterDialog = false },
|
||||||
@@ -502,7 +511,7 @@ fun ListDetailScreen(
|
|||||||
|
|
||||||
itemToEdit?.let { itemWithValues ->
|
itemToEdit?.let { itemWithValues ->
|
||||||
EditItemDialog(
|
EditItemDialog(
|
||||||
fields = fields,
|
fields = stableFields,
|
||||||
itemWithValues = itemWithValues,
|
itemWithValues = itemWithValues,
|
||||||
onDismiss = { itemToEdit = null },
|
onDismiss = { itemToEdit = null },
|
||||||
onUpdate = { fieldValues ->
|
onUpdate = { fieldValues ->
|
||||||
@@ -586,6 +595,10 @@ fun ItemRow(
|
|||||||
scrollState: androidx.compose.foundation.ScrollState,
|
scrollState: androidx.compose.foundation.ScrollState,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
// Map values by fieldId to ensure correct value-column alignment regardless of original order
|
||||||
|
val valuesByFieldId = remember(itemWithValues.values) {
|
||||||
|
itemWithValues.values.associateBy { it.fieldId }
|
||||||
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -593,8 +606,9 @@ fun ItemRow(
|
|||||||
.horizontalScroll(scrollState),
|
.horizontalScroll(scrollState),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
fields.forEachIndexed { _, field ->
|
fields.forEach { field ->
|
||||||
val value = itemWithValues.values.find { it.fieldId == field.id }
|
key(field.id) {
|
||||||
|
val value = valuesByFieldId[field.id]
|
||||||
val fieldWidth = fieldWidths[field.id] ?: 150.dp
|
val fieldWidth = fieldWidths[field.id] ?: 150.dp
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -1002,6 +1016,7 @@ fun ItemRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -2596,6 +2611,10 @@ fun EditItemDialog(
|
|||||||
) {
|
) {
|
||||||
val fieldValues = remember { mutableStateMapOf<String, String>() }
|
val fieldValues = remember { mutableStateMapOf<String, String>() }
|
||||||
var showDeleteConfirmation by remember { mutableStateOf(false) }
|
var showDeleteConfirmation by remember { mutableStateOf(false) }
|
||||||
|
// Map values by field to avoid linear lookups and ensure consistent mapping when fields reorder
|
||||||
|
val valuesByFieldId = remember(itemWithValues.values) {
|
||||||
|
itemWithValues.values.associateBy { it.fieldId }
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize field values from existing item
|
// Initialize field values from existing item
|
||||||
LaunchedEffect(itemWithValues) {
|
LaunchedEffect(itemWithValues) {
|
||||||
@@ -2647,7 +2666,7 @@ fun EditItemDialog(
|
|||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
items(fields, key = { it.id }) { field ->
|
items(fields, key = { it.id }) { field ->
|
||||||
val itemValue = itemWithValues.values.find { it.fieldId == field.id }
|
val itemValue = valuesByFieldId[field.id]
|
||||||
if (itemValue != null) {
|
if (itemValue != null) {
|
||||||
FieldInput(
|
FieldInput(
|
||||||
field = field,
|
field = field,
|
||||||
|
|||||||
+28
-14
@@ -3,12 +3,14 @@ package com.collabtable.app.ui.screens
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.room.withTransaction
|
||||||
import com.collabtable.app.data.database.CollabTableDatabase
|
import com.collabtable.app.data.database.CollabTableDatabase
|
||||||
import com.collabtable.app.data.model.*
|
import com.collabtable.app.data.model.*
|
||||||
import com.collabtable.app.data.repository.SyncRepository
|
import com.collabtable.app.data.repository.SyncRepository
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -35,7 +37,9 @@ class ListDetailViewModel(
|
|||||||
|
|
||||||
private fun loadListData() {
|
private fun loadListData() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
database.listDao().getListWithFields(listId).collect { listWithFields ->
|
database.listDao().getListWithFields(listId)
|
||||||
|
.debounce(75)
|
||||||
|
.collect { listWithFields ->
|
||||||
_list.value = listWithFields?.list
|
_list.value = listWithFields?.list
|
||||||
_fields.value = listWithFields?.fields ?: emptyList()
|
_fields.value = listWithFields?.fields ?: emptyList()
|
||||||
}
|
}
|
||||||
@@ -90,12 +94,11 @@ class ListDetailViewModel(
|
|||||||
createdAt = timestamp,
|
createdAt = timestamp,
|
||||||
updatedAt = timestamp
|
updatedAt = timestamp
|
||||||
)
|
)
|
||||||
database.fieldDao().insertField(newField)
|
|
||||||
|
|
||||||
// Create empty ItemValue entries for this new field for all existing items
|
// Create empty ItemValue entries for this new field for all existing items
|
||||||
val existingItems = _items.value
|
val existingItems = _items.value
|
||||||
if (existingItems.isNotEmpty()) {
|
val newValues = if (existingItems.isNotEmpty()) {
|
||||||
val newValues = existingItems.map { itemWithValues ->
|
existingItems.map { itemWithValues ->
|
||||||
ItemValue(
|
ItemValue(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
itemId = itemWithValues.item.id,
|
itemId = itemWithValues.item.id,
|
||||||
@@ -104,8 +107,17 @@ class ListDetailViewModel(
|
|||||||
updatedAt = timestamp
|
updatedAt = timestamp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert atomically to avoid intermediate inconsistent states
|
||||||
|
database.withTransaction {
|
||||||
|
database.fieldDao().insertField(newField)
|
||||||
|
if (newValues.isNotEmpty()) {
|
||||||
database.itemValueDao().insertValues(newValues)
|
database.itemValueDao().insertValues(newValues)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
performSync()
|
performSync()
|
||||||
}
|
}
|
||||||
@@ -143,8 +155,9 @@ class ListDetailViewModel(
|
|||||||
createdAt = timestamp,
|
createdAt = timestamp,
|
||||||
updatedAt = timestamp
|
updatedAt = timestamp
|
||||||
)
|
)
|
||||||
|
// Insert item and its values atomically
|
||||||
|
database.withTransaction {
|
||||||
database.itemDao().insertItem(newItem)
|
database.itemDao().insertItem(newItem)
|
||||||
|
|
||||||
// Create empty values for each field
|
// Create empty values for each field
|
||||||
val values = _fields.value.map { field ->
|
val values = _fields.value.map { field ->
|
||||||
ItemValue(
|
ItemValue(
|
||||||
@@ -155,7 +168,10 @@ class ListDetailViewModel(
|
|||||||
updatedAt = timestamp
|
updatedAt = timestamp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (values.isNotEmpty()) {
|
||||||
database.itemValueDao().insertValues(values)
|
database.itemValueDao().insertValues(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
performSync()
|
performSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,8 +185,9 @@ class ListDetailViewModel(
|
|||||||
createdAt = timestamp,
|
createdAt = timestamp,
|
||||||
updatedAt = timestamp
|
updatedAt = timestamp
|
||||||
)
|
)
|
||||||
|
// Insert item and provided values atomically
|
||||||
|
database.withTransaction {
|
||||||
database.itemDao().insertItem(newItem)
|
database.itemDao().insertItem(newItem)
|
||||||
|
|
||||||
// Create values for each field with the provided values
|
// Create values for each field with the provided values
|
||||||
val values = _fields.value.map { field ->
|
val values = _fields.value.map { field ->
|
||||||
ItemValue(
|
ItemValue(
|
||||||
@@ -181,7 +198,10 @@ class ListDetailViewModel(
|
|||||||
updatedAt = timestamp
|
updatedAt = timestamp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (values.isNotEmpty()) {
|
||||||
database.itemValueDao().insertValues(values)
|
database.itemValueDao().insertValues(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
performSync()
|
performSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,14 +231,8 @@ class ListDetailViewModel(
|
|||||||
fun reorderFields(reorderedFields: List<Field>) {
|
fun reorderFields(reorderedFields: List<Field>) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val timestamp = System.currentTimeMillis()
|
val timestamp = System.currentTimeMillis()
|
||||||
reorderedFields.forEachIndexed { index, field ->
|
// Use DAO-level transaction for clarity
|
||||||
database.fieldDao().updateField(
|
database.fieldDao().reorderFieldsInTransaction(reorderedFields, timestamp)
|
||||||
field.copy(
|
|
||||||
order = index,
|
|
||||||
updatedAt = timestamp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
performSync()
|
performSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user