diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt index 7d7d88c85947a26a4868eff9ea324dfb6295e77d..02a2317e6753bbe1823a24a51e1371036df570fa 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt @@ -16,7 +16,6 @@ import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart import net.novagamestudios.kaffeekasse.model.kaffeekasse.isNotEmpty import net.novagamestudios.kaffeekasse.model.session.Session import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider -import net.novagamestudios.kaffeekasse.ui.AppModuleSelection import net.novagamestudios.kaffeekasse.ui.kaffeekasse.ManualBillScreenModel.Companion.backNavigationHandler import net.novagamestudios.kaffeekasse.ui.navigation.AppSubpageTitle import net.novagamestudios.kaffeekasse.ui.navigation.KaffeekasseNavigation @@ -34,7 +33,6 @@ class KaffeekasseModuleScreenModel private constructor( else -> null } - companion object : ScreenModelFactory<KaffeekasseNavigation.Tab, KaffeekasseModuleScreenModel> { context (RepositoryProvider) override fun create(screen: KaffeekasseNavigation.Tab) = KaffeekasseModuleScreenModel( @@ -52,12 +50,8 @@ fun KaffeekasseTopBarTitle( model: KaffeekasseModuleScreenModel, navigator: Navigator ) { - when (navigator.lastItem) { - is KaffeekasseNavigation.ManualBillScreen -> { - val name = model.accountName - if (name != null) AppSubpageTitle("$name") - else AppModuleSelection() - } + when (val screen = navigator.lastItem) { + is KaffeekasseNavigation.ManualBillScreen -> ManualBillTopBarTitle(screen.model, navigator) is KaffeekasseNavigation.AccountScreen -> AppSubpageTitle("Konto") is KaffeekasseNavigation.TransactionsScreen -> AppSubpageTitle("Übersicht") } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/ManualBill.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/ManualBill.kt index b4acc8670a0414091e09021a6fa30e5fee105c81..1da6dceb34ba87c3b49c0bb17adc98c81629a30b 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/ManualBill.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/ManualBill.kt @@ -14,6 +14,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Receipt +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -42,19 +43,23 @@ import net.novagamestudios.common_utils.Logger import net.novagamestudios.common_utils.compose.components.CircularProgressIndicator import net.novagamestudios.common_utils.compose.tabIndicatorOffset import net.novagamestudios.kaffeekasse.KaffeekasseModule.Companion.kaffeekasseCartProvider +import net.novagamestudios.kaffeekasse.model.kaffeekasse.Item import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty import net.novagamestudios.kaffeekasse.model.session.Session import net.novagamestudios.kaffeekasse.model.session.deviceOrNull import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository +import net.novagamestudios.kaffeekasse.ui.AppModuleSelection import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CategorizedItems import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CategorizedItemsState import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.Checkout import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CheckoutState import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CustomItems import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CustomItemsState +import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.ItemSearchField import net.novagamestudios.kaffeekasse.ui.login.LogoutTopBarAction +import net.novagamestudios.kaffeekasse.ui.navigation.AppSubpageTitle import net.novagamestudios.kaffeekasse.ui.navigation.KaffeekasseNavigation import net.novagamestudios.kaffeekasse.ui.util.FailureRetryScreen import net.novagamestudios.kaffeekasse.ui.util.RichDataContent @@ -70,13 +75,43 @@ class ManualBillScreenModel private constructor( private val kaffeekasse: KaffeekasseRepository, val cart: MutableCart ) : ScreenModel, Logger { + val accountName = when (session) { + is Session.WithDevice -> session.realUser.displayName ?: "Unknown User" + else -> null + } + + private var _showSearch by mutableStateOf(false) + var showSearch + get() = _showSearch + set(value) { + _showSearch = value + if (!value) searchQuery = "" + } + var searchQuery by mutableStateOf("") val stock = kaffeekasse.stock.collectAsRichStateHere() val itemGroups get() = stock.dataOrNull?.itemGroups ?: emptyList() + fun Sequence<Item>.filtered(): Sequence<Item> { + val query = searchQuery.takeIf { showSearch }?.takeUnless { it.isBlank() } + return if (query != null) filter { item -> + item.cleanFullName.contains(query, ignoreCase = true) + } else this + } + + val searchItems by derivedStateOf { + if (showSearch) CategorizedItemsState( + itemGroups + .asSequence() + .flatMap { it.items } + .filtered() + .toList() + ) else null + } + val categorizedItemsByGroup by derivedStateOf { - itemGroups.map { CategorizedItemsState(it.items) } + itemGroups.map { group -> CategorizedItemsState(group.items.asSequence().filtered().toList()) } } val customItemsState by lazy { @@ -127,10 +162,25 @@ class ManualBillScreenModel private constructor( @Composable fun ManualBillScreenModel.backNavigationHandler(navigator: Navigator): BackNavigationHandler { - val categorizedItems = categorizedItemsByGroup.getOrNull(currentGroupIndex) + val categorizedItems = if (currentPagerIndex == 0) { + searchItems + } else { + categorizedItemsByGroup.getOrNull(currentGroupIndex) + } return remember(categorizedItems, cart) { - categorizedItems?.backNavigation then CartBackNavigationHandler(cart) + categorizedItems?.backNavigation then SearchBackNavigationHandler() then CartBackNavigationHandler(cart) + } + } + } + + private inner class SearchBackNavigationHandler : BackNavigationHandler { + override fun canNavigateBack() = showSearch + override fun onNavigateBack(): Boolean { + if (showSearch) { + showSearch = false + return true } + return false } } @@ -242,27 +292,75 @@ private fun TabbedItemGroups( model.pagerState, beyondViewportPageCount = 4 ) { index -> - if (index == 0) CustomItems( - state = model.customItemsState, - cart = model.cart, - Modifier - .weight(1f) - .fillMaxHeight() - ) else CategorizedItems( - state = model.categorizedItemsByGroup[index - 1], - cart = model.cart, - Modifier - .weight(1f) - .fillMaxHeight() + if (index == 0) CustomItemsTab( + model = model, + Modifier.fillMaxHeight() + ) else ItemGroupTab( + model = model, + groupIndex = index - 1, + Modifier.fillMaxHeight() ) } } +@Composable +private fun CustomItemsTab( + model: ManualBillScreenModel, + modifier: Modifier = Modifier +) { + val state = model.searchItems + if (state != null) { + CategorizedItems( + state = state, + cart = model.cart, + modifier + ) + } else CustomItems( + state = model.customItemsState, + cart = model.cart, + modifier + ) +} + +@Composable +private fun ItemGroupTab( + model: ManualBillScreenModel, + groupIndex: Int, + modifier: Modifier = Modifier +) { + CategorizedItems( + state = model.categorizedItemsByGroup[groupIndex], + cart = model.cart, + modifier + ) +} + + +@Composable +fun ManualBillTopBarTitle( + model: ManualBillScreenModel, + navigator: Navigator +) { + if (model.showSearch) { + ItemSearchField( + query = model.searchQuery, + onQueryChange = { model.searchQuery = it }, + ) + } else { + val name = model.accountName + if (name != null) AppSubpageTitle("$name") + else AppModuleSelection() + } +} + @Composable fun ManualBillTopBarActions( model: ManualBillScreenModel ) { + if (!model.showSearch) IconButton(onClick = { model.showSearch = !model.showSearch }) { + Icon(Icons.Default.Search, "Suchen") + } val navigator = LocalNavigator.currentOrThrow IconButton(onClick = { navigator.push(KaffeekasseNavigation.AccountScreen(model.session)) diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt index 0820ff42793eeef8326f525a4f11cc3945b44602..887bd96743920b65e757905d5a149ca577b63052 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt @@ -77,7 +77,6 @@ import net.novagamestudios.kaffeekasse.util.richdata.RichDataState import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichStateIn import net.novagamestudios.kaffeekasse.util.richdata.combineRich import net.novagamestudios.kaffeekasse.util.richdata.dataOrNull -import net.novagamestudios.kaffeekasse.util.richdata.flatMapLatestRich import net.novagamestudios.kaffeekasse.util.richdata.mapRich import net.novagamestudios.kaffeekasse.util.richdata.stateIn import kotlin.time.Duration.Companion.seconds @@ -229,9 +228,9 @@ private class APIPurchaseController( }.stateIn(coroutineScope, SharingStarted.Eagerly) private val selfId get() = selfUserBasic.value.dataOrNull?.id - private val selfUserExtended = selfUserBasic.flatMapLatestRich { - kaffeekasse.getExtendedUserInfo(it.id) - } +// private val selfUserExtended = selfUserBasic.flatMapLatestRich { +// kaffeekasse.getExtendedUserInfo(it.id) +// } // override val purchaseAccounts: RichDataFlow<List<PurchaseAccount>> = combineRich( // selfUserExtended, diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/ItemSearch.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/ItemSearch.kt new file mode 100644 index 0000000000000000000000000000000000000000..5f769bcaa359ca8d5932126a67079b75160d6022 --- /dev/null +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/ItemSearch.kt @@ -0,0 +1,42 @@ +package net.novagamestudios.kaffeekasse.ui.kaffeekasse.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester + + +@Composable +fun ItemSearchField( + query: String, + onQueryChange: (String) -> Unit, + modifier: Modifier = Modifier +) { + val focusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + SearchBarDefaults.InputField( + query = query, + onQueryChange = onQueryChange, + onSearch = { }, + expanded = false, + onExpandedChange = { }, + modifier = modifier.focusRequester(focusRequester), + placeholder = { Text("Suchen") }, + trailingIcon = { + if (query.isNotEmpty()) IconButton(onClick = { onQueryChange("") }) { + Icon(Icons.Default.Clear, "Clear") + } + }, + ) +} +