taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit 2f87dec68aea11b2a6691620def7a4791e037c95
parent 05300f1e8146a70c27ec31314542876e36c77366
Author: Iván Ávalos <avalos@disroot.org>
Date:   Fri,  1 Aug 2025 13:33:52 +0200

[wallet] add description and automaticExecutableIndex to v1

Diffstat:
Mtaler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt | 3+++
Mwallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt | 2+-
Mwallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt | 33+++++++++++++++++++++++++++------
Mwallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt | 7+++++++
Mwallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt | 31++++++++++++++++++-------------
5 files changed, 56 insertions(+), 20 deletions(-)

diff --git a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt @@ -223,6 +223,9 @@ data class Tax( @Serializable data class ContractChoice( val amount: Amount, + val description: String? = null, + @SerialName("description_i18n") + val descriptionI18n: Map<String, String>? = null, val inputs: List<ContractInput>, val outputs: List<ContractOutput>, @SerialName("max_fee") diff --git a/wallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt b/wallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt @@ -108,7 +108,7 @@ fun ExpandableCard( } } else { OutlinedCard( - modifier = modifier.padding(8.dp), + modifier = modifier.cardPaddings(), onClick = { setExpanded(!expanded) } ) { body() diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -28,11 +28,11 @@ import net.taler.common.Amount import net.taler.common.ContractInput import net.taler.common.ContractOutput import net.taler.common.ContractTerms +import net.taler.common.TalerUtils.getLocalizedString import net.taler.wallet.TAG import net.taler.wallet.backend.BackendManager import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.backend.WalletBackendApi -import net.taler.wallet.balances.BalanceManager import net.taler.wallet.balances.ScopeInfo import net.taler.wallet.exchanges.ExchangeManager import net.taler.wallet.payment.PayStatus.AlreadyPaid @@ -42,6 +42,7 @@ import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse import org.json.JSONObject import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail +import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail.PaymentPossible sealed class PayStatus { data object None : PayStatus() @@ -87,10 +88,17 @@ sealed class PayStatus { data class PayChoiceDetails( val choiceIndex: Int, val amountRaw: Amount, + val description: String? = null, + val descriptionI18n: Map<String, String>? = null, val inputs: List<ContractInput>, val outputs: List<ContractOutput>, val details: ChoiceSelectionDetail, -) +) { + val localizedDescription: String? + get() = description?.let { + getLocalizedString(descriptionI18n, it) + } +} @Serializable data class CheckPayTemplateResponse( @@ -139,8 +147,8 @@ class PaymentManager( api.request("getChoicesForPayment", GetChoicesForPaymentResponse.serializer()) { put("transactionId", transactionId) }.onSuccess { res -> - if (res.automaticExecution == true && res.defaultChoiceIndex != null) { - confirmPay(transactionId, res.defaultChoiceIndex, automaticExecution = true) + if (res.automaticExecution == true && res.automaticExecutableIndex != null) { + confirmPay(transactionId, res.automaticExecutableIndex, automaticExecution = true) return@onSuccess } @@ -157,7 +165,7 @@ class PaymentManager( ) ?: exchangeManager.getSpecForCurrency(choice.amountRaw.currency) when (choice) { - is ChoiceSelectionDetail.PaymentPossible -> { + is PaymentPossible -> { choice.copy( amountRaw = choice.amountRaw.withSpec(spec), amountEffective = choice.amountEffective.withSpec(spec), @@ -171,6 +179,8 @@ class PaymentManager( }.mapIndexed { i, choice -> PayChoiceDetails( choiceIndex = i, + description = choice.description, + descriptionI18n = choice.descriptionI18n, amountRaw = choice.amountRaw, inputs = (res.contractData as? ContractTerms.V1) ?.choices?.get(i)?.inputs ?: listOf(), @@ -178,7 +188,18 @@ class PaymentManager( ?.choices?.get(i)?.outputs ?: listOf(), details = choice, ) - }, + }.filter { + // Hide auto executable choice + res.automaticExecutableIndex != it.choiceIndex + }.sortedWith( + compareByDescending<PayChoiceDetails> { + it.choiceIndex == res.defaultChoiceIndex + }.thenByDescending { + it.details is PaymentPossible + }.thenByDescending { + it.amountRaw + }, + ), ) onSuccess() diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt @@ -87,6 +87,7 @@ data class GetChoicesForPaymentResponse( val contractData: ContractTerms, val defaultChoiceIndex: Int? = null, val automaticExecution: Boolean? = null, + val automaticExecutableIndex: Int? = null, ) { @Serializable @OptIn(ExperimentalSerializationApi::class) @@ -94,6 +95,8 @@ data class GetChoicesForPaymentResponse( sealed class ChoiceSelectionDetail { abstract val amountRaw: Amount abstract val tokenDetails: PaymentTokenAvailabilityDetails? + abstract val description: String? + abstract val descriptionI18n: Map<String, String>? @Serializable @SerialName("payment-possible") @@ -101,6 +104,8 @@ data class GetChoicesForPaymentResponse( override val amountRaw: Amount, val amountEffective: Amount, override val tokenDetails: PaymentTokenAvailabilityDetails? = null, + override val description: String? = null, + override val descriptionI18n: Map<String, String>? = null, ) : ChoiceSelectionDetail() @Serializable @@ -109,6 +114,8 @@ data class GetChoicesForPaymentResponse( override val amountRaw: Amount, val balanceDetails: PaymentInsufficientBalanceDetails? = null, override val tokenDetails: PaymentTokenAvailabilityDetails? = null, + override val description: String? = null, + override val descriptionI18n: Map<String, String>? = null, ) : ChoiceSelectionDetail() } } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentComposable.kt @@ -92,6 +92,7 @@ import net.taler.wallet.cleanExchange import net.taler.wallet.compose.BottomButtonBox import net.taler.wallet.compose.ExpandableSection import net.taler.wallet.compose.TalerSurface +import net.taler.wallet.compose.cardPaddings import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail.InsufficientBalance import net.taler.wallet.payment.GetChoicesForPaymentResponse.ChoiceSelectionDetail.PaymentPossible import net.taler.wallet.payment.TokenAvailabilityHint.MerchantUnexpected @@ -437,15 +438,7 @@ fun ChoicesSection( // CHOICES // TODO: LazyColumn would be better, but can't be nested - status.choices.sortedWith( - compareByDescending<PayChoiceDetails> { - it.choiceIndex == status.defaultChoiceIndex - }.thenByDescending { - it.details is PaymentPossible - }.thenByDescending { - it.amountRaw - } - ).forEach { choice -> + status.choices.forEach { choice -> PaymentChoice( choice, tokenFamilies, @@ -468,10 +461,8 @@ fun PaymentChoice( ) { OutlinedCard( modifier = Modifier - .padding( - horizontal = 9.dp, - vertical = 6.dp, - ).fillMaxWidth() + .cardPaddings() + .fillMaxWidth() .animateContentSize() .clickable { onSelect() }, border = if (selected) { @@ -499,6 +490,14 @@ fun PaymentChoice( ) } + choice.localizedDescription?.let { + Text( + text = it, + modifier = Modifier.padding(top = 9.dp), + style = MaterialTheme.typography.bodyLarge, + ) + } + // INPUTS if (choice.inputs.isNotEmpty()) { Column { @@ -810,6 +809,7 @@ private val contractTermsV1 = ContractTerms.V1( choices = listOf( ContractChoice( amount = Amount.fromJSONString("KUDOS:10"), + description = "Movie pass discount", maxFee = Amount.fromJSONString("KUDOS:0"), inputs = listOf( ContractInput.Token(tokenFamilySlug = "half-tax", count = 2), @@ -822,6 +822,7 @@ private val contractTermsV1 = ContractTerms.V1( ContractChoice( amount = Amount.fromJSONString("KUDOS:200"), + description = "Movie pass access renewal", maxFee = Amount.fromJSONString("KUDOS:0"), inputs = listOf( ContractInput.Token(tokenFamilySlug = "movie-pass"), @@ -833,6 +834,7 @@ private val contractTermsV1 = ContractTerms.V1( ContractChoice( amount = Amount.fromJSONString("KUDOS:0"), + description = "Movie pass access renewal", maxFee = Amount.fromJSONString("KUDOS:0"), inputs = listOf( ContractInput.Token(tokenFamilySlug = "movie-pass"), @@ -895,6 +897,7 @@ fun PromptPaymentV1Preview() { PayChoiceDetails( choiceIndex = 0, amountRaw = contractTermsV1.choices[0].amount, + description = "Movie pass discount", inputs = contractTermsV1.choices[0].inputs, outputs = contractTermsV1.choices[0].outputs, details = PaymentPossible( @@ -928,6 +931,7 @@ fun PromptPaymentV1Preview() { PayChoiceDetails( choiceIndex = 1, amountRaw = contractTermsV1.choices[1].amount, + description = "Movie pass access renewal", inputs = contractTermsV1.choices[1].inputs, outputs = contractTermsV1.choices[1].outputs, details = InsufficientBalance( @@ -937,6 +941,7 @@ fun PromptPaymentV1Preview() { PayChoiceDetails( choiceIndex = 2, amountRaw = contractTermsV1.choices[2].amount, + description = "Movie pass access renewal", inputs = contractTermsV1.choices[2].inputs, outputs = contractTermsV1.choices[2].outputs, details = PaymentPossible(