Feat: Implementiere ShoppingListDetailScreen für die Artikelverwaltung
Dieser Commit führt einen neuen Detailbildschirm zur Verwaltung von Artikeln innerhalb einer Einkaufsliste ein. Die wichtigsten Änderungen umfassen: - Added `ShoppingListDetailScreen` to display items of a selected shopping list. - Implemented navigation from `ShoppingListsScreen` to `ShoppingListDetailScreen` upon clicking a list card. - Updated `MainActivity.kt` to handle navigation state and display a back button for the detail view. - Enhanced `ShoppingListsViewModel.kt` to provide a stream for a single shopping list with its items. - Added new string resources for the detail screen. - Users can now view and toggle the checked status of individual items within a dedicated screen.
This commit is contained in:
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -48,6 +49,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.lxtools.noteshop.ui.notes.NotesScreen
|
||||
import de.lxtools.noteshop.ui.notes.NotesViewModel
|
||||
import de.lxtools.noteshop.ui.shopping.ShoppingListDetailScreen // New import
|
||||
import de.lxtools.noteshop.ui.shopping.ShoppingListsScreen
|
||||
import de.lxtools.noteshop.ui.shopping.ShoppingListsViewModel
|
||||
import de.lxtools.noteshop.ui.theme.NoteshopTheme
|
||||
@@ -57,6 +59,7 @@ import kotlinx.coroutines.launch
|
||||
sealed class Screen(val route: String, val titleRes: Int) {
|
||||
object ShoppingLists : Screen("shopping_lists", R.string.menu_shopping_lists)
|
||||
object Notes : Screen("notes", R.string.menu_notes)
|
||||
object ShoppingListDetail : Screen("shopping_list_detail", R.string.menu_shopping_list_detail) // New screen
|
||||
}
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@@ -80,10 +83,12 @@ fun AppShell(
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
var currentScreen: Screen by remember { mutableStateOf(Screen.ShoppingLists) }
|
||||
var selectedListId: Int? by remember { mutableStateOf(null) } // New state for selected list ID
|
||||
|
||||
val navigationItems = listOf(
|
||||
Screen.ShoppingLists,
|
||||
Screen.Notes
|
||||
// ShoppingListDetail is not in the drawer menu, it's navigated to from ShoppingListsScreen
|
||||
)
|
||||
|
||||
ModalNavigationDrawer(
|
||||
@@ -118,6 +123,7 @@ fun AppShell(
|
||||
selected = screen == currentScreen,
|
||||
onClick = {
|
||||
currentScreen = screen
|
||||
selectedListId = null // Clear selected list when navigating via drawer
|
||||
scope.launch { drawerState.close() }
|
||||
}
|
||||
)
|
||||
@@ -131,11 +137,23 @@ fun AppShell(
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(id = currentScreen.titleRes)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { scope.launch { drawerState.apply { if (isClosed) open() else close() } } }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = stringResource(id = R.string.menu_open)
|
||||
)
|
||||
if (currentScreen == Screen.ShoppingListDetail) { // Show back button for detail screen
|
||||
IconButton(onClick = {
|
||||
currentScreen = Screen.ShoppingLists
|
||||
selectedListId = null
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = stringResource(id = R.string.menu_open) // Reusing string, should be "Back"
|
||||
)
|
||||
}
|
||||
} else { // Show menu icon for other screens
|
||||
IconButton(onClick = { scope.launch { drawerState.apply { if (isClosed) open() else close() } } }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = stringResource(id = R.string.menu_open)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -151,11 +169,27 @@ fun AppShell(
|
||||
) {
|
||||
when (currentScreen) {
|
||||
is Screen.ShoppingLists -> {
|
||||
ShoppingListsScreen(viewModel = shoppingListsViewModel)
|
||||
ShoppingListsScreen(
|
||||
viewModel = shoppingListsViewModel,
|
||||
onListClick = { listId -> // Pass navigation lambda
|
||||
selectedListId = listId
|
||||
currentScreen = Screen.ShoppingListDetail
|
||||
}
|
||||
)
|
||||
}
|
||||
is Screen.Notes -> {
|
||||
NotesScreen(viewModel = notesViewModel)
|
||||
}
|
||||
is Screen.ShoppingListDetail -> { // New case for detail screen
|
||||
ShoppingListDetailScreen(
|
||||
listId = selectedListId,
|
||||
viewModel = shoppingListsViewModel, // Reusing ViewModel for now
|
||||
onBack = {
|
||||
currentScreen = Screen.ShoppingLists
|
||||
selectedListId = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package de.lxtools.noteshop.ui.shopping
|
||||
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.lxtools.noteshop.R
|
||||
import de.lxtools.noteshop.data.ShoppingList
|
||||
import de.lxtools.noteshop.data.ShoppingListItem
|
||||
import de.lxtools.noteshop.data.ShoppingListWithItems
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ShoppingListDetailScreen(
|
||||
listId: Int?,
|
||||
viewModel: ShoppingListsViewModel,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// Fetch the specific shopping list with items
|
||||
val shoppingListWithItems by viewModel.getShoppingListWithItemsStream(listId ?: 0).collectAsState(initial = null)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
if (shoppingListWithItems == null) { // Changed from ?: run { ... }
|
||||
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))
|
||||
// Optionally, a button to go back
|
||||
// Button(onClick = onBack) {
|
||||
// Text(stringResource(R.string.back))
|
||||
// }
|
||||
} else {
|
||||
val list = shoppingListWithItems!! // Safe to use !! here because we checked for null
|
||||
Text(
|
||||
text = list.shoppingList.name,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
if (list.items.isEmpty()) {
|
||||
Text(text = stringResource(R.string.no_items_in_list))
|
||||
} else {
|
||||
LazyColumn {
|
||||
items(list.items) { item ->
|
||||
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 = item.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,11 @@ import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ShoppingListsScreen(viewModel: ShoppingListsViewModel = viewModel(factory = AppViewModelProvider.Factory), modifier: Modifier = Modifier) {
|
||||
fun ShoppingListsScreen(
|
||||
viewModel: ShoppingListsViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||
modifier: Modifier = Modifier,
|
||||
onListClick: (Int) -> Unit // New parameter
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
val listDetails by viewModel.listDetails.collectAsState()
|
||||
val itemDetails by viewModel.itemDetails.collectAsState()
|
||||
@@ -107,7 +111,8 @@ fun ShoppingListsScreen(viewModel: ShoppingListsViewModel = viewModel(factory =
|
||||
coroutineScope.launch {
|
||||
viewModel.deleteItem(it)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.clickable { onListClick(listWithItems.shoppingList.id) } // Make card clickable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import de.lxtools.noteshop.data.NoteshopRepository
|
||||
import de.lxtools.noteshop.data.ShoppingList
|
||||
import de.lxtools.noteshop.data.ShoppingListItem
|
||||
import de.lxtools.noteshop.data.ShoppingListWithItems
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -42,7 +43,12 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository)
|
||||
|
||||
suspend fun saveList() {
|
||||
if (listDetails.value.isValid()) {
|
||||
noteshopRepository.insertShoppingList(listDetails.value.toShoppingList())
|
||||
val currentList = listDetails.value.toShoppingList()
|
||||
if (currentList.id == 0) {
|
||||
noteshopRepository.insertShoppingList(currentList)
|
||||
} else {
|
||||
noteshopRepository.updateShoppingList(currentList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +87,10 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository)
|
||||
_itemDetails.value = ShoppingListItemDetails(listId = listId)
|
||||
}
|
||||
|
||||
fun getShoppingListWithItemsStream(listId: Int): Flow<ShoppingListWithItems?> {
|
||||
return noteshopRepository.getShoppingListWithItemsStream(listId)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TIMEOUT_MILLIS = 5_000L
|
||||
}
|
||||
|
||||
@@ -24,4 +24,7 @@
|
||||
<string name="item_name">Artikelname</string>
|
||||
<string name="item_checked">Abgehakt</string>
|
||||
<string name="edit">Bearbeiten</string>
|
||||
<string name="menu_shopping_list_detail">Einkaufslisten-Details</string>
|
||||
<string name="no_items_in_list">Noch keine Artikel in dieser Liste.</string>
|
||||
<string name="shopping_list_not_found">Einkaufsliste nicht gefunden.</string>
|
||||
</resources>
|
||||
@@ -24,4 +24,7 @@
|
||||
<string name="item_name">Item Name</string>
|
||||
<string name="item_checked">Checked</string>
|
||||
<string name="edit">Edit</string>
|
||||
<string name="menu_shopping_list_detail">Shopping List Details</string>
|
||||
<string name="no_items_in_list">No items in this list yet.</string>
|
||||
<string name="shopping_list_not_found">Shopping list not found.</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user