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:
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 = {