taler-android

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

commit 79f3a999da50e87687132d057d7c635285f4c4ae
parent 167d0d8ac6ed2bfe01151de45336cc1e45102080
Author: Iván Ávalos <avalos@disroot.org>
Date:   Wed, 29 May 2024 11:40:01 -0600

[wallet] Fix template defaults logic and currency options + auto focus for summary

bug 0008854

Diffstat:
Mwallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt | 22+++++++++++++++-------
Mwallet/src/main/java/net/taler/wallet/payment/PayTemplateDetails.kt | 16+++++++---------
Mwallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt | 2+-
Mwallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt | 50++++++++++++++++++++++++++++++++++++--------------
4 files changed, 59 insertions(+), 31 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt @@ -46,13 +46,21 @@ fun PayTemplateComposable( if (currencies.isEmpty()) { PayTemplateError(stringResource(R.string.payment_balance_insufficient)) } else when (val p = payStatus) { - is PayStatus.Checked -> PayTemplateOrderComposable( - currencies = currencies, - templateDetails = p.details, - onCreateAmount = onCreateAmount, - onError = onError, - onSubmit = onSubmit, - ) + is PayStatus.Checked -> { + val usableCurrencies = currencies.intersect(p.supportedCurrencies.toSet()).toList() + if (usableCurrencies.isEmpty()) { + // If user doesn't have any supported currency, they can't pay either + PayTemplateError(stringResource(R.string.payment_balance_insufficient)) + } else { + PayTemplateOrderComposable( + usableCurrencies = usableCurrencies, + templateDetails = p.details, + onCreateAmount = onCreateAmount, + onError = onError, + onSubmit = onSubmit, + ) + } + } is PayStatus.None, is PayStatus.Loading -> PayTemplateLoading() is PayStatus.AlreadyPaid -> PayTemplateError(stringResource(R.string.payment_already_paid)) diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateDetails.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateDetails.kt @@ -64,12 +64,6 @@ data class TemplateContractDetailsDefaults( val minimumAge: Int? = null, ) -fun TemplateContractDetailsDefaults?.isNullOrEmpty() = - this == null || (summary == null - && currency == null - && amount == null - && minimumAge == null) - @Serializable class WalletTemplateDetails( /** @@ -105,11 +99,15 @@ class WalletTemplateDetails( ?: editableDefaults?.currency ?: templateContract.currency - fun isSummaryEditable() = editableDefaults?.summary != null + fun isSummaryEditable() = templateContract.summary == null + + fun isAmountEditable() = templateContract.amount == null - fun isAmountEditable() = editableDefaults?.amount != null + fun isCurrencyEditable() = requiredCurrency == null && templateContract.currency == null - fun isCurrencyEditable() = requiredCurrency == null && editableDefaults?.currency != null + fun isTemplateEditable() = isSummaryEditable() + || isAmountEditable() + || isCurrencyEditable() // NOTE: it is important to nullify non-editable values! fun toTemplateParams() = TemplateParams( diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt @@ -79,7 +79,7 @@ class PayTemplateFragment : Fragment() { showError(payStatus.error) } - is PayStatus.Checked -> if (payStatus.details.editableDefaults.isNullOrEmpty()) { + is PayStatus.Checked -> if (!payStatus.details.isTemplateEditable()) { createOrder(payStatus.details.toTemplateParams()) } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt @@ -24,12 +24,18 @@ import androidx.compose.material3.Button import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.End +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -41,9 +47,10 @@ import net.taler.wallet.compose.AmountInputField import net.taler.wallet.compose.TalerSurface import net.taler.wallet.deposit.CurrencyDropdown +@OptIn(ExperimentalComposeUiApi::class) @Composable fun PayTemplateOrderComposable( - currencies: List<String>, // assumed to have size > 0 + usableCurrencies: List<String>, // non-empty intersection between the stored currencies and the ones supported by the merchant templateDetails: WalletTemplateDetails, onCreateAmount: (String, String) -> AmountResult, onError: (msgRes: Int) -> Unit, @@ -53,32 +60,40 @@ fun PayTemplateOrderComposable( val defaultAmount = templateDetails.defaultAmount val defaultCurrency = templateDetails.defaultCurrency - var summary by remember { mutableStateOf(defaultSummary) } - var currency by remember { mutableStateOf(defaultCurrency ?: currencies[0]) } + val summaryFocusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + + var summary by remember { mutableStateOf(defaultSummary ?: "") } + var currency by remember { mutableStateOf(defaultCurrency ?: usableCurrencies[0]) } var amount by remember { mutableStateOf(defaultAmount?.amountStr ?: "0") } Column(horizontalAlignment = End) { - if (defaultSummary != null) OutlinedTextField( + OutlinedTextField( modifier = Modifier .padding(horizontal = 16.dp) - .fillMaxWidth(), - value = summary ?: "", - isError = summary.isNullOrBlank(), + .fillMaxWidth() + .focusRequester(summaryFocusRequester) + .onFocusChanged { + if (it.isFocused) { + keyboardController?.show() + } + }, + value = summary, + isError = templateDetails.isSummaryEditable() && summary.isBlank(), onValueChange = { summary = it }, singleLine = true, readOnly = !templateDetails.isSummaryEditable(), label = { Text(stringResource(R.string.withdraw_manual_ready_subject)) }, ) - if (defaultAmount != null || defaultCurrency != null) AmountField( + AmountField( modifier = Modifier .padding(16.dp) .fillMaxWidth(), amount = amount, currency = currency, - currencies = currencies, - // TODO: uncomment when merchant supports multi-currency - // readOnlyCurrency = !templateDetails.isCurrencyEditable(), + currencies = usableCurrencies, + readOnlyCurrency = !templateDetails.isCurrencyEditable(), readOnlyAmount = !templateDetails.isAmountEditable(), onAmountChosen = { a, c -> amount = a @@ -104,6 +119,13 @@ fun PayTemplateOrderComposable( Text(stringResource(R.string.payment_create_order)) } } + + LaunchedEffect(Unit) { + if (templateDetails.isSummaryEditable() + && templateDetails.defaultSummary == null) { + summaryFocusRequester.requestFocus() + } + } } @Composable @@ -156,7 +178,7 @@ fun PayTemplateDefaultPreview() { TalerSurface { PayTemplateOrderComposable( templateDetails = defaultTemplateDetails, - currencies = listOf("KUDOS", "ARS"), + usableCurrencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, @@ -172,7 +194,7 @@ fun PayTemplateFixedAmountPreview() { TalerSurface { PayTemplateOrderComposable( templateDetails = defaultTemplateDetails, - currencies = listOf("KUDOS", "ARS"), + usableCurrencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, @@ -188,7 +210,7 @@ fun PayTemplateBlankSubjectPreview() { TalerSurface { PayTemplateOrderComposable( templateDetails = defaultTemplateDetails, - currencies = listOf("KUDOS", "ARS"), + usableCurrencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) },