taler-android

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

commit d1da72fad71abb5f33e4e25dd8e49e87b2aa4d3c
parent bcf0ddff322c80803bbdd632cdb18cd85bfe27c1
Author: Iván Ávalos <avalos@disroot.org>
Date:   Fri,  1 Nov 2024 18:20:30 +0100

[wallet] refactor AmountInputField with better logic

Diffstat:
Mtaler-kotlin-android/src/main/java/net/taler/common/Amount.kt | 27++++++++++++++++++++++++++-
Awallet/src/main/java/net/taler/wallet/compose/AmountInputFIeld.kt | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dwallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt | 290-------------------------------------------------------------------------------
Mwallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt | 5++---
Mwallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt | 5++---
Mwallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt | 9++++-----
Mwallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt | 6++----
Mwallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt | 6++----
Mwallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt | 18+++++++++---------
Mwallet/src/main/java/net/taler/wallet/withdraw/WithdrawalShowInfo.kt | 4+---
10 files changed, 235 insertions(+), 322 deletions(-)

diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt @@ -23,6 +23,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Serializer import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import java.math.RoundingMode import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.text.NumberFormat @@ -68,6 +69,7 @@ data class Amount( companion object { + const val DEFAULT_INPUT_DECIMALS = 2 private const val FRACTIONAL_BASE: Int = 100000000 // 1e8 private val REGEX_CURRENCY = Regex("""^[-_*A-Za-z0-9]{1,12}$""") @@ -213,6 +215,30 @@ data class Amount( negative = false, ) + fun addInputDigit(c: Char): Amount? = c.digitToIntOrNull()?.let { digit -> + try { + val value = amountStr.toBigDecimal() + val decimals = spec?.numFractionalInputDigits ?: DEFAULT_INPUT_DECIMALS + fromString( + currency, + // some real math! + ((value * 10.0.toBigDecimal().setScale(decimals)) + + (digit.toBigDecimal().setScale(decimals) + / 10.0.toBigDecimal().pow(decimals))).toString() + ) + } catch (e: AmountParserException) { null } + } + + fun removeInputDigit(): Amount? = try { + val decimals = spec?.numFractionalInputDigits ?: DEFAULT_INPUT_DECIMALS + val value = amountStr.toBigDecimal().setScale(decimals + 1, RoundingMode.FLOOR) + fromString( + currency, + // more math! + (value / "10.0".toBigDecimal()).setScale(decimals, RoundingMode.FLOOR).toString() + ) + } catch (e: AmountParserException) { null } + fun toString( showSymbol: Boolean = true, negative: Boolean = false, @@ -266,7 +292,6 @@ data class Amount( else -> return 1 } } - } @OptIn(ExperimentalSerializationApi::class) diff --git a/wallet/src/main/java/net/taler/wallet/compose/AmountInputFIeld.kt b/wallet/src/main/java/net/taler/wallet/compose/AmountInputFIeld.kt @@ -0,0 +1,186 @@ +/* + * This file is part of GNU Taler + * (C) 2024 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.compose + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.OutlinedTextField +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.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.input.key.utf16CodePoint +import androidx.compose.ui.platform.LocalTextInputService +import androidx.compose.ui.text.InternalTextApi +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.input.BackspaceCommand +import androidx.compose.ui.text.input.CommitTextCommand +import androidx.compose.ui.text.input.DeleteSurroundingTextCommand +import androidx.compose.ui.text.input.EditCommand +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.ImeOptions +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.TextInputService +import androidx.compose.ui.text.input.TextInputSession +import androidx.compose.ui.unit.dp +import net.taler.common.Amount +import net.taler.wallet.deposit.CurrencyDropdown + +@Composable +fun AmountCurrencyField( + modifier: Modifier = Modifier, + amount: Amount, + editableCurrency: Boolean = true, + currencies: List<String>, + onAmountChanged: (amount: Amount) -> Unit, + label: @Composable (() -> Unit)? = null, + supportingText: @Composable (() -> Unit)? = null, + isError: Boolean = false, + readOnly: Boolean = false, +) { + Row(modifier = modifier) { + AmountInputFieldBase( + modifier = Modifier + .weight(2f, true) + .padding(end = 16.dp), + amount = amount, + onAmountChanged = onAmountChanged, + label = label, + isError = isError, + supportingText = supportingText, + readOnly = readOnly, + ) + + CurrencyDropdown( + modifier = Modifier.weight(1f), + currencies = currencies, + onCurrencyChanged = { onAmountChanged(amount.copy(currency = it)) }, + initialCurrency = amount.currency, + readOnly = !editableCurrency, + ) + } +} + +@Composable +private fun AmountInputFieldBase( + amount: Amount, + onAmountChanged: (amount: Amount) -> Unit, + modifier: Modifier, + label: @Composable (() -> Unit)? = null, + supportingText: @Composable (() -> Unit)? = null, + isError: Boolean = false, + readOnly: Boolean = false, +) { + val inputService = LocalTextInputService.current + val interactionSource = remember { MutableInteractionSource() } + val isFocused: Boolean by interactionSource.collectIsFocusedAsState() + val isClicked: Boolean by interactionSource.collectIsPressedAsState() + var session by remember { mutableStateOf<TextInputSession?>(null) } + + val currentOnEnterDigit by rememberUpdatedState { digit: Char -> + amount.addInputDigit(digit)?.let { + onAmountChanged(it) + true + } ?: false + } + + val currentOnRemoveDigit by rememberUpdatedState { + amount.removeInputDigit()?.let { + onAmountChanged(it) + true + } ?: false + } + + LaunchedEffect(isFocused, isClicked) { + if (readOnly) return@LaunchedEffect + if (isFocused || isClicked) { + session = startSession(inputService) { commands -> + commands.forEach { cmd -> + when (cmd) { + is BackspaceCommand -> currentOnRemoveDigit() + is DeleteSurroundingTextCommand -> currentOnRemoveDigit() + is CommitTextCommand -> cmd.text.forEach { currentOnEnterDigit(it) } + } + } + } + } else if (session != null) { + session?.let { inputService?.stopInput(it) } + session = null + } + } + + OutlinedTextField( + value = amount.toString(), + onValueChange = {}, + modifier = modifier.onKeyEvent { + if (it.type == KeyEventType.KeyDown) return@onKeyEvent false + if (it.key == Key.Backspace) { + currentOnRemoveDigit() + } else { + currentOnEnterDigit(it.utf16CodePoint.toChar()) + } + }, + readOnly = true, + textStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace), + label = label, + supportingText = supportingText, + isError = isError, + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.NumberPassword), + singleLine = true, + maxLines = 1, + interactionSource = interactionSource, + ) +} + +@OptIn(InternalTextApi::class) +fun startSession( + textInputService: TextInputService?, + onEditCommand: (List<EditCommand>) -> Unit, +): TextInputSession? = textInputService?.let { service -> + service.startInput( + TextFieldValue(), + imeOptions = ImeOptions.Default.copy( + singleLine = false, + autoCorrect = false, + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.NumberPassword, + imeAction = ImeAction.Done, + ), + onEditCommand = onEditCommand, + onImeActionPerformed = { action -> + if (action == ImeAction.Done) { + service.stopInput() + } + } + ) +} +\ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt b/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt @@ -1,289 +0,0 @@ -/* - * This file is part of GNU Taler - * (C) 2023 Taler Systems S.A. - * - * GNU Taler is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3, or (at your option) any later version. - * - * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -package net.taler.wallet.compose - -import android.os.Build -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.OutlinedTextField -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.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.OffsetMapping -import androidx.compose.ui.text.input.TransformedText -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.unit.dp -import net.taler.common.Amount -import net.taler.common.CurrencySpecification -import net.taler.wallet.deposit.CurrencyDropdown -import net.taler.wallet.getAmount -import java.text.DecimalFormat -import java.text.DecimalFormatSymbols -import kotlin.math.max -import kotlin.math.pow -import kotlin.math.roundToLong - -const val DEFAULT_INPUT_DECIMALS = 2 - -@Composable -fun AmountCurrencyField( - modifier: Modifier = Modifier, - initialAmount: Amount, - initialCurrency: String?, - editableCurrency: Boolean = true, - currencies: List<String>, - onAmountChanged: (amount: Amount) -> Unit, - getCurrencySpec: (currency: String) -> CurrencySpecification?, - label: @Composable (() -> Unit)? = null, - supportingText: @Composable (() -> Unit)? = null, - isError: Boolean = false, - keyboardActions: KeyboardActions = KeyboardActions.Default, - decimalFormatSymbols: DecimalFormatSymbols = DecimalFormat().decimalFormatSymbols, - readOnly: Boolean = false, -) { - var text by remember(initialAmount) { mutableStateOf(initialAmount.amountStr) } - var selectedCurrency by rememberSaveable(initialCurrency) { mutableStateOf(initialCurrency ?: currencies[0]) } - val selectedSpec: CurrencySpecification? = getCurrencySpec(selectedCurrency) - val amount = remember(selectedCurrency, text) { getAmount(selectedCurrency, text) } - - LaunchedEffect(amount) { - amount?.let { onAmountChanged(amount) } - } - - Row(modifier = modifier) { - AmountInputFieldBase( - modifier = Modifier - .weight(2f, true) - .padding(end = 16.dp), - value = text, - onValueChange = { input -> text = input }, - label = label, - numberOfDecimals = selectedSpec - ?.numFractionalInputDigits - ?: DEFAULT_INPUT_DECIMALS, - isError = isError, - supportingText = supportingText, - keyboardActions = keyboardActions, - decimalFormatSymbols = decimalFormatSymbols, - readOnly = readOnly, - ) - - CurrencyDropdown( - modifier = Modifier.weight(1f), - currencies = currencies, - onCurrencyChanged = { selectedCurrency = it }, - initialCurrency = initialCurrency, - readOnly = !editableCurrency, - ) - } -} - -@Composable -fun AmountInputFieldBase( - value: String, - onValueChange: (value: String) -> Unit, - modifier: Modifier = Modifier, - label: @Composable (() -> Unit)? = null, - supportingText: @Composable (() -> Unit)? = null, - isError: Boolean = false, - keyboardActions: KeyboardActions = KeyboardActions.Default, - decimalFormatSymbols: DecimalFormatSymbols = DecimalFormat().decimalFormatSymbols, - numberOfDecimals: Int = DEFAULT_INPUT_DECIMALS, - readOnly: Boolean = false, -) { - var amountInput by remember { mutableStateOf(value) } - - // React to external changes - val amountValue = remember(amountInput, value) { - transformOutput(amountInput).let { - if (value != it) transformInput(value, numberOfDecimals) else amountInput - } - } - - OutlinedTextField( - value = amountValue, - onValueChange = { input -> - if (input.matches("0+".toRegex())) { - amountInput = "0" - onValueChange("") - } else transformOutput(input, numberOfDecimals)?.let { filtered -> - if (Amount.isValidAmountStr(filtered) && !input.contains("-")) { - amountInput = input.trimStart('0') - onValueChange(filtered) - } - } - }, - modifier = modifier, - readOnly = readOnly, - textStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace), - label = label, - supportingText = supportingText, - isError = isError, - visualTransformation = AmountInputVisualTransformation( - symbols = decimalFormatSymbols, - fixedCursorAtTheEnd = true, - numberOfDecimals = numberOfDecimals, - ), - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.NumberPassword), - keyboardActions = keyboardActions, - singleLine = true, - maxLines = 1, - ) -} - -// 500 -> 5.0 -private fun transformOutput( - input: String, - numberOfDecimals: Int = 2, -) = if (input.isEmpty()) "0" else { - input.toLongOrNull()?.let { it / 10.0.pow(numberOfDecimals) }?.toBigDecimal()?.toPlainString() -} - -// 5.0 -> 500 -private fun transformInput( - output: String, - numberOfDecimals: Int = 2, -) = if (output.isEmpty()) "0" else { - (output.toDouble() * 10.0.pow(numberOfDecimals)).roundToLong().toString() -} - -// Source: https://github.com/banmarkovic/CurrencyAmountInput - -private class AmountInputVisualTransformation( - private val symbols: DecimalFormatSymbols, - private val fixedCursorAtTheEnd: Boolean = true, - private val numberOfDecimals: Int = 2, -): VisualTransformation { - - override fun filter(text: AnnotatedString): TransformedText { - val thousandsSeparator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - symbols.monetaryGroupingSeparator - } else { - symbols.groupingSeparator - } - val decimalSeparator = symbols.monetaryDecimalSeparator - val zero = symbols.zeroDigit - - val inputText = text.text - - val intPart = inputText - .dropLast(numberOfDecimals) - .reversed() - .chunked(3) - .joinToString(thousandsSeparator.toString()) - .reversed() - .ifEmpty { - zero.toString() - } - - val fractionPart = inputText.takeLast(numberOfDecimals).let { - if (it.length != numberOfDecimals) { - List(numberOfDecimals - it.length) { - zero - }.joinToString("") + it - } else { - it - } - } - - // Hide trailing decimal separator if decimals are 0 - val formattedNumber = if (numberOfDecimals > 0) { - intPart + decimalSeparator + fractionPart - } else { - intPart - } - - val newText = AnnotatedString( - text = formattedNumber, - spanStyles = text.spanStyles, - paragraphStyles = text.paragraphStyles - ) - - val offsetMapping = if (fixedCursorAtTheEnd) { - FixedCursorOffsetMapping( - contentLength = inputText.length, - formattedContentLength = formattedNumber.length - ) - } else { - MovableCursorOffsetMapping( - unmaskedText = text.toString(), - maskedText = newText.toString(), - decimalDigits = numberOfDecimals - ) - } - - return TransformedText(newText, offsetMapping) - } - - private class FixedCursorOffsetMapping( - private val contentLength: Int, - private val formattedContentLength: Int, - ) : OffsetMapping { - override fun originalToTransformed(offset: Int): Int = formattedContentLength - override fun transformedToOriginal(offset: Int): Int = contentLength - } - - private class MovableCursorOffsetMapping( - private val unmaskedText: String, - private val maskedText: String, - private val decimalDigits: Int - ) : OffsetMapping { - override fun originalToTransformed(offset: Int): Int = - when { - unmaskedText.length <= decimalDigits -> { - maskedText.length - (unmaskedText.length - offset) - } - else -> { - offset + offsetMaskCount(offset, maskedText) - } - } - - override fun transformedToOriginal(offset: Int): Int = - when { - unmaskedText.length <= decimalDigits -> { - max(unmaskedText.length - (maskedText.length - offset), 0) - } - else -> { - offset - maskedText.take(offset).count { !it.isDigit() } - } - } - - private fun offsetMaskCount(offset: Int, maskedText: String): Int { - var maskOffsetCount = 0 - var dataCount = 0 - for (maskChar in maskedText) { - if (!maskChar.isDigit()) { - maskOffsetCount++ - } else if (++dataCount > offset) { - break - } - } - return maskOffsetCount - } - } -} -\ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt @@ -84,6 +84,7 @@ fun MakeDepositComposable( // TODO: use scopeInfo instead of currency! var checkResult by remember { mutableStateOf<CheckDepositResult>(CheckDepositResult.None) } var amount by remember { mutableStateOf(Amount.zero(defaultCurrency ?: currencies[0])) } + val currencySpec = remember (amount) { getCurrencySpec(amount.currency) } var depositWireTypes by remember { mutableStateOf<GetDepositWireTypesForCurrencyResponse?>(null) } val supportedWireTypes = remember(depositWireTypes) { depositWireTypes?.wireTypes ?: emptyList() } @@ -162,12 +163,10 @@ fun MakeDepositComposable( start = 16.dp, end = 16.dp, ).fillMaxWidth(), - initialAmount = amount, - initialCurrency = defaultCurrency, + amount = amount.withSpec(currencySpec), onAmountChanged = { amount = it }, editableCurrency = true, currencies = currencies, - getCurrencySpec = getCurrencySpec, isError = checkResult !is CheckDepositResult.Success, label = { Text(stringResource(R.string.amount_deposit)) }, supportingText = { diff --git a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt @@ -133,18 +133,17 @@ private fun PayToComposable( verticalArrangement = Arrangement.spacedBy(16.dp), ) { var amount by remember { mutableStateOf(Amount.zero(currencies[0])) } + val currencySpec = remember(amount.currency) { getCurrencySpec(amount.currency) } var amountError by rememberSaveable { mutableStateOf("") } AmountCurrencyField( modifier = Modifier .padding(horizontal = 16.dp) .fillMaxWidth(), - initialAmount = amount, - initialCurrency = amount.currency, + amount = amount.withSpec(currencySpec), currencies = currencies, readOnly = false, onAmountChanged = { amount = it }, - getCurrencySpec = getCurrencySpec, label = { Text(stringResource(R.string.amount_send)) }, isError = amountError.isNotBlank(), supportingText = { diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt @@ -29,7 +29,6 @@ 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 @@ -47,7 +46,6 @@ import net.taler.wallet.R import net.taler.wallet.compose.AmountCurrencyField import net.taler.wallet.compose.TalerSurface -@OptIn(ExperimentalComposeUiApi::class) @Composable fun PayTemplateOrderComposable( usableCurrencies: List<String>, // non-empty intersection between the stored currencies and the ones supported by the merchant @@ -69,6 +67,9 @@ fun PayTemplateOrderComposable( val currency = defaultCurrency ?: usableCurrencies[0] mutableStateOf(defaultAmount?.withCurrency(currency) ?: Amount.zero(currency)) } + val currencySpec = remember(amount.currency) { + getCurrencySpec(amount.currency) + } Column(horizontalAlignment = End) { OutlinedTextField( @@ -93,13 +94,11 @@ fun PayTemplateOrderComposable( modifier = Modifier .padding(16.dp) .fillMaxWidth(), - initialAmount = amount, - initialCurrency = amount.currency, + amount = amount.withSpec(currencySpec), currencies = usableCurrencies, editableCurrency = !templateDetails.isCurrencyEditable(usableCurrencies), readOnly = !templateDetails.isAmountEditable(), onAmountChanged = { amount = it }, - getCurrencySpec = getCurrencySpec, label = { Text(stringResource(R.string.amount_send)) }, ) diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt @@ -119,7 +119,7 @@ fun OutgoingPullIntroComposable( ) { var subject by rememberSaveable { mutableStateOf("") } var amount by remember { mutableStateOf(Amount.zero(defaultCurrency ?: currencies[0])) } - val selectedSpec = remember(amount) { getCurrencySpec(amount.currency) } + val selectedSpec = remember(amount.currency) { getCurrencySpec(amount.currency) } var checkResult by remember { mutableStateOf<CheckPeerPullCreditResult?>(null) } amount.useDebounce { @@ -134,12 +134,10 @@ fun OutgoingPullIntroComposable( modifier = Modifier .padding(bottom = 16.dp) .fillMaxWidth(), - initialAmount = amount, - initialCurrency = amount.currency, + amount = amount.withSpec(selectedSpec), currencies = currencies, readOnly = false, onAmountChanged = { amount = it }, - getCurrencySpec = getCurrencySpec, isError = amount.isZero(), label = { Text(stringResource(R.string.amount_receive)) }, ) diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt @@ -96,7 +96,7 @@ fun OutgoingPushIntroComposable( horizontalAlignment = CenterHorizontally, ) { var amount by remember { mutableStateOf(Amount.zero(defaultCurrency ?: currencies[0])) } - val selectedSpec = remember(amount) { getCurrencySpec(amount.currency) } + val selectedSpec = remember(amount.currency) { getCurrencySpec(amount.currency) } var feeResult by remember { mutableStateOf<CheckFeeResult>(None) } amount.useDebounce { @@ -109,12 +109,10 @@ fun OutgoingPushIntroComposable( AmountCurrencyField( modifier = Modifier.fillMaxWidth(), - initialAmount = amount, - initialCurrency = amount.currency, + amount = amount.withSpec(selectedSpec), currencies = currencies, readOnly = false, onAmountChanged = { amount = it }, - getCurrencySpec = getCurrencySpec, label = { Text(stringResource(R.string.amount_send)) }, isError = amount.isZero() || feeResult is InsufficientBalance, supportingText = { diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt @@ -104,6 +104,14 @@ class PromptWithdrawFragment: Fragment() { ?: currencies.firstOrNull() ?: error("no default currency specified") + val currencySpec = remember(exchange?.scopeInfo) { + exchange?.scopeInfo?.let { scopeInfo -> + balanceManager.getSpecForScopeInfo(scopeInfo) + } ?: status.currency?.let { + balanceManager.getSpecForCurrency(it) + } + } + TalerSurface { status.let { s -> if (s.error != null) { @@ -115,21 +123,13 @@ class PromptWithdrawFragment: Fragment() { Loading -> LoadingScreen() None, InfoReceived, TosReviewRequired, Updating -> { - val spec = remember(s) { - exchange?.scopeInfo?.let { scopeInfo -> - balanceManager.getSpecForScopeInfo(scopeInfo) - } ?: s.currency?.let { - balanceManager.getSpecForCurrency(it) - } - } - // TODO: use scopeInfo instead of currency! WithdrawalShowInfo( status = s, defaultCurrency = defaultCurrency, editableCurrency = editableCurrency, currencies = currencies, - spec = spec, + spec = currencySpec, onSelectExchange = { selectExchange() }, diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawalShowInfo.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawalShowInfo.kt @@ -113,12 +113,10 @@ fun WithdrawalShowInfo( modifier = Modifier .padding(16.dp) .fillMaxWidth(), - initialAmount = selectedAmount.withSpec(spec), - initialCurrency = defaultCurrency, + amount = selectedAmount.withSpec(spec), currencies = currencies, editableCurrency = editableCurrency, onAmountChanged = { selectedAmount = it }, - getCurrencySpec = { spec }, label = { Text(stringResource(R.string.amount_withdraw)) }, isError = selectedAmount.isZero() || maxAmount != null && selectedAmount > maxAmount, supportingText = {