Refactor: Überarbeitung der Einkaufslistendetailansicht
- Checkboxen entfernt und durch Antippen der Artikel zum Abhaken ersetzt. - Drag-and-Drop zum Neuanordnen von Artikeln implementiert. - Buttons zum Verschieben von Artikeln nach oben/unten hinzugefügt, mit Langdruck zum Verschieben an den Anfang/Ende. - Menü zum Umbenennen und Festlegen der Menge für ausgewählte Artikel hinzugefügt. - Build-Fehler und Lint-Warnungen behoben.
This commit is contained in:
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
|
||||
@@ -31,9 +32,12 @@ import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.LooksOne
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -60,6 +64,7 @@ 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.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -171,6 +176,38 @@ fun AppShell(
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back), tint = Color.White)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { scope.launch { selectedItem?.let { shoppingListsViewModel.moveItemUp(selectedListId ?: 0, it) } } }, enabled = selectedItem != null,
|
||||
modifier = Modifier.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
scope.launch { selectedItem?.let { shoppingListsViewModel.moveItemToTop(selectedListId ?: 0, it) } }
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Default.ArrowUpward, contentDescription = stringResource(R.string.move_item_up), tint = Color.White)
|
||||
}
|
||||
IconButton(onClick = { scope.launch { selectedItem?.let { shoppingListsViewModel.moveItemDown(selectedListId ?: 0, it) } } }, enabled = selectedItem != null,
|
||||
modifier = Modifier.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
scope.launch { selectedItem?.let { shoppingListsViewModel.moveItemToBottom(selectedListId ?: 0, it) } }
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Default.ArrowDownward, contentDescription = stringResource(R.string.move_item_down), tint = Color.White)
|
||||
}
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = { showMenu = !showMenu }, enabled = selectedItem != null) {
|
||||
Icon(Icons.Default.MoreVert, contentDescription = stringResource(R.string.more_options), tint = Color.White)
|
||||
}
|
||||
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.rename)) }, onClick = { shoppingListsViewModel.onShowRenameDialog(true); showMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.change_quantity)) }, onClick = { shoppingListsViewModel.onShowQuantityDialog(true); showMenu = false })
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||
)
|
||||
|
||||
@@ -13,10 +13,10 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
@@ -30,13 +30,19 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -113,52 +119,64 @@ fun ShoppingListDetailScreen(
|
||||
if (list.items.isEmpty()) {
|
||||
Text(text = stringResource(R.string.no_items_in_list))
|
||||
} else {
|
||||
val allItems = list.items.sortedBy { it.displayOrder }
|
||||
val filteredItems = if (showCompletedItems) {
|
||||
list.items
|
||||
allItems
|
||||
} else {
|
||||
list.items.filter { !it.isChecked }
|
||||
}.sortedBy { it.displayOrder }
|
||||
allItems.filter { !it.isChecked }
|
||||
}
|
||||
|
||||
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 ->
|
||||
val reorderedItems = filteredItems.toMutableList().apply {
|
||||
add(to.index, removeAt(from.index))
|
||||
}
|
||||
viewModel.moveItem(listId ?: 0, reorderedItems)
|
||||
})
|
||||
LazyColumn(
|
||||
state = state.listState,
|
||||
modifier = Modifier
|
||||
.reorderable(state)
|
||||
.detectReorderAfterLongPress(state)
|
||||
) {
|
||||
items(filteredItems, { it.id }) { item ->
|
||||
val isSelected = item == selectedItem
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(if (isSelected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
viewModel.onSelectItem(item)
|
||||
}
|
||||
ReorderableItem(state, key = item.id) { isDragging ->
|
||||
val elevation = if (isDragging) 8.dp else 0.dp
|
||||
val isSelected = item == selectedItem
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.shadow(elevation)
|
||||
.background(if (isSelected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onTap = {
|
||||
coroutineScope.launch {
|
||||
viewModel.saveShoppingListItem(item.copy(isChecked = !item.isChecked))
|
||||
}
|
||||
},
|
||||
onLongPress = {
|
||||
viewModel.onSelectItem(item)
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = if (item.quantity != null) "${item.name} (${item.quantity})" else item.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textDecoration = if (item.isChecked) TextDecoration.LineThrough else null,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,45 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun moveItemUp(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(index - 1, itemToMove)
|
||||
val updatedItems = currentItems.mapIndexed { idx, item ->
|
||||
item.copy(displayOrder = idx)
|
||||
}
|
||||
noteshopRepository.updateShoppingListItems(updatedItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun moveItemDown(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(index + 1, itemToMove)
|
||||
val updatedItems = currentItems.mapIndexed { idx, item ->
|
||||
item.copy(displayOrder = idx)
|
||||
}
|
||||
noteshopRepository.updateShoppingListItems(updatedItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun moveItem(listId: Int, items: List<ShoppingListItem>) {
|
||||
viewModelScope.launch {
|
||||
val updatedItems = items.mapIndexed { index, item ->
|
||||
item.copy(displayOrder = index)
|
||||
}
|
||||
noteshopRepository.updateShoppingListItems(updatedItems)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private const val TIMEOUT_MILLIS = 5_000L
|
||||
|
||||
@@ -50,4 +50,9 @@
|
||||
<string name="back">Zurück</string>
|
||||
<string name="move_to_top">Nach oben</string>
|
||||
<string name="move_to_bottom">Nach unten</string>
|
||||
<string name="more_options">Mehr Optionen</string>
|
||||
<string name="move_item_up">Element nach oben verschieben</string>
|
||||
<string name="move_item_down">Element nach unten verschieben</string>
|
||||
<string name="rename_item">Element umbenennen</string>
|
||||
<string name="set_quantity">Menge festlegen</string>
|
||||
</resources>
|
||||
@@ -50,4 +50,9 @@
|
||||
<string name="back">Back</string>
|
||||
<string name="move_to_top">Move to Top</string>
|
||||
<string name="move_to_bottom">Move to Bottom</string>
|
||||
<string name="more_options">More options</string>
|
||||
<string name="move_item_up">Move item up</string>
|
||||
<string name="move_item_down">Move item down</string>
|
||||
<string name="rename_item">Rename item</string>
|
||||
<string name="set_quantity">Set quantity</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user