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:
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(