Skip to content
Snippets Groups Projects
Commit 6bc6cbe1 authored by Jonas Broeckmann's avatar Jonas Broeckmann
Browse files

Added item search

parent 921f1be8
No related branches found
No related tags found
No related merge requests found
...@@ -16,7 +16,6 @@ import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart ...@@ -16,7 +16,6 @@ import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart
import net.novagamestudios.kaffeekasse.model.kaffeekasse.isNotEmpty import net.novagamestudios.kaffeekasse.model.kaffeekasse.isNotEmpty
import net.novagamestudios.kaffeekasse.model.session.Session import net.novagamestudios.kaffeekasse.model.session.Session
import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider 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.kaffeekasse.ManualBillScreenModel.Companion.backNavigationHandler
import net.novagamestudios.kaffeekasse.ui.navigation.AppSubpageTitle import net.novagamestudios.kaffeekasse.ui.navigation.AppSubpageTitle
import net.novagamestudios.kaffeekasse.ui.navigation.KaffeekasseNavigation import net.novagamestudios.kaffeekasse.ui.navigation.KaffeekasseNavigation
...@@ -34,7 +33,6 @@ class KaffeekasseModuleScreenModel private constructor( ...@@ -34,7 +33,6 @@ class KaffeekasseModuleScreenModel private constructor(
else -> null else -> null
} }
companion object : ScreenModelFactory<KaffeekasseNavigation.Tab, KaffeekasseModuleScreenModel> { companion object : ScreenModelFactory<KaffeekasseNavigation.Tab, KaffeekasseModuleScreenModel> {
context (RepositoryProvider) context (RepositoryProvider)
override fun create(screen: KaffeekasseNavigation.Tab) = KaffeekasseModuleScreenModel( override fun create(screen: KaffeekasseNavigation.Tab) = KaffeekasseModuleScreenModel(
...@@ -52,12 +50,8 @@ fun KaffeekasseTopBarTitle( ...@@ -52,12 +50,8 @@ fun KaffeekasseTopBarTitle(
model: KaffeekasseModuleScreenModel, model: KaffeekasseModuleScreenModel,
navigator: Navigator navigator: Navigator
) { ) {
when (navigator.lastItem) { when (val screen = navigator.lastItem) {
is KaffeekasseNavigation.ManualBillScreen -> { is KaffeekasseNavigation.ManualBillScreen -> ManualBillTopBarTitle(screen.model, navigator)
val name = model.accountName
if (name != null) AppSubpageTitle("$name")
else AppModuleSelection()
}
is KaffeekasseNavigation.AccountScreen -> AppSubpageTitle("Konto") is KaffeekasseNavigation.AccountScreen -> AppSubpageTitle("Konto")
is KaffeekasseNavigation.TransactionsScreen -> AppSubpageTitle("Übersicht") is KaffeekasseNavigation.TransactionsScreen -> AppSubpageTitle("Übersicht")
} }
......
...@@ -14,6 +14,7 @@ import androidx.compose.material.icons.Icons ...@@ -14,6 +14,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Receipt import androidx.compose.material.icons.filled.Receipt
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
...@@ -42,19 +43,23 @@ import net.novagamestudios.common_utils.Logger ...@@ -42,19 +43,23 @@ import net.novagamestudios.common_utils.Logger
import net.novagamestudios.common_utils.compose.components.CircularProgressIndicator import net.novagamestudios.common_utils.compose.components.CircularProgressIndicator
import net.novagamestudios.common_utils.compose.tabIndicatorOffset import net.novagamestudios.common_utils.compose.tabIndicatorOffset
import net.novagamestudios.kaffeekasse.KaffeekasseModule.Companion.kaffeekasseCartProvider 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.MutableCart
import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty
import net.novagamestudios.kaffeekasse.model.session.Session import net.novagamestudios.kaffeekasse.model.session.Session
import net.novagamestudios.kaffeekasse.model.session.deviceOrNull import net.novagamestudios.kaffeekasse.model.session.deviceOrNull
import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider
import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository 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.CategorizedItems
import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CategorizedItemsState import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CategorizedItemsState
import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.Checkout import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.Checkout
import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CheckoutState import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CheckoutState
import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CustomItems import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CustomItems
import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.CustomItemsState 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.login.LogoutTopBarAction
import net.novagamestudios.kaffeekasse.ui.navigation.AppSubpageTitle
import net.novagamestudios.kaffeekasse.ui.navigation.KaffeekasseNavigation import net.novagamestudios.kaffeekasse.ui.navigation.KaffeekasseNavigation
import net.novagamestudios.kaffeekasse.ui.util.FailureRetryScreen import net.novagamestudios.kaffeekasse.ui.util.FailureRetryScreen
import net.novagamestudios.kaffeekasse.ui.util.RichDataContent import net.novagamestudios.kaffeekasse.ui.util.RichDataContent
...@@ -70,13 +75,43 @@ class ManualBillScreenModel private constructor( ...@@ -70,13 +75,43 @@ class ManualBillScreenModel private constructor(
private val kaffeekasse: KaffeekasseRepository, private val kaffeekasse: KaffeekasseRepository,
val cart: MutableCart val cart: MutableCart
) : ScreenModel, Logger { ) : 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 stock = kaffeekasse.stock.collectAsRichStateHere()
val itemGroups get() = stock.dataOrNull?.itemGroups ?: emptyList() 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 { val categorizedItemsByGroup by derivedStateOf {
itemGroups.map { CategorizedItemsState(it.items) } itemGroups.map { group -> CategorizedItemsState(group.items.asSequence().filtered().toList()) }
} }
val customItemsState by lazy { val customItemsState by lazy {
...@@ -127,10 +162,25 @@ class ManualBillScreenModel private constructor( ...@@ -127,10 +162,25 @@ class ManualBillScreenModel private constructor(
@Composable @Composable
fun ManualBillScreenModel.backNavigationHandler(navigator: Navigator): BackNavigationHandler { 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) { 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( ...@@ -242,27 +292,75 @@ private fun TabbedItemGroups(
model.pagerState, model.pagerState,
beyondViewportPageCount = 4 beyondViewportPageCount = 4
) { index -> ) { index ->
if (index == 0) CustomItems( if (index == 0) CustomItemsTab(
state = model.customItemsState, model = model,
cart = model.cart, Modifier.fillMaxHeight()
Modifier ) else ItemGroupTab(
.weight(1f) model = model,
.fillMaxHeight() groupIndex = index - 1,
) else CategorizedItems( Modifier.fillMaxHeight()
state = model.categorizedItemsByGroup[index - 1],
cart = model.cart,
Modifier
.weight(1f)
.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 @Composable
fun ManualBillTopBarActions( fun ManualBillTopBarActions(
model: ManualBillScreenModel model: ManualBillScreenModel
) { ) {
if (!model.showSearch) IconButton(onClick = { model.showSearch = !model.showSearch }) {
Icon(Icons.Default.Search, "Suchen")
}
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
IconButton(onClick = { IconButton(onClick = {
navigator.push(KaffeekasseNavigation.AccountScreen(model.session)) navigator.push(KaffeekasseNavigation.AccountScreen(model.session))
......
...@@ -77,7 +77,6 @@ import net.novagamestudios.kaffeekasse.util.richdata.RichDataState ...@@ -77,7 +77,6 @@ import net.novagamestudios.kaffeekasse.util.richdata.RichDataState
import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichStateIn import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichStateIn
import net.novagamestudios.kaffeekasse.util.richdata.combineRich import net.novagamestudios.kaffeekasse.util.richdata.combineRich
import net.novagamestudios.kaffeekasse.util.richdata.dataOrNull 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.mapRich
import net.novagamestudios.kaffeekasse.util.richdata.stateIn import net.novagamestudios.kaffeekasse.util.richdata.stateIn
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
...@@ -229,9 +228,9 @@ private class APIPurchaseController( ...@@ -229,9 +228,9 @@ private class APIPurchaseController(
}.stateIn(coroutineScope, SharingStarted.Eagerly) }.stateIn(coroutineScope, SharingStarted.Eagerly)
private val selfId get() = selfUserBasic.value.dataOrNull?.id private val selfId get() = selfUserBasic.value.dataOrNull?.id
private val selfUserExtended = selfUserBasic.flatMapLatestRich { // private val selfUserExtended = selfUserBasic.flatMapLatestRich {
kaffeekasse.getExtendedUserInfo(it.id) // kaffeekasse.getExtendedUserInfo(it.id)
} // }
// override val purchaseAccounts: RichDataFlow<List<PurchaseAccount>> = combineRich( // override val purchaseAccounts: RichDataFlow<List<PurchaseAccount>> = combineRich(
// selfUserExtended, // selfUserExtended,
......
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")
}
},
)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment