feat: Implement item selection mode with contextual actions and fix build errors
This commit is contained in:
@@ -183,7 +183,11 @@ fun AppShell(
|
||||
is Screen.ShoppingListDetail -> { // New case for detail screen
|
||||
ShoppingListDetailScreen(
|
||||
listId = selectedListId,
|
||||
viewModel = shoppingListsViewModel
|
||||
viewModel = shoppingListsViewModel,
|
||||
navigateBack = {
|
||||
currentScreen = Screen.ShoppingLists
|
||||
selectedListId = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
package de.lxtools.noteshop.ui.shopping
|
||||
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.heightIn
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.LooksOne
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
@@ -29,12 +33,15 @@ import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
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.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -44,661 +51,386 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.lxtools.noteshop.R
|
||||
import de.lxtools.noteshop.data.ShoppingListItem
|
||||
import kotlinx.coroutines.launch
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
|
||||
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
|
||||
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
||||
fun ShoppingListDetailScreen(
|
||||
|
||||
listId: Int?,
|
||||
|
||||
viewModel: ShoppingListsViewModel,
|
||||
|
||||
modifier: Modifier = Modifier
|
||||
|
||||
modifier: Modifier = Modifier,
|
||||
navigateBack: () -> Unit
|
||||
) {
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var newItemName by remember { mutableStateOf("") }
|
||||
|
||||
val showCompletedItems by viewModel.showCompletedItems.collectAsState()
|
||||
|
||||
|
||||
|
||||
val shoppingListWithItems by viewModel.getShoppingListWithItemsStream(listId ?: 0)
|
||||
|
||||
.collectAsState(initial = null)
|
||||
|
||||
|
||||
var selectedItem by remember { mutableStateOf<ShoppingListItem?>(null) }
|
||||
var showRenameDialog by remember { mutableStateOf(false) }
|
||||
var showQuantityDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
|
||||
modifier = modifier.fillMaxSize(),
|
||||
|
||||
topBar = {
|
||||
if (selectedItem != null) {
|
||||
TopAppBar(
|
||||
title = { Text(text = selectedItem!!.name) },
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { selectedItem = null }) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
coroutineScope.launch {
|
||||
listId?.let { selectedItem?.let { item -> viewModel.moveItemToTop(it, item) } }
|
||||
selectedItem = null // Exit selection mode after move
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Filled.ArrowUpward, contentDescription = stringResource(R.string.move_to_top))
|
||||
}
|
||||
IconButton(onClick = {
|
||||
coroutineScope.launch {
|
||||
listId?.let { selectedItem?.let { item -> viewModel.moveItemToBottom(it, item) } }
|
||||
selectedItem = null // Exit selection mode after move
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Filled.ArrowDownward, contentDescription = stringResource(R.string.move_to_bottom))
|
||||
}
|
||||
IconButton(onClick = { showRenameDialog = true }) {
|
||||
Icon(Icons.Filled.Edit, contentDescription = stringResource(R.string.rename))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
TopAppBar(
|
||||
title = { Text(text = shoppingListWithItems?.shoppingList?.name ?: stringResource(R.string.menu_shopping_list_detail)) },
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = navigateBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
|
||||
if (shoppingListWithItems != null) {
|
||||
|
||||
val list = shoppingListWithItems!!
|
||||
|
||||
BottomAppBar(
|
||||
|
||||
modifier = Modifier.imePadding()
|
||||
|
||||
) {
|
||||
|
||||
Row(
|
||||
|
||||
modifier = Modifier
|
||||
|
||||
.fillMaxWidth(),
|
||||
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
|
||||
) {
|
||||
|
||||
IconButton( // Default Add Button
|
||||
|
||||
onClick = {
|
||||
|
||||
if (newItemName.isNotBlank()) {
|
||||
|
||||
coroutineScope.launch {
|
||||
|
||||
val existingItemNames = list.items.map { it.name.trim().lowercase() }
|
||||
|
||||
val itemsToAdd = if (newItemName.contains(',')) {
|
||||
|
||||
newItemName.split(',').map { it.trim() }.filter { it.isNotBlank() && !existingItemNames.contains(it.lowercase()) }
|
||||
|
||||
} else {
|
||||
|
||||
if (!existingItemNames.contains(newItemName.trim().lowercase())) {
|
||||
|
||||
listOf(newItemName.trim())
|
||||
|
||||
} else {
|
||||
|
||||
emptyList()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
itemsToAdd.forEach { name ->
|
||||
|
||||
viewModel.saveShoppingListItem(
|
||||
|
||||
ShoppingListItem(
|
||||
|
||||
name = name,
|
||||
|
||||
listId = list.shoppingList.id
|
||||
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
newItemName = "" // Clear input field
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
enabled = newItemName.isNotBlank()
|
||||
|
||||
) {
|
||||
|
||||
Icon(
|
||||
|
||||
Icons.AutoMirrored.Filled.PlaylistAdd,
|
||||
|
||||
contentDescription = stringResource(R.string.add_item_icon_desc)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
IconButton( // Simple Mode Add Button
|
||||
|
||||
onClick = {
|
||||
|
||||
if (newItemName.isNotBlank()) {
|
||||
|
||||
coroutineScope.launch {
|
||||
|
||||
val existingItemNames = list.items.map { it.name.trim().lowercase() }
|
||||
|
||||
if (!existingItemNames.contains(newItemName.trim().lowercase())) {
|
||||
|
||||
viewModel.saveShoppingListItem(
|
||||
|
||||
ShoppingListItem(
|
||||
|
||||
name = newItemName.trim(),
|
||||
|
||||
listId = list.shoppingList.id
|
||||
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
newItemName = "" // Clear input field
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
enabled = newItemName.isNotBlank()
|
||||
|
||||
) {
|
||||
|
||||
Icon(
|
||||
|
||||
Icons.Filled.LooksOne,
|
||||
|
||||
contentDescription = stringResource(R.string.add_item_simple_icon_desc)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
IconButton(onClick = { viewModel.toggleShowCompletedItems() }) {
|
||||
|
||||
Icon(
|
||||
|
||||
imageVector = if (showCompletedItems) Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
|
||||
|
||||
contentDescription = if (showCompletedItems) stringResource(R.string.hide_completed_icon_desc) else stringResource(
|
||||
|
||||
R.string.show_completed_icon_desc
|
||||
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
IconButton(
|
||||
|
||||
onClick = {
|
||||
|
||||
coroutineScope.launch {
|
||||
|
||||
listId?.let { viewModel.deleteCompletedItems(it) }
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
enabled = list.items.any { it.isChecked }
|
||||
|
||||
) {
|
||||
|
||||
Icon(
|
||||
|
||||
Icons.Default.Delete,
|
||||
|
||||
contentDescription = stringResource(R.string.remove_completed_icon_desc)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
) { innerPadding ->
|
||||
|
||||
Column(
|
||||
|
||||
modifier = Modifier
|
||||
|
||||
.fillMaxSize()
|
||||
|
||||
.padding(innerPadding)
|
||||
|
||||
.padding(16.dp)
|
||||
|
||||
) {
|
||||
|
||||
if (shoppingListWithItems == null) {
|
||||
|
||||
Text(
|
||||
|
||||
text = stringResource(R.string.shopping_list_not_found),
|
||||
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
} else {
|
||||
|
||||
val list = shoppingListWithItems!!
|
||||
|
||||
Text(
|
||||
|
||||
text = list.shoppingList.name,
|
||||
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
OutlinedTextField(
|
||||
|
||||
value = newItemName,
|
||||
|
||||
onValueChange = { newItemName = it },
|
||||
|
||||
label = { Text(stringResource(R.string.add_item_hint)) },
|
||||
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
if (newItemName.isNotBlank()) {
|
||||
|
||||
val suggestions = shoppingListWithItems?.items?.filter {
|
||||
|
||||
it.name.contains(newItemName, ignoreCase = true) && it.isChecked
|
||||
|
||||
} ?: emptyList()
|
||||
|
||||
|
||||
|
||||
if (suggestions.isNotEmpty()) {
|
||||
|
||||
LazyColumn(modifier = Modifier.heightIn(max = 150.dp)) {
|
||||
|
||||
items(suggestions) { item ->
|
||||
|
||||
Row(
|
||||
|
||||
modifier = Modifier
|
||||
|
||||
.fillMaxWidth()
|
||||
|
||||
.padding(vertical = 8.dp)
|
||||
|
||||
.clickable {
|
||||
|
||||
coroutineScope.launch {
|
||||
|
||||
viewModel.saveShoppingListItem(item.copy(isChecked = false))
|
||||
|
||||
newItemName = ""
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
|
||||
) {
|
||||
|
||||
Text(
|
||||
|
||||
text = item.name + " (" + stringResource(R.string.completed) + ")",
|
||||
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
|
||||
modifier = Modifier.weight(1f)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
|
||||
|
||||
if (list.items.isEmpty()) {
|
||||
|
||||
Text(text = stringResource(R.string.no_items_in_list))
|
||||
|
||||
} else {
|
||||
|
||||
val filteredItems = if (showCompletedItems) {
|
||||
|
||||
list.items
|
||||
|
||||
} else {
|
||||
|
||||
list.items.filter { !it.isChecked } // Filter out checked items
|
||||
|
||||
}.sortedBy { it.displayOrder }
|
||||
|
||||
|
||||
} else {
|
||||
val filteredItems = if (showCompletedItems) {
|
||||
list.items
|
||||
} else {
|
||||
list.items.filter { !it.isChecked } // Filter out checked items
|
||||
}.sortedBy { it.displayOrder }
|
||||
|
||||
if (filteredItems.isEmpty() && !showCompletedItems) {
|
||||
|
||||
Text(text = stringResource(R.string.no_uncompleted_items))
|
||||
|
||||
} else if (filteredItems.isEmpty() && showCompletedItems) {
|
||||
|
||||
Text(text = stringResource(R.string.no_items_in_list))
|
||||
|
||||
} else {
|
||||
|
||||
val state = rememberReorderableLazyListState(onMove = { from, to ->
|
||||
|
||||
listId?.let { viewModel.moveItem(it, from.index, to.index) }
|
||||
|
||||
})
|
||||
|
||||
LazyColumn(
|
||||
|
||||
state = state.listState,
|
||||
|
||||
modifier = Modifier.reorderable(state)
|
||||
|
||||
modifier = Modifier
|
||||
) {
|
||||
|
||||
items(filteredItems, { it.id }) { item ->
|
||||
|
||||
ReorderableItem(state, key = item.id) {
|
||||
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
var showRenameDialog by remember { mutableStateOf(false) }
|
||||
|
||||
var showQuantityDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
|
||||
if (showRenameDialog) {
|
||||
|
||||
RenameItemDialog(
|
||||
|
||||
item = item,
|
||||
|
||||
onDismiss = { showRenameDialog = false },
|
||||
|
||||
onRename = { newName ->
|
||||
|
||||
coroutineScope.launch {
|
||||
|
||||
viewModel.saveShoppingListItem(item.copy(name = newName))
|
||||
|
||||
}
|
||||
|
||||
showRenameDialog = false
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (showQuantityDialog) {
|
||||
|
||||
QuantityDialog(
|
||||
|
||||
item = item,
|
||||
|
||||
onDismiss = { showQuantityDialog = false },
|
||||
|
||||
onSetQuantity = { quantity ->
|
||||
|
||||
coroutineScope.launch {
|
||||
|
||||
viewModel.saveShoppingListItem(item.copy(quantity = quantity))
|
||||
|
||||
}
|
||||
|
||||
showQuantityDialog = false
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Box(
|
||||
val isSelected = item == selectedItem
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(if (isSelected) Color.LightGray else Color.Transparent)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
showMenu = true
|
||||
}
|
||||
)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.detectReorderAfterLongPress(state),
|
||||
onClick = { /* Drag handle, no direct click action */ }
|
||||
) {
|
||||
Icon(Icons.Default.DragHandle, contentDescription = "Drag handle")
|
||||
}
|
||||
Checkbox(
|
||||
checked = item.isChecked,
|
||||
onCheckedChange = { isChecked ->
|
||||
coroutineScope.launch {
|
||||
viewModel.saveShoppingListItem(item.copy(isChecked = isChecked))
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
selectedItem = item
|
||||
}
|
||||
)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = item.isChecked,
|
||||
onCheckedChange = { isChecked ->
|
||||
coroutineScope.launch {
|
||||
viewModel.saveShoppingListItem(
|
||||
item.copy(
|
||||
isChecked = isChecked
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = if (item.quantity != null) "${item.name} (${item.quantity})" else item.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.rename)) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
showRenameDialog = true
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.change_quantity)) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
showQuantityDialog = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = if (item.quantity != null) "${item.name} (${item.quantity})" else item.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (showRenameDialog && selectedItem != null) {
|
||||
RenameItemDialog(
|
||||
item = selectedItem!!,
|
||||
onDismiss = { showRenameDialog = false },
|
||||
onRename = { newName ->
|
||||
coroutineScope.launch {
|
||||
viewModel.saveShoppingListItem(selectedItem!!.copy(name = newName))
|
||||
}
|
||||
showRenameDialog = false
|
||||
selectedItem = null // Exit selection mode after rename
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showQuantityDialog && selectedItem != null) {
|
||||
QuantityDialog(
|
||||
item = selectedItem!!,
|
||||
onDismiss = { showQuantityDialog = false },
|
||||
onSetQuantity = { quantity ->
|
||||
coroutineScope.launch {
|
||||
viewModel.saveShoppingListItem(selectedItem!!.copy(quantity = quantity))
|
||||
}
|
||||
showQuantityDialog = false
|
||||
selectedItem = null // Exit selection mode after quantity change
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
|
||||
fun RenameItemDialog(item: ShoppingListItem, onDismiss: () -> Unit, onRename: (String) -> Unit) {
|
||||
|
||||
var text by remember { mutableStateOf(item.name) }
|
||||
|
||||
AlertDialog(
|
||||
|
||||
onDismissRequest = onDismiss,
|
||||
|
||||
title = { Text(stringResource(R.string.rename_item_title)) },
|
||||
|
||||
text = {
|
||||
|
||||
OutlinedTextField(
|
||||
|
||||
value = text,
|
||||
|
||||
onValueChange = { text = it },
|
||||
|
||||
label = { Text(stringResource(R.string.new_name)) }
|
||||
|
||||
)
|
||||
|
||||
},
|
||||
|
||||
confirmButton = {
|
||||
|
||||
Button(
|
||||
|
||||
onClick = { onRename(text) }
|
||||
|
||||
) {
|
||||
|
||||
Text(stringResource(R.string.save))
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
dismissButton = {
|
||||
|
||||
Button(onClick = onDismiss) {
|
||||
|
||||
Text(stringResource(R.string.cancel))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
|
||||
fun QuantityDialog(item: ShoppingListItem, onDismiss: () -> Unit, onSetQuantity: (String?) -> Unit) {
|
||||
|
||||
var text by remember { mutableStateOf(item.quantity ?: "") }
|
||||
|
||||
AlertDialog(
|
||||
|
||||
onDismissRequest = onDismiss,
|
||||
|
||||
title = { Text(stringResource(R.string.change_quantity)) },
|
||||
|
||||
text = {
|
||||
|
||||
OutlinedTextField(
|
||||
|
||||
value = text,
|
||||
|
||||
onValueChange = { text = it },
|
||||
|
||||
label = { Text(stringResource(R.string.quantity_hint)) }
|
||||
|
||||
)
|
||||
|
||||
},
|
||||
|
||||
confirmButton = {
|
||||
|
||||
Button(
|
||||
|
||||
onClick = { onSetQuantity(text.ifBlank { null }) }
|
||||
|
||||
) {
|
||||
|
||||
Text(stringResource(R.string.save))
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
dismissButton = {
|
||||
|
||||
Button(onClick = onDismiss) {
|
||||
|
||||
Text(stringResource(R.string.cancel))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -106,25 +106,6 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository)
|
||||
}
|
||||
}
|
||||
|
||||
fun moveItem(listId: Int, from: Int, to: Int) {
|
||||
viewModelScope.launch {
|
||||
val currentListWithItems = noteshopRepository.getShoppingListWithItemsStream(listId).firstOrNull()
|
||||
currentListWithItems?.let {
|
||||
val items = it.items.toMutableList()
|
||||
// Ensure 'from' and 'to' indices are within bounds
|
||||
if (from >= 0 && from < items.size && to >= 0 && to < items.size) {
|
||||
val movedItem = items.removeAt(from)
|
||||
items.add(to, movedItem)
|
||||
|
||||
val updatedItems = items.mapIndexed { index, item ->
|
||||
item.copy(displayOrder = index)
|
||||
}
|
||||
noteshopRepository.updateShoppingListItems(updatedItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteItem(item: ShoppingListItem) {
|
||||
noteshopRepository.deleteShoppingListItem(item)
|
||||
}
|
||||
@@ -166,6 +147,36 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun moveItemToTop(listId: Int, itemToMove: ShoppingListItem) {
|
||||
noteshopRepository.getShoppingListWithItemsStream(listId).firstOrNull()?.let { listWithItems ->
|
||||
val currentItems = listWithItems.items.toMutableList()
|
||||
val index = currentItems.indexOfFirst { it.id == itemToMove.id }
|
||||
if (index > 0) {
|
||||
currentItems.removeAt(index)
|
||||
currentItems.add(0, itemToMove)
|
||||
val updatedItems = currentItems.mapIndexed { idx, item ->
|
||||
item.copy(displayOrder = idx)
|
||||
}
|
||||
noteshopRepository.updateShoppingListItems(updatedItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun moveItemToBottom(listId: Int, itemToMove: ShoppingListItem) {
|
||||
noteshopRepository.getShoppingListWithItemsStream(listId).firstOrNull()?.let { listWithItems ->
|
||||
val currentItems = listWithItems.items.toMutableList()
|
||||
val index = currentItems.indexOfFirst { it.id == itemToMove.id }
|
||||
if (index != -1 && index < currentItems.size - 1) {
|
||||
currentItems.removeAt(index)
|
||||
currentItems.add(currentItems.size, itemToMove)
|
||||
val updatedItems = currentItems.mapIndexed { idx, item ->
|
||||
item.copy(displayOrder = idx)
|
||||
}
|
||||
noteshopRepository.updateShoppingListItems(updatedItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TIMEOUT_MILLIS = 5_000L
|
||||
}
|
||||
|
||||
@@ -47,4 +47,7 @@
|
||||
<string name="rename_item_title">Rename Item</string>
|
||||
<string name="new_name">New Name</string>
|
||||
<string name="quantity_hint">Quantity (e.g., 1kg, 2 pieces)</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="move_to_top">Move to Top</string>
|
||||
<string name="move_to_bottom">Move to Bottom</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user