diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt index aa790757b56d0d8347f908aae51f7658565e5c4c..76b51fb1264977914fa6ea4245b18263d3721a5d 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt @@ -17,6 +17,8 @@ import net.novagamestudios.kaffeekasse.api.portal.PortalClient import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItem import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails +import net.novagamestudios.kaffeekasse.model.kaffeekasse.Name.Companion.toNameOrNull +import net.novagamestudios.kaffeekasse.model.kaffeekasse.ScraperPurchaseAccount import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty import java.time.LocalDateTime @@ -45,7 +47,7 @@ class KaffeekasseScraper( } } - suspend fun submitCart(account: ManualBillDetails.PurchaseAccount, cart: Cart): Unit = with(client) { + suspend fun submitCart(account: ScraperPurchaseAccount, cart: Cart): Unit = with(client) { if (cart.isEmpty()) return post( manualBillUrl, @@ -73,7 +75,9 @@ private fun Doc.scrapeManualBillDetails(): ManualBillDetails = form { if (option.hasAttribute("disabled")) null else if (option.attribute("value").isBlank()) null else ManualBillDetails.PurchaseAccount( - name = option.text, + name = option.text.let { + requireNotNull(it.toNameOrNull()) { "Invalid account name: $it" } + }, id = option.attribute("value").let { requireNotNull(it.toIntOrNull()) { "Invalid account id: $it" } }, diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt index e9f493409678e07f2658348672317aba57bebc75..3e2b94425645f533a870493706a34dd241d342b3 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt @@ -2,6 +2,7 @@ package net.novagamestudios.kaffeekasse.api.kaffeekasse.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import net.novagamestudios.kaffeekasse.model.kaffeekasse.Name import net.novagamestudios.kaffeekasse.util.IntAsBooleanSerializer @Serializable @@ -9,12 +10,9 @@ data class BasicUserInfo( @SerialName("id") override val id: Int, @SerialName("name") - override val name: String, + override val name: Name, @SerialName("empty_pin") val noPinSet: @Serializable(with = IntAsBooleanSerializer::class) Boolean? = null ) : APIPurchaseAccount { - val firstName by lazy { name.split(", ")[1] } - val lastName by lazy { name.split(", ")[0] } - val mayHavePin get() = noPinSet == null || !noPinSet } \ No newline at end of file diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt index cb98eaee42a6b1b559a12d149ca18f25d7d1cf07..af64c210b240e8dbeae7abf46d10533a81ec949e 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt @@ -2,11 +2,12 @@ package net.novagamestudios.kaffeekasse.api.kaffeekasse.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import net.novagamestudios.kaffeekasse.model.kaffeekasse.Name @Serializable data class BlackWhiteListUserInfo( @SerialName("target_id") override val id: Int, @SerialName("name") - override val name: String + override val name: Name ) : APIPurchaseAccount \ No newline at end of file diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt index 3ef3fbbe0e5ae2a9d83117045a47ebee1d6078ad..ac1e31fc7b302c0d34a1854d6e5962d63ee170c8 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt @@ -3,13 +3,14 @@ package net.novagamestudios.kaffeekasse.api.kaffeekasse.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonArray +import net.novagamestudios.kaffeekasse.model.kaffeekasse.Name @Serializable data class UserInfoResponse( @SerialName("user_id") override val id: Int, @SerialName("name") - override val name: String, + override val name: Name, @SerialName("balance") override val balance: Double, @SerialName("favorites") @@ -22,7 +23,7 @@ data class UserInfoResponse( sealed interface ExtendedUserInfo : APIPurchaseAccount { override val id: Int - override val name: String + override val name: Name val balance: Double val favorites: JsonArray val blacklist: List<BlackWhiteListUserInfo>? diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ManualBillDetails.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ManualBillDetails.kt index 2753b2f3137190d0018555ab998ae8506dd7b5ad..c7bdbab2f5e96876a3e8d2306841586f30cb61d6 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ManualBillDetails.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ManualBillDetails.kt @@ -16,10 +16,10 @@ data class ManualBillDetails( ) { @Serializable data class PurchaseAccount( - override val name: String, + override val name: Name, override val id: Int, val isDefault: Boolean = false - ) : net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount + ) : ScraperPurchaseAccount @Serializable data class ItemGroup( override val originalName: String, @@ -47,4 +47,6 @@ data class ManualBillDetails( override val imageDrawable: Int? get() = knownItem?.drawableResource override val imageUrl: String? get() = null } -} \ No newline at end of file +} + +sealed interface ScraperPurchaseAccount : PurchaseAccount diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt index ffc199cc07122ffa66d126f21a5f4c9f1cd9c590..db8d3d222f61de5dffd5f061ee29ace81900e316 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt @@ -1,6 +1,43 @@ package net.novagamestudios.kaffeekasse.model.kaffeekasse +import kotlinx.serialization.Serializable + interface PurchaseAccount { val id: Int - val name: String -} \ No newline at end of file + val name: Name +} + +@Serializable +@JvmInline +value class Name private constructor(private val raw: String) : Comparable<Name> { + private val components get() = raw.split(Separator) + + operator fun component1() = components[0] + operator fun component2() = components[1] + + val last get() = component1() + val first get() = component2() + + val firstLast get() = "$first $last" + + val char get() = last.first().uppercaseChar() + + override fun toString() = raw + + override fun compareTo(other: Name) = raw.uppercase().compareTo(other.raw.uppercase()) + + init { + require(components.size == 2) { "Name \"$raw\" does not have exactly two components" } + } + + companion object { + const val Separator = ", " + + operator fun invoke(firstName: String, lastName: String) = Name("$lastName$Separator$firstName") + + fun String.toNameOrNull(): Name? = this + .split(Separator) + .takeIf { it.size == 2 } + ?.let { (lastName, firstName) -> Name(firstName, lastName) } + } +} diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt index 0cd93d672e1d32d4733cad0a534e863a14642728..534260f9efe840bb8114117f1e9d4a7db31f5f44 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt @@ -22,6 +22,7 @@ import net.novagamestudios.kaffeekasse.model.kaffeekasse.ItemGroup import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItem import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItemGroup import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails +import net.novagamestudios.kaffeekasse.model.kaffeekasse.ScraperPurchaseAccount import net.novagamestudios.kaffeekasse.model.kaffeekasse.Stock import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty @@ -122,31 +123,31 @@ class KaffeekasseRepository( // Unknown item itemGroupsById[item.itemTypeId]?.items?.add( ItemImpl( - id = item.id, - originalName = item.originalName, - category = ItemCategory.Other, - cleanProductName = item.originalName, - cleanVariantName = null, - price = item.price, - estimatedPrice = item.price, - imageDrawable = null, - imageUrl = item.imageUrl - ) + id = item.id, + originalName = item.originalName, + category = ItemCategory.Other, + cleanProductName = item.originalName, + cleanVariantName = null, + price = item.price, + estimatedPrice = item.price, + imageDrawable = null, + imageUrl = item.imageUrl + ) ) } else knownItems.forEach { known -> // Combine with known values itemGroupsById[item.itemTypeId]?.items?.add( ItemImpl( - id = item.id, - originalName = item.originalName, - category = known.category, - cleanProductName = known.cleanProductName, - cleanVariantName = known.cleanVariantName, - price = item.price, - estimatedPrice = item.price, - imageDrawable = known.drawableResource, - imageUrl = item.imageUrl - ) + id = item.id, + originalName = item.originalName, + category = known.category, + cleanProductName = known.cleanProductName, + cleanVariantName = known.cleanVariantName, + price = item.price, + estimatedPrice = item.price, + imageDrawable = known.drawableResource, + imageUrl = item.imageUrl + ) ) } } @@ -173,7 +174,7 @@ class KaffeekasseRepository( } - suspend fun purchase(asUser: User, cart: Cart, account: ManualBillDetails.PurchaseAccount) { + suspend fun purchase(asUser: User, cart: Cart, account: ScraperPurchaseAccount) { if (cart.isEmpty()) return requireLoggedIn(asUser) scraper.submitCart(account, cart) @@ -202,6 +203,7 @@ class KaffeekasseRepository( } override fun markUserDataDirty() { + stock.markDirty() account.clear() transactions.clear() extendedUserInfoById.values.forEach { it.markDirty() } diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt index d358d2d027ce01f482f4aa6db59c9607e016e72e..30f34dc16e6d0d17341bbe1948ee60fa1dd7f63a 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt @@ -18,17 +18,17 @@ import androidx.compose.ui.Modifier import net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount -class AccountSelectionState( - val accounts: List<PurchaseAccount> +class AccountSelectionState<out PA : PurchaseAccount>( + val accounts: List<PA> ) { var selectedIndex by mutableStateOf(0) - val selectedAccount: PurchaseAccount? get() = accounts.getOrNull(selectedIndex) + val selectedAccount: PA? get() = accounts.getOrNull(selectedIndex) } @Composable fun AccountSelection( - state: AccountSelectionState, + state: AccountSelectionState<*>, modifier: Modifier = Modifier ) { var expanded by remember { mutableStateOf(false) } @@ -38,7 +38,7 @@ fun AccountSelection( modifier ) { OutlinedTextField( - state.selectedAccount?.name ?: "", + state.selectedAccount?.name?.let { "$it" } ?: "", onValueChange = { }, Modifier .menuAnchor(MenuAnchorType.PrimaryNotEditable), @@ -53,7 +53,7 @@ fun AccountSelection( ) { state.accounts.forEachIndexed { index, account -> DropdownMenuItem( - text = { Text(account.name) }, + text = { Text("${account.name}") }, onClick = { state.selectedIndex = index expanded = false 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 b9af47722dc9b40a7aa31f99954f9a86b1d1700b..091ac1258045e3fe377d761ad87dd810460ff0bf 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 @@ -57,9 +57,9 @@ import net.novagamestudios.common_utils.warn import net.novagamestudios.kaffeekasse.KaffeekasseModule.Companion.kaffeekasseCart import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.APIPurchaseAccount import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart -import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart import net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount +import net.novagamestudios.kaffeekasse.model.kaffeekasse.ScraperPurchaseAccount import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty import net.novagamestudios.kaffeekasse.model.kaffeekasse.isNotEmpty import net.novagamestudios.kaffeekasse.model.session.Session @@ -68,6 +68,7 @@ import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.CategoryIcon import net.novagamestudios.kaffeekasse.util.richdata.RichDataFlow +import net.novagamestudios.kaffeekasse.util.richdata.RichDataSource import net.novagamestudios.kaffeekasse.util.richdata.RichDataState import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichStateIn import net.novagamestudios.kaffeekasse.util.richdata.combineRich @@ -78,7 +79,7 @@ import net.novagamestudios.kaffeekasse.util.richdata.stateIn import kotlin.time.Duration.Companion.seconds -abstract class CheckoutViewModel( +abstract class CheckoutViewModel<PA : PurchaseAccount>( private val coroutineScope: CoroutineScope, protected val kaffeekasse: KaffeekasseRepository, val cart: MutableCart, @@ -94,7 +95,13 @@ abstract class CheckoutViewModel( var indicateSuccess by mutableStateOf(false) - abstract val accountSelectionState: RichDataState<AccountSelectionState> + protected abstract val purchaseAccounts: RichDataFlow<List<PA>> + + val accountSelectionState: RichDataState<AccountSelectionState<PA>> by lazy { + purchaseAccounts + .mapRich { accounts -> AccountSelectionState(accounts) } + .collectAsRichStateIn(coroutineScope) + } protected abstract suspend fun refreshPurchaseAccountsIfNeeded() fun refreshPurchaseAccounts() { @@ -104,7 +111,7 @@ abstract class CheckoutViewModel( } } } - protected abstract suspend fun performSubmitCart(purchaseAccount: PurchaseAccount) + protected abstract suspend fun performSubmitCart(purchaseAccount: PA) fun submitCart() { if (cart.isEmpty()) return @@ -136,7 +143,7 @@ abstract class CheckoutViewModel( session: Session.WithRealUser, coroutineScope: CoroutineScope, onSubmitted: suspend () -> Unit - ): CheckoutViewModel = if (session is Session.WithDevice) { + ): CheckoutViewModel<*> = if (session is Session.WithDevice) { APICheckoutViewModel( session.realUser, coroutineScope, @@ -162,27 +169,22 @@ private class ScraperCheckoutViewModel( kaffeekasse: KaffeekasseRepository, cart: MutableCart, onSubmitted: suspend () -> Unit -) : CheckoutViewModel( +) : CheckoutViewModel<ScraperPurchaseAccount>( coroutineScope, kaffeekasse, cart, onSubmitted ) { - - private val purchaseAccounts = kaffeekasse.manualBillAccounts[user] - - override val accountSelectionState = purchaseAccounts.mapRich { accounts -> - AccountSelectionState(accounts) - }.collectAsRichStateIn(coroutineScope) + override val purchaseAccounts: RichDataSource<List<ScraperPurchaseAccount>> = kaffeekasse.manualBillAccounts[user] override suspend fun refreshPurchaseAccountsIfNeeded() { purchaseAccounts.ensureCleanData() } - override suspend fun performSubmitCart(purchaseAccount: PurchaseAccount) { + override suspend fun performSubmitCart(purchaseAccount: ScraperPurchaseAccount) { kaffeekasse.purchase( asUser = user, cart = cart, - account = purchaseAccount as ManualBillDetails.PurchaseAccount + account = purchaseAccount ) } } @@ -193,49 +195,52 @@ private class APICheckoutViewModel( kaffeekasse: KaffeekasseRepository, cart: MutableCart, onSubmitted: suspend () -> Unit -) : CheckoutViewModel(coroutineScope, kaffeekasse, cart, onSubmitted) { +) : CheckoutViewModel<APIPurchaseAccount>(coroutineScope, kaffeekasse, cart, onSubmitted) { private val selfUserBasic = kaffeekasse.basicUserInfoList.mapRich { userList -> val selfDisplayName = user.displayName ?: return@mapRich null - userList.firstOrNull { "${it.firstName} ${it.lastName}" == selfDisplayName } + userList.firstOrNull { it.name.firstLast == selfDisplayName } }.stateIn(coroutineScope) private val selfUserExtended = selfUserBasic.flatMapLatestRich { kaffeekasse.getExtendedUserInfo(it.id) } - private val purchaseAccounts: RichDataFlow<List<PurchaseAccount>> = combineRich( - selfUserExtended, +// override val purchaseAccounts: RichDataFlow<List<PurchaseAccount>> = combineRich( +// selfUserExtended, +// kaffeekasse.basicUserInfoList +// ) { self, userList -> +// // FIXME wrong way around +// +// val fromWhitelist = self.whitelist.orEmpty() +// val fromBlacklist = self.blacklist?.let { blacklist -> +// val blacklistIds = blacklist.mapTo(mutableSetOf()) { it.id } +// userList.filter { it.id !in blacklistIds } +// }.orEmpty() +// +// listOf(self) + fromWhitelist + fromBlacklist +// } + + override val purchaseAccounts = combineRich( + kaffeekasse.manualBillAccounts[user], kaffeekasse.basicUserInfoList - ) { self, userList -> - // FIXME wrong way around - - val fromWhitelist = self.whitelist.orEmpty() - val fromBlacklist = self.blacklist?.let { blacklist -> - val blacklistIds = blacklist.mapTo(mutableSetOf()) { it.id } - userList.filter { it.id !in blacklistIds } - }.orEmpty() - - listOf(self) + fromWhitelist + fromBlacklist + ) { scraperAccounts, apiAccounts -> + scraperAccounts.mapNotNull { account -> apiAccounts.firstOrNull { it.name == account.name } } } - override val accountSelectionState = purchaseAccounts.mapRich { accounts -> - AccountSelectionState(accounts) - }.collectAsRichStateIn(coroutineScope) - override suspend fun refreshPurchaseAccountsIfNeeded() { kaffeekasse.basicUserInfoList.ensureCleanData() selfUserBasic.value.dataOrNull?.let { kaffeekasse.getExtendedUserInfo(it.id).ensureCleanData() } } - override suspend fun performSubmitCart(purchaseAccount: PurchaseAccount) { + override suspend fun performSubmitCart(purchaseAccount: APIPurchaseAccount) { kaffeekasse.purchase( asUser = user, cart = cart, targetAccount = purchaseAccount.takeUnless { it.id == selfUserBasic.value.dataOrNull?.id - } as? APIPurchaseAccount + } ) } } @@ -243,7 +248,7 @@ private class APICheckoutViewModel( @Composable fun Checkout( - vm: CheckoutViewModel, + vm: CheckoutViewModel<*>, modifier: Modifier = Modifier ) { if (vm.cart.isNotEmpty()) CheckoutFAB( @@ -293,7 +298,7 @@ private fun CheckoutFAB( @Composable private fun CheckoutModal( - accountSelectionState: AccountSelectionState, + accountSelectionState: AccountSelectionState<*>, cart: MutableCart, onSubmit: () -> Unit, onDismissRequest: () -> Unit, @@ -327,7 +332,7 @@ private fun CheckoutModal( @Composable private fun CheckoutDetails( - accountSelectionState: AccountSelectionState, + accountSelectionState: AccountSelectionState<*>, cart: MutableCart, onSubmit: () -> Unit, modifier: Modifier = Modifier, diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt index 2ffadf561e2583c31fee8a30077a479eb9213836..cb27a9c510f19b92197fef63bd59a2d67bf06d36 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt @@ -127,7 +127,8 @@ class CustomItemsViewModel private constructor( allItemsByName: Map<String, Item>, transactions: List<Transaction> ): Sequence<Item> { - val statsByItem = transactions + val statsByItem: Map<Item, ItemStats> = transactions.asSequence() + .take(TransactionsLimit) .mapNotNull { val purpose = it.purpose as? Transaction.Purpose.Purchase ?: return@mapNotNull null val item = allItemsByName[purpose.itemName] ?: return@mapNotNull null @@ -169,6 +170,7 @@ class CustomItemsViewModel private constructor( val purchaseCount: Int ) + private const val TransactionsLimit = 100 private const val RecentItemsConsidered = 10 private const val FrequentItemsConsidered = 10 private const val RecentItemsWeight = 1.0 diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt index 4b93751b3e85a5f751efea3f738524d7c8779778..9c6fb2d808861665579ecae7ea3ed65d5364a85d 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt @@ -148,7 +148,7 @@ private fun ItemInformation( // Very bad code val app = app() - val session = app.portalRepository.session.collectAsState() + val session = app.portalRepository.session.collectAsState().value if (session is Session.WithRealUser) { val transactionsState = app.kaffeekasseRepository.transactions[session.realUser].collectAsRichState() val lastUnitPrice by remember { diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt index e1117c1b3e2bbe1ba573ce71ff468cd90a8d46aa..83f8330ca611d2caf9b6a2a81cce04ed2930c891 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt @@ -118,9 +118,9 @@ class UserSelectionViewModel private constructor( users, loadDuringTransform = true ) { query, users -> - val sorted = users.asSequence().sortedBy { it.lastName.uppercase() } + val sorted = users.asSequence().sortedBy { it.name } if (query.isBlank()) return@combineRich sorted.toList() - sorted.filter { query in "${it.firstName} ${it.lastName}".lowercase() }.toList() + sorted.filter { query in it.name.firstLast.lowercase() }.toList() } .flowOn(Dispatchers.IO) .stateIn(screenModelScope) @@ -257,7 +257,7 @@ fun UserSelection( val gridState = rememberLazyGridState() val coroutineScope = rememberCoroutineScope() - val currentStartChars by rememberDerivedStateOf { + val currentStartChars: Set<Char> by rememberDerivedStateOf { val start = gridState.layoutInfo.viewportStartOffset + gridState.layoutInfo.beforeContentPadding val end = gridState.layoutInfo.viewportEndOffset - gridState.layoutInfo.afterContentPadding gridState.layoutInfo.visibleItemsInfo @@ -267,13 +267,13 @@ fun UserSelection( mid in start..<end } .mapNotNull { it.contentType as? Int } - .mapNotNullTo(mutableSetOf()) { users.getOrNull(it)?.charOrNull } + .mapNotNullTo(mutableSetOf()) { users.getOrNull(it)?.name?.char } } val userIndexAndCharIndexByChar: Map<Char, Pair<Int, Int>> by rememberDerivedStateOf { users.asSequence() .withIndex() - .mapNotNull { (userIndex, user) -> - user.charOrNull?.let { it to userIndex } + .map { (userIndex, user) -> + user.name.char to userIndex } .distinctBy { (char, _) -> char } .withIndex() @@ -463,7 +463,7 @@ private fun UserGrid( ) { var prevChar: Char? = null users.forEachIndexed { i, user -> - val char = user.charOrNull + val char = user.name.char if (prevChar != char) { item( key = char, @@ -518,9 +518,9 @@ private fun UserItem( .padding(horizontal = 16.dp) ) { ProvideTextStyle(MaterialTheme.typography.titleMedium) { - Text("${user.lastName},") + Text("${user.name.last},") Spacer(Modifier.width(8.dp)) - Text(user.firstName) + Text(user.name.first) } Spacer(Modifier.weight(1f)) if (user.noPinSet == false) { @@ -551,7 +551,7 @@ private fun UserAuthDialog( TextButton(onClick = onDismiss) { Text("Abbrechen") } }, icon = { Icon(Icons.Default.Password, "PIN") }, - title = { Text("${state.user.firstName} ${state.user.lastName}") }, + title = { Text(state.user.name.firstLast) }, text = { val focusRequester = remember { FocusRequester() } OutlinedTextField( @@ -577,5 +577,5 @@ private fun UserAuthDialog( ) -private val BasicUserInfo.charOrNull get() = lastName.firstOrNull()?.uppercaseChar() +private val BasicUserInfo.charOrNull get() = name.last.firstOrNull()?.uppercaseChar() diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFunctions.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFunctions.kt index c06161256fe40b7432deecbaf8bb61da9003e249..bf7fce133fe961cb3c39ef902708d68820166ab5 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFunctions.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFunctions.kt @@ -3,7 +3,7 @@ package net.novagamestudios.kaffeekasse.util.richdata import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -interface RichDataFunctions<in T : Any> { +interface RichDataFunctions<out T : Any> { fun markDirty() suspend fun ensureCleanData() suspend fun refresh() diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataSource.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataSource.kt index 4374a937e6089b9cfa50b8c0463ffee3917ee8c5..c3db23acfab1e95da739fd69933dac4ecde7b1e6 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataSource.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataSource.kt @@ -2,7 +2,6 @@ package net.novagamestudios.kaffeekasse.util.richdata import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -14,7 +13,7 @@ typealias RichDataCollector<T> = FlowCollector<RichData<T>> typealias RichDataFactory<T> = suspend RichDataCollector<T>.() -> RichData<T> -interface RichDataSource<T : Any> : RichDataFunctions<T>, RichDataStateFlow<T> { +interface RichDataSource<out T : Any> : RichDataFunctions<T>, RichDataStateFlow<T>, RichDataFlow<T> { companion object { context (CoroutineScope) fun <T : Any> RichDataSource(