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 c7bdbab2f5e96876a3e8d2306841586f30cb61d6..0084fd1d5a073c3b12ba8aa870d5e803c6715300 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 @@ -18,7 +18,7 @@ data class ManualBillDetails( data class PurchaseAccount( override val name: Name, override val id: Int, - val isDefault: Boolean = false + override val isDefault: Boolean = false ) : ScraperPurchaseAccount @Serializable data class ItemGroup( @@ -49,4 +49,6 @@ data class ManualBillDetails( } } -sealed interface ScraperPurchaseAccount : PurchaseAccount +sealed interface ScraperPurchaseAccount : PurchaseAccount { + val isDefault: Boolean +} 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 30f34dc16e6d0d17341bbe1948ee60fa1dd7f63a..00023a5068809a8249e1ec1929c0392fa8822b12 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 @@ -19,9 +19,10 @@ import net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount class AccountSelectionState<out PA : PurchaseAccount>( - val accounts: List<PA> + val accounts: List<PA>, + initialIndex: Int = -1 ) { - var selectedIndex by mutableStateOf(0) + var selectedIndex by mutableStateOf(initialIndex) val selectedAccount: PA? get() = accounts.getOrNull(selectedIndex) } 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 0d65ea959ac90f2ad908e3bbf1f64f0fd395c791..0820ff42793eeef8326f525a4f11cc3945b44602 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 @@ -46,6 +46,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.launch import net.novagamestudios.common_utils.Logger import net.novagamestudios.common_utils.compose.components.BoxCenter @@ -67,11 +68,11 @@ import net.novagamestudios.kaffeekasse.model.session.User import net.novagamestudios.kaffeekasse.repositories.LoginRepository import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository +import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.PurchaseController.Account import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.CategoryIcon import net.novagamestudios.kaffeekasse.ui.util.Toasts import net.novagamestudios.kaffeekasse.ui.util.ToastsState 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 @@ -82,11 +83,10 @@ import net.novagamestudios.kaffeekasse.util.richdata.stateIn import kotlin.time.Duration.Companion.seconds - class CheckoutState<PA : PurchaseAccount> private constructor( coroutineScope: CoroutineScope, + private val session: Session.WithRealUser, val cart: MutableCart, - val hasInstantCheckout: Boolean, private val purchaseController: PurchaseController<PA>, private val loginRepository: LoginRepository, private val onSubmitted: suspend () -> Unit @@ -94,6 +94,7 @@ class CheckoutState<PA : PurchaseAccount> private constructor( var showModal by mutableStateOf(false) + val hasInstantCheckout: Boolean = session is Session.WithDevice private val loadingMutex = ReentrantActionState() val isCheckoutLoading by loadingMutex @@ -105,7 +106,13 @@ class CheckoutState<PA : PurchaseAccount> private constructor( val accountSelectionState: RichDataState<AccountSelectionState<PA>> by lazy { purchaseController .purchaseAccounts - .mapRich { accounts -> AccountSelectionState(accounts) } + .mapRich { accounts -> accounts.sortedBy { it.name } } + .mapRich { accounts -> + AccountSelectionState( + accounts = accounts.map { it.account }, + initialIndex = accounts.indexOfFirst { it.isDefault } + ) + } .collectAsRichStateIn(this) } @@ -153,8 +160,8 @@ class CheckoutState<PA : PurchaseAccount> private constructor( onSubmitted: suspend () -> Unit ): CheckoutState<*> = CheckoutState( coroutineScope = coroutineScope, + session = session, cart = kaffeekasseCartProvider[session.realUser], - hasInstantCheckout = session is Session.WithDevice, purchaseController = when (session) { is Session.WithDevice -> APIPurchaseController( user = session.realUser, @@ -173,19 +180,32 @@ class CheckoutState<PA : PurchaseAccount> private constructor( } private interface PurchaseController<PA : PurchaseAccount> { - val purchaseAccounts: RichDataFlow<List<PA>> + val purchaseAccounts: RichDataFlow<List<Account<PA>>> suspend fun refreshPurchaseAccountsIfNeeded() suspend fun performPurchase(purchaseAccount: PA, cart: Cart) + + data class Account<out PA : PurchaseAccount>( + val account: PA, + val isDefault: Boolean + ) : PurchaseAccount by account } private class ScraperPurchaseController( private val user: User, private val kaffeekasse: KaffeekasseRepository ) : PurchaseController<ScraperPurchaseAccount> { - override val purchaseAccounts: RichDataSource<List<ScraperPurchaseAccount>> = kaffeekasse.manualBillAccounts[user] + private val manualBillAccounts = kaffeekasse.manualBillAccounts[user] + override val purchaseAccounts: RichDataFlow<List<Account<ScraperPurchaseAccount>>> = manualBillAccounts.mapRich { accounts -> + accounts.map { account -> + Account( + account = account, + isDefault = account.isDefault + ) + } + } override suspend fun refreshPurchaseAccountsIfNeeded() { - purchaseAccounts.ensureCleanData() + manualBillAccounts.ensureCleanData() } override suspend fun performPurchase(purchaseAccount: ScraperPurchaseAccount, cart: Cart) { @@ -206,7 +226,8 @@ private class APIPurchaseController( private val selfUserBasic = kaffeekasse.basicUserInfoList.mapRich { userList -> val selfDisplayName = user.displayName ?: return@mapRich null userList.firstOrNull { it.name.firstLast == selfDisplayName } - }.stateIn(coroutineScope) + }.stateIn(coroutineScope, SharingStarted.Eagerly) + private val selfId get() = selfUserBasic.value.dataOrNull?.id private val selfUserExtended = selfUserBasic.flatMapLatestRich { kaffeekasse.getExtendedUserInfo(it.id) @@ -231,22 +252,27 @@ private class APIPurchaseController( kaffeekasse.manualBillAccounts[user], kaffeekasse.basicUserInfoList ) { scraperAccounts, apiAccounts -> - scraperAccounts.mapNotNull { account -> apiAccounts.firstOrNull { it.name == account.name } } + scraperAccounts.mapNotNull { scraperAccount -> + apiAccounts.firstOrNull { it.name == scraperAccount.name }?.let { apiAccount -> + Account( + account = apiAccount, + isDefault = scraperAccount.isDefault + ) + } + } } override suspend fun refreshPurchaseAccountsIfNeeded() { kaffeekasse.basicUserInfoList.ensureCleanData() - selfUserBasic.value.dataOrNull?.let { - kaffeekasse.getExtendedUserInfo(it.id).ensureCleanData() + selfId?.let { + kaffeekasse.getExtendedUserInfo(it).ensureCleanData() } } override suspend fun performPurchase(purchaseAccount: APIPurchaseAccount, cart: Cart) { kaffeekasse.purchase( asUser = user, cart = cart, - targetAccount = purchaseAccount.takeUnless { - it.id == selfUserBasic.value.dataOrNull?.id - } + targetAccount = purchaseAccount.takeUnless { it.id == selfId } ) } }