From ed7f77234259113007af8cabeed32a560ccd4f32 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Wed, 13 Sep 2023 16:57:30 -0600 Subject: [wallet] Refactor amount input into single composable --- .../java/net/taler/wallet/ReceiveFundsFragment.kt | 25 ++---- .../java/net/taler/wallet/SendFundsFragment.kt | 33 ++------ .../net/taler/wallet/compose/AmountInputField.kt | 89 ++++++++++++++++++++++ .../net/taler/wallet/deposit/PayToUriFragment.kt | 21 ++--- .../taler/wallet/payment/PayTemplateComposable.kt | 83 +++++++++----------- .../taler/wallet/payment/PayTemplateFragment.kt | 2 +- 6 files changed, 150 insertions(+), 103 deletions(-) create mode 100644 wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt (limited to 'wallet') diff --git a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt index dbff6ae..1511128 100644 --- a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt @@ -29,12 +29,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -46,7 +44,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType.Companion.Decimal import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf @@ -55,7 +52,7 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import net.taler.common.Amount -import net.taler.common.Amount.Companion.isValidAmountStr +import net.taler.wallet.compose.AmountInputField import net.taler.wallet.compose.TalerSurface import net.taler.wallet.exchanges.ExchangeItem @@ -134,28 +131,20 @@ private fun ReceiveFundsIntro( modifier = Modifier .padding(16.dp), ) { - OutlinedTextField( + AmountInputField( modifier = Modifier .weight(1f) .padding(end = 16.dp), value = text, - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = Decimal), onValueChange = { input -> isError = false - val filtered = input.filter { it.isDigit() || it == '.' } - if (filtered.endsWith('.') || isValidAmountStr(filtered)) text = filtered + text = input + }, + label = { Text(stringResource(R.string.receive_amount)) }, + supportingText = { + if (isError) Text(stringResource(R.string.receive_amount_invalid)) }, isError = isError, - label = { - if (isError) { - Text( - stringResource(R.string.receive_amount_invalid), - color = MaterialTheme.colorScheme.error, - ) - } else { - Text(stringResource(R.string.receive_amount)) - } - } ) Text( modifier = Modifier, diff --git a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt index c2680d5..2e5eb52 100644 --- a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt @@ -27,12 +27,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -44,7 +42,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf @@ -52,7 +49,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import net.taler.common.Amount -import net.taler.common.Amount.Companion.isValidAmountStr +import net.taler.wallet.compose.AmountInputField import net.taler.wallet.compose.TalerSurface class SendFundsFragment : Fragment() { @@ -116,34 +113,20 @@ private fun SendFundsIntro( modifier = Modifier .padding(16.dp), ) { - OutlinedTextField( + AmountInputField( modifier = Modifier .weight(1f) .padding(end = 16.dp), value = text, - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Decimal), onValueChange = { input -> isError = false - insufficientBalance = false - val filtered = input.filter { it.isDigit() || it == '.' } - if (filtered.endsWith('.') || isValidAmountStr(filtered)) text = filtered + text = input }, - isError = isError || insufficientBalance, - label = { - if (isError) { - Text( - stringResource(R.string.receive_amount_invalid), - color = MaterialTheme.colorScheme.error, - ) - } else if (insufficientBalance) { - Text( - stringResource(R.string.payment_balance_insufficient), - color = MaterialTheme.colorScheme.error, - ) - } else { - Text(stringResource(R.string.send_amount)) - } - } + label = { Text(stringResource(R.string.send_amount)) }, + supportingText = { + if (isError) Text(stringResource(R.string.receive_amount_invalid)) + }, + isError = isError, ) Text( modifier = Modifier, diff --git a/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt b/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt new file mode 100644 index 0000000..79a01c8 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt @@ -0,0 +1,89 @@ +/* + * 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 + */ + +package net.taler.wallet.compose + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldColors +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.VisualTransformation +import net.taler.common.Amount + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AmountInputField( + value: String, + onValueChange: (value: String) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + label: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + supportingText: @Composable (() -> Unit)? = null, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + shape: Shape = TextFieldDefaults.outlinedShape, + colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() +) { + OutlinedTextField( + value = if (value == "0" || value.endsWith(".0")) value.trimEnd('0') else value, + onValueChange = { input -> + if (input.isNotBlank()) { + val filtered = input.filter { it.isDigit() || it == '.' }.let { + if (it == "" || it.endsWith(".")) "${it}0" else it + } + if (Amount.isValidAmountStr(filtered)) { + onValueChange(filtered) + } + } else onValueChange("0") + }, + modifier = modifier, + enabled = enabled, + readOnly = readOnly, + textStyle = textStyle, + label = label, + placeholder = { Text("0") }, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + supportingText = supportingText, + isError = isError, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions.copy(keyboardType = KeyboardType.Decimal), + keyboardActions = keyboardActions, + singleLine = true, + maxLines = 1, + interactionSource = interactionSource, + shape = shape, + colors = colors, + ) +} \ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt index 2584763..d4c9f6c 100644 --- a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt @@ -30,7 +30,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.DropdownMenu @@ -56,7 +55,6 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf @@ -67,6 +65,7 @@ import net.taler.common.Amount import net.taler.wallet.AmountResult import net.taler.wallet.MainViewModel import net.taler.wallet.R +import net.taler.wallet.compose.AmountInputField import net.taler.wallet.compose.TalerSurface class PayToUriFragment : Fragment() { @@ -136,24 +135,18 @@ private fun PayToComposable( var amountError by rememberSaveable { mutableStateOf("") } var currency by rememberSaveable { mutableStateOf(currencies[0]) } val focusRequester = remember { FocusRequester() } - OutlinedTextField( - modifier = Modifier - .focusRequester(focusRequester), + AmountInputField( + modifier = Modifier.focusRequester(focusRequester), value = amountText, onValueChange = { input -> amountError = "" amountText = input }, - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Decimal), - singleLine = true, + label = { Text(stringResource(R.string.send_amount)) }, + supportingText = { + if (amountError.isNotBlank()) Text(amountError) + }, isError = amountError.isNotBlank(), - label = { - if (amountError.isBlank()) { - Text(stringResource(R.string.send_amount)) - } else { - Text(amountError, color = MaterialTheme.colorScheme.error) - } - } ) CurrencyDropdown( modifier = Modifier diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt index 8bbff03..59a088d 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt @@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api @@ -38,12 +37,12 @@ import androidx.compose.ui.Alignment.Companion.Center import androidx.compose.ui.Alignment.Companion.End import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import net.taler.common.Amount import net.taler.wallet.AmountResult import net.taler.wallet.R +import net.taler.wallet.compose.AmountInputField import net.taler.wallet.compose.TalerSurface import net.taler.wallet.deposit.CurrencyDropdown @@ -104,11 +103,14 @@ fun PayTemplateDefault( onError: (msgRes: Int) -> Unit, onSubmit: (summary: String?, amount: Amount?) -> Unit, ) { + val amountDefault = amountStatus as? AmountFieldStatus.Default + var localSummary by remember { mutableStateOf(summary) } var localAmount by remember { mutableStateOf( - (amountStatus as? AmountFieldStatus.Default)?.let { s -> - Amount.fromString(s.currency ?: currencies[0], s.amountStr ?: "0") - } + amountDefault?.let { s -> s.amountStr ?: "0" } + ) } + var localCurrency by remember { mutableStateOf( + amountDefault?.let { s -> s.currency ?: currencies[0] } ) } Column(horizontalAlignment = End) { @@ -126,15 +128,21 @@ fun PayTemplateDefault( } localAmount?.let { amount -> - AmountField( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - amount = amount, - currencies = currencies, - fixedCurrency = (amountStatus as? AmountFieldStatus.Default)?.currency != null, - onAmountChosen = { localAmount = it }, - ) + localCurrency?.let { currency -> + AmountField( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + amount = amount, + currency = currency, + currencies = currencies, + fixedCurrency = (amountStatus as? AmountFieldStatus.Default)?.currency != null, + onAmountChosen = { a, c -> + localAmount = a + localCurrency = c + }, + ) + } } Button( @@ -142,14 +150,12 @@ fun PayTemplateDefault( enabled = localSummary == null || localSummary!!.isNotBlank(), onClick = { localAmount?.let { amount -> - val result = onCreateAmount( - amount.amountStr, - amount.currency, - ) - when (result) { - AmountResult.InsufficientBalance -> onError(R.string.payment_balance_insufficient) - AmountResult.InvalidAmount -> onError(R.string.receive_amount_invalid) - else -> onSubmit(summary, amount) + localCurrency?.let { currency -> + when (val res = onCreateAmount(amount, currency)) { + is AmountResult.InsufficientBalance -> onError(R.string.payment_balance_insufficient) + is AmountResult.InvalidAmount -> onError(R.string.receive_amount_invalid) + is AmountResult.Success -> onSubmit(summary, res.amount) + } } } }, @@ -184,43 +190,30 @@ fun PayTemplateLoading() { } @Composable -@OptIn(ExperimentalMaterial3Api::class) -// TODO can we combine this with existing amount composables, e.g. whats in PayToComposable? private fun AmountField( modifier: Modifier = Modifier, currencies: List, fixedCurrency: Boolean, - amount: Amount, - onAmountChosen: (Amount) -> Unit, + amount: String, + currency: String, + onAmountChosen: (amount: String, currency: String) -> Unit, ) { Row( modifier = modifier, ) { - val amountText = if (amount.value == 0L) "" else amount.value.toString() - OutlinedTextField( + AmountInputField( modifier = Modifier .padding(end = 16.dp) .weight(1f), - value = amountText, - placeholder = { Text("0") }, - onValueChange = { input -> - if (input.isNotBlank()) { - onAmountChosen(Amount.fromString(amount.currency, input)) - } else { - onAmountChosen(Amount.zero(amount.currency)) - } - }, - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Decimal), - singleLine = true, - label = { Text(stringResource(R.string.send_amount)) }, + value = amount, + onValueChange = { onAmountChosen(it, currency) }, + label = { Text(stringResource(R.string.send_amount)) } ) CurrencyDropdown( modifier = Modifier.weight(1f), - initialCurrency = amount.currency, + initialCurrency = currency, currencies = currencies, - onCurrencyChanged = { c -> - onAmountChosen(Amount.fromString(c, amount.amountStr)) - }, + onCurrencyChanged = { onAmountChosen(amount, it) }, readOnly = fixedCurrency, ) } @@ -232,7 +225,7 @@ fun PayTemplateComposablePreview() { TalerSurface { PayTemplateComposable( summary = "Donation", - amountStatus = AmountFieldStatus.Default("ARS", "20"), + amountStatus = AmountFieldStatus.Default("20", "ARS"), currencies = listOf("KUDOS", "ARS"), // TODO create previews for other states payStatus = PayStatus.None, diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt index c4b8132..01160ec 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt @@ -60,7 +60,7 @@ class PayTemplateFragment : Fragment() { when (parts.size) { 0 -> AmountFieldStatus.Default() 1 -> AmountFieldStatus.Default(currency = parts[0]) - 2 -> AmountFieldStatus.Default(parts[0], parts[1]) + 2 -> AmountFieldStatus.Default(parts[1], parts[0]) else -> AmountFieldStatus.Invalid } } else AmountFieldStatus.FixedAmount -- cgit v1.2.3