diff options
Diffstat (limited to 'wallet/src/main/java')
37 files changed, 423 insertions, 207 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/HandleUriFragment.kt b/wallet/src/main/java/net/taler/wallet/HandleUriFragment.kt index 6b8db78..58adeee 100644 --- a/wallet/src/main/java/net/taler/wallet/HandleUriFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/HandleUriFragment.kt @@ -228,6 +228,7 @@ class HandleUriFragment: Fragment() { withContext(Dispatchers.Main) { model.showProgressBar.value = false val args = Bundle().apply { + putBoolean("hideScanQr", true) if (response.amount != null) { putString("amount", response.amount.toJSONString()) } diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt index 00fd2d3..d15340a 100644 --- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt +++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt @@ -128,6 +128,7 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, model.networkManager.networkStatus.observe(this) { online -> ui.content.offlineBanner.visibility = if (online) GONE else VISIBLE + model.hintNetworkAvailability(online) } model.devMode.observe(this) { diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt index 82eb8d7..c7318de 100644 --- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt +++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt @@ -231,6 +231,14 @@ class MainViewModel( } } + fun hintNetworkAvailability(isAvailable: Boolean) { + viewModelScope.launch { + api.request<Unit>("hintNetworkAvailability") { + put("isNetworkAvailable", isAvailable) + } + } + } + fun runIntegrationTest() { viewModelScope.launch { api.request<Unit>("runIntegrationTestV2") { diff --git a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt index 2accaaf..25d35ec 100644 --- a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt @@ -144,9 +144,9 @@ private fun ReceiveFundsIntro( isError = false text = input }, - label = { Text(stringResource(R.string.receive_amount)) }, + label = { Text(stringResource(R.string.amount_receive)) }, supportingText = { - if (isError) Text(stringResource(R.string.receive_amount_invalid)) + if (isError) Text(stringResource(R.string.amount_invalid)) }, isError = isError, numberOfDecimals = spec?.numFractionalInputDigits ?: DEFAULT_INPUT_DECIMALS, diff --git a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt index 2581979..ca72a64 100644 --- a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt @@ -126,9 +126,9 @@ private fun SendFundsIntro( insufficientBalance = false text = input }, - label = { Text(stringResource(R.string.send_amount)) }, + label = { Text(stringResource(R.string.amount_send)) }, supportingText = { - if (isError) Text(stringResource(R.string.receive_amount_invalid)) + if (isError) Text(stringResource(R.string.amount_invalid)) else if (insufficientBalance) { Text(stringResource(R.string.payment_balance_insufficient)) } diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt index f40def4..aabef4b 100644 --- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt +++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt @@ -61,8 +61,7 @@ class BalanceAdapter(private val listener: BalanceClickListener) : Adapter<Balan private val amountView: TextView = v.findViewById(R.id.balanceAmountView) private val scopeView: TextView = v.findViewById(R.id.scopeView) private val balanceInboundAmount: TextView = v.findViewById(R.id.balanceInboundAmount) - private val balanceInboundLabel: TextView = v.findViewById(R.id.balanceInboundLabel) - private val pendingView: TextView = v.findViewById(R.id.pendingView) + private val balanceOutboundAmount: TextView = v.findViewById(R.id.balanceOutboundAmount) fun bind(item: BalanceItem) { v.setOnClickListener { listener.onBalanceClick(item.scopeInfo) } @@ -71,11 +70,17 @@ class BalanceAdapter(private val listener: BalanceClickListener) : Adapter<Balan val amountIncoming = item.pendingIncoming if (amountIncoming.isZero()) { balanceInboundAmount.visibility = GONE - balanceInboundLabel.visibility = GONE } else { balanceInboundAmount.visibility = VISIBLE - balanceInboundLabel.visibility = VISIBLE - balanceInboundAmount.text = v.context.getString(R.string.amount_positive, amountIncoming.toString(showSymbol = false)) + balanceInboundAmount.text = v.context.getString(R.string.balances_inbound_amount, amountIncoming.toString(showSymbol = false)) + } + + val amountOutgoing = item.pendingOutgoing + if (amountOutgoing.isZero()) { + balanceOutboundAmount.visibility = GONE + } else { + balanceOutboundAmount.visibility = VISIBLE + balanceOutboundAmount.text = v.context.getString(R.string.balances_outbound_amount, amountOutgoing.toString(showSymbol = false)) } val scopeInfo = item.scopeInfo @@ -90,8 +95,6 @@ class BalanceAdapter(private val listener: BalanceClickListener) : Adapter<Balan VISIBLE } } - - pendingView.visibility = if (item.hasPending) VISIBLE else GONE } } diff --git a/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt b/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt index a524d1b..d2a877b 100644 --- a/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt +++ b/wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt @@ -53,6 +53,7 @@ fun AmountInputField( keyboardActions: KeyboardActions = KeyboardActions.Default, decimalFormatSymbols: DecimalFormatSymbols = DecimalFormat().decimalFormatSymbols, numberOfDecimals: Int = DEFAULT_INPUT_DECIMALS, + readOnly: Boolean = false, ) { var amountInput by remember { mutableStateOf(value) } @@ -77,6 +78,7 @@ fun AmountInputField( } }, modifier = modifier, + readOnly = readOnly, textStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace), label = label, supportingText = supportingText, diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt index 3fa0d98..d356051 100644 --- a/wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt @@ -91,7 +91,7 @@ fun MakeBitcoinDepositComposable( } val amountTitle = if (state.effectiveDepositAmount == null) { R.string.amount_chosen - } else R.string.send_deposit_amount_effective + } else R.string.amount_effective TransactionAmountComposable( label = stringResource(id = amountTitle), amount = state.effectiveDepositAmount ?: amount, @@ -104,14 +104,16 @@ fun MakeBitcoinDepositComposable( ) { val totalAmount = state.totalDepositCost ?: amount val effectiveAmount = state.effectiveDepositAmount ?: Amount.zero(amount.currency) - val fee = totalAmount - effectiveAmount + if (totalAmount > effectiveAmount) { + val fee = totalAmount - effectiveAmount + TransactionAmountComposable( + label = stringResource(id = R.string.amount_fee), + amount = fee, + amountType = AmountType.Negative, + ) + } TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), - amount = fee, - amountType = AmountType.Negative, - ) - TransactionAmountComposable( - label = stringResource(id = R.string.send_amount), + label = stringResource(id = R.string.amount_send), amount = totalAmount, amountType = AmountType.Positive, ) diff --git a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt index 9333ce1..2f9fd88 100644 --- a/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt @@ -134,16 +134,18 @@ fun MakeDepositComposable( ) { val totalAmount = state.totalDepositCost ?: amount val effectiveAmount = state.effectiveDepositAmount ?: Amount.zero(amount.currency) - val fee = totalAmount - effectiveAmount + if (totalAmount > effectiveAmount) { + val fee = totalAmount - effectiveAmount - TransactionAmountComposable( - label = stringResource(R.string.withdraw_fees), - amount = fee.withSpec(amount.spec), - amountType = if (fee.isZero()) Positive else Negative, - ) + TransactionAmountComposable( + label = stringResource(R.string.amount_fee), + amount = fee.withSpec(amount.spec), + amountType = Negative, + ) + } TransactionAmountComposable( - label = stringResource(R.string.send_amount), + label = stringResource(R.string.amount_send), amount = effectiveAmount.withSpec(amount.spec), amountType = Positive, ) 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 8961016..0dd3abd 100644 --- a/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt @@ -140,7 +140,7 @@ private fun PayToComposable( amountError = "" amountText = input }, - label = { Text(stringResource(R.string.send_amount)) }, + label = { Text(stringResource(R.string.amount_send)) }, supportingText = { if (amountError.isNotBlank()) Text(amountError) }, @@ -158,7 +158,7 @@ private fun PayToComposable( } val focusManager = LocalFocusManager.current - val errorStrInvalidAmount = stringResource(id = R.string.receive_amount_invalid) + val errorStrInvalidAmount = stringResource(id = R.string.amount_invalid) val errorStrInsufficientBalance = stringResource(id = R.string.payment_balance_insufficient) Button( modifier = Modifier.padding(16.dp), diff --git a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt index 817dfac..11264a1 100644 --- a/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt @@ -77,10 +77,10 @@ fun TransactionDepositComposable( amountType = AmountType.Neutral, ) - val fee = t.amountEffective - t.amountRaw - if (!fee.isZero()) { + if (t.amountEffective > t.amountRaw) { + val fee = t.amountEffective - t.amountRaw TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(spec), amountType = AmountType.Negative, ) 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 ffa4875..d744183 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt @@ -34,42 +34,37 @@ import net.taler.wallet.R import net.taler.wallet.compose.LoadingScreen import net.taler.wallet.compose.TalerSurface -sealed class AmountFieldStatus { - object FixedAmount : AmountFieldStatus() - class Default( - val amountStr: String? = null, - val currency: String? = null, - ) : AmountFieldStatus() - - object Invalid : AmountFieldStatus() -} - @Composable fun PayTemplateComposable( - defaultSummary: String?, - amountStatus: AmountFieldStatus, currencies: List<String>, payStatus: PayStatus, onCreateAmount: (String, String) -> AmountResult, - onSubmit: (summary: String?, amount: Amount?) -> Unit, + onSubmit: (params: TemplateParams) -> Unit, onError: (resId: Int) -> Unit, ) { // If wallet is empty, there's no way the user can pay something - if (amountStatus is AmountFieldStatus.Invalid) { - PayTemplateError(stringResource(R.string.receive_amount_invalid)) - } else if (currencies.isEmpty()) { + if (currencies.isEmpty()) { PayTemplateError(stringResource(R.string.payment_balance_insufficient)) } else when (val p = payStatus) { - is PayStatus.None -> PayTemplateOrderComposable( - currencies = currencies, - defaultSummary = defaultSummary, - amountStatus = amountStatus, - 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.Loading -> PayTemplateLoading() + is PayStatus.None, is PayStatus.Loading -> PayTemplateLoading() is PayStatus.AlreadyPaid -> PayTemplateError(stringResource(R.string.payment_already_paid)) is PayStatus.InsufficientBalance -> PayTemplateError(stringResource(R.string.payment_balance_insufficient)) is PayStatus.Pending -> { @@ -109,14 +104,12 @@ fun PayTemplateLoading() { fun PayTemplateLoadingPreview() { TalerSurface { PayTemplateComposable( - defaultSummary = "Donation", - amountStatus = AmountFieldStatus.Default("20", "ARS"), payStatus = PayStatus.Loading, currencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, - onSubmit = { _, _ -> }, + onSubmit = { _ -> }, onError = { _ -> }, ) } @@ -127,8 +120,6 @@ fun PayTemplateLoadingPreview() { fun PayTemplateInsufficientBalancePreview() { TalerSurface { PayTemplateComposable( - defaultSummary = "Donation", - amountStatus = AmountFieldStatus.Default("20", "ARS"), payStatus = PayStatus.InsufficientBalance( ContractTerms( "test", @@ -140,7 +131,7 @@ fun PayTemplateInsufficientBalancePreview() { onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, - onSubmit = { _, _ -> }, + onSubmit = { _ -> }, onError = { _ -> }, ) } @@ -151,14 +142,12 @@ fun PayTemplateInsufficientBalancePreview() { fun PayTemplateAlreadyPaidPreview() { TalerSurface { PayTemplateComposable( - defaultSummary = "Donation", - amountStatus = AmountFieldStatus.Default("20", "ARS"), payStatus = PayStatus.AlreadyPaid(transactionId = "transactionId"), currencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, - onSubmit = { _, _ -> }, + onSubmit = { _ -> }, onError = { _ -> }, ) } @@ -170,14 +159,12 @@ fun PayTemplateAlreadyPaidPreview() { fun PayTemplateNoCurrenciesPreview() { TalerSurface { PayTemplateComposable( - defaultSummary = "Donation", - amountStatus = AmountFieldStatus.Default("20", "ARS"), payStatus = PayStatus.None, currencies = emptyList(), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, - onSubmit = { _, _ -> }, + onSubmit = { _ -> }, onError = { _ -> }, ) } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateDetails.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateDetails.kt new file mode 100644 index 0000000..4c3f0a5 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateDetails.kt @@ -0,0 +1,126 @@ +/* + * 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.payment + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import net.taler.common.Amount +import net.taler.common.RelativeTime + +@Serializable +data class TemplateContractDetails( + /** + * Human-readable summary for the template. + */ + val summary: String? = null, + + /** + * Required currency for payments to the template. The user may specify + * any amount, but it must be in this currency. This parameter is + * optional and should not be present if "amount" is given. + */ + val currency: String? = null, + + /** + * The price is imposed by the merchant and cannot be changed by the + * customer. This parameter is optional. + */ + val amount: Amount? = null, + + /** + * Minimum age buyer must have (in years). Default is 0. + */ + @SerialName("minimum_age") + val minimumAge: Int, + + /** + * The time the customer need to pay before his order will be deleted. It + * is deleted if the customer did not pay and if the duration is over. + */ + @SerialName("pay_duration") + val payDuration: RelativeTime, +) + +@Serializable +data class TemplateContractDetailsDefaults( + val summary: String? = null, + val currency: String? = null, + val amount: Amount? = null, + @SerialName("minimum_age") + val minimumAge: Int? = null, +) + +@Serializable +class WalletTemplateDetails( + /** + * Hard-coded information about the contract terms for this template. + */ + @SerialName("template_contract") + val templateContract: TemplateContractDetails, + + /** + * Key-value pairs matching a subset of the fields from template_contract + * that are user-editable defaults for this template. + */ + @SerialName("editable_defaults") + val editableDefaults: TemplateContractDetailsDefaults? = null, + + /** + * Required currency for payments. Useful if no amount is specified in + * the template_contract but the user should be required to pay in a + * particular currency anyway. Merchant backends may reject requests if + * the template_contract or editable_defaults do specify an amount in a + * different currency. This parameter is optional. + */ + @SerialName("required_currency") + val requiredCurrency: String? = null, +) { + val defaultSummary get() = editableDefaults?.summary + ?: templateContract.summary + + val defaultAmount get() = editableDefaults?.amount + ?: templateContract.amount + + val defaultCurrency get() = requiredCurrency + ?: editableDefaults?.currency + ?: templateContract.currency + + fun isSummaryEditable() = templateContract.summary == null + + fun isAmountEditable() = templateContract.amount == null + + fun isCurrencyEditable(usableCurrencies: List<String>) = isAmountEditable() + && requiredCurrency == null + && templateContract.currency == null + && usableCurrencies.size > 1 + + fun isTemplateEditable(usableCurrencies: List<String>) = isSummaryEditable() + || isAmountEditable() + || isCurrencyEditable(usableCurrencies) + + // NOTE: it is important to nullify non-editable values! + fun toTemplateParams() = TemplateParams( + amount = if(isAmountEditable()) templateContract.amount else null, + summary = if(isSummaryEditable()) templateContract.summary else null, + ) +} + +@Serializable +data class TemplateParams( + val amount: Amount? = null, + val summary: String? = null, +)
\ No newline at end of file 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 4eb2c11..51c0bc0 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt @@ -26,7 +26,6 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.asFlow import androidx.navigation.fragment.findNavController -import net.taler.common.Amount import net.taler.common.showError import net.taler.wallet.MainViewModel import net.taler.wallet.R @@ -39,6 +38,7 @@ class PayTemplateFragment : Fragment() { private val model: MainViewModel by activityViewModels() private lateinit var uriString: String private lateinit var uri: Uri + private val currencies by lazy { model.getCurrencies() } override fun onCreateView( inflater: LayoutInflater, @@ -48,10 +48,6 @@ class PayTemplateFragment : Fragment() { uriString = arguments?.getString("uri") ?: error("no amount passed") uri = Uri.parse(uriString) - val defaultSummary = uri.getQueryParameter("summary") - val defaultAmount = uri.getQueryParameter("amount") - val amountFieldStatus = getAmountFieldStatus(defaultAmount) - val payStatusFlow = model.paymentManager.payStatus.asFlow() return ComposeView(requireContext()).apply { @@ -59,9 +55,7 @@ class PayTemplateFragment : Fragment() { val payStatus = payStatusFlow.collectAsStateLifecycleAware(initial = PayStatus.None) TalerSurface { PayTemplateComposable( - currencies = model.getCurrencies(), - defaultSummary = defaultSummary, - amountStatus = amountFieldStatus, + currencies = currencies, payStatus = payStatus.value, onCreateAmount = model::createAmount, onSubmit = this@PayTemplateFragment::createOrder, @@ -74,9 +68,7 @@ class PayTemplateFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if (uri.queryParameterNames?.isEmpty() == true) { - createOrder(null, null) - } + checkTemplate() model.paymentManager.payStatus.observe(viewLifecycleOwner) { payStatus -> when (payStatus) { @@ -88,28 +80,25 @@ class PayTemplateFragment : Fragment() { showError(payStatus.error) } + is PayStatus.Checked -> { + val usableCurrencies = currencies + .intersect(payStatus.supportedCurrencies.toSet()) + .toList() + if (!payStatus.details.isTemplateEditable(usableCurrencies)) { + createOrder(payStatus.details.toTemplateParams()) + } + } + else -> {} } } } - private fun getAmountFieldStatus(defaultAmount: String?): AmountFieldStatus { - return if (defaultAmount == null) { - AmountFieldStatus.FixedAmount - } else if (defaultAmount.isBlank()) { - AmountFieldStatus.Default() - } else { - val parts = defaultAmount.split(":") - when (parts.size) { - 0 -> AmountFieldStatus.Default() - 1 -> AmountFieldStatus.Default(currency = parts[0]) - 2 -> AmountFieldStatus.Default(parts[1], parts[0]) - else -> AmountFieldStatus.Invalid - } - } + private fun checkTemplate() { + model.paymentManager.checkPayForTemplate(uriString) } - private fun createOrder(summary: String?, amount: Amount?) { - model.paymentManager.preparePayForTemplate(uriString, summary, amount) + private fun createOrder(params: TemplateParams) { + model.paymentManager.preparePayForTemplate(uriString, params) } } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt index d6131c7..2febfbb 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt @@ -24,84 +24,118 @@ 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 import net.taler.common.Amount +import net.taler.common.RelativeTime 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 +@OptIn(ExperimentalComposeUiApi::class) @Composable fun PayTemplateOrderComposable( - currencies: List<String>, // assumed to have size > 0 - defaultSummary: String? = null, - amountStatus: AmountFieldStatus, + 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, - onSubmit: (summary: String?, amount: Amount?) -> Unit, + onSubmit: (params: TemplateParams) -> Unit, ) { - val amountDefault = amountStatus as? AmountFieldStatus.Default + val defaultSummary = templateDetails.defaultSummary + val defaultAmount = templateDetails.defaultAmount + val defaultCurrency = templateDetails.defaultCurrency - var summary by remember { mutableStateOf(defaultSummary) } - var currency by remember { mutableStateOf(amountDefault?.currency ?: currencies[0]) } - var amount by remember { mutableStateOf(amountDefault?.amountStr ?: "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 (amountDefault != null) AmountField( + + AmountField( modifier = Modifier .padding(16.dp) .fillMaxWidth(), amount = amount, currency = currency, - currencies = currencies, - fixedCurrency = (amountStatus as? AmountFieldStatus.Default)?.currency != null, + currencies = usableCurrencies, + readOnlyCurrency = !templateDetails.isCurrencyEditable(usableCurrencies), + readOnlyAmount = !templateDetails.isAmountEditable(), onAmountChosen = { a, c -> amount = a currency = c }, ) + Button( modifier = Modifier.padding(16.dp), - enabled = defaultSummary == null || !summary.isNullOrBlank(), + enabled = !templateDetails.isSummaryEditable() || summary.isNotBlank(), onClick = { 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) + is AmountResult.InvalidAmount -> onError(R.string.amount_invalid) + // NOTE: it is important to nullify non-editable values! + is AmountResult.Success -> onSubmit(TemplateParams( + summary = if (templateDetails.isSummaryEditable()) summary else null, + amount = if(templateDetails.isAmountEditable()) res.amount else null, + )) } }, ) { Text(stringResource(R.string.payment_create_order)) } } + + LaunchedEffect(Unit) { + if (templateDetails.isSummaryEditable() + && templateDetails.defaultSummary == null) { + summaryFocusRequester.requestFocus() + } + } } @Composable private fun AmountField( modifier: Modifier = Modifier, currencies: List<String>, - fixedCurrency: Boolean, amount: String, currency: String, + readOnlyAmount: Boolean = true, + readOnlyCurrency: Boolean = true, onAmountChosen: (amount: String, currency: String) -> Unit, ) { Row( @@ -113,30 +147,42 @@ private fun AmountField( .weight(1f), value = amount, onValueChange = { onAmountChosen(it, currency) }, - label = { Text(stringResource(R.string.send_amount)) } + label = { Text(stringResource(R.string.amount_send)) }, + readOnly = readOnlyAmount, ) + CurrencyDropdown( modifier = Modifier.weight(1f), initialCurrency = currency, currencies = currencies, onCurrencyChanged = { onAmountChosen(amount, it) }, - readOnly = fixedCurrency, + readOnly = readOnlyCurrency, ) } } +val defaultTemplateDetails = WalletTemplateDetails( + templateContract = TemplateContractDetails( + minimumAge = 18, + payDuration = RelativeTime.forever(), + ), + editableDefaults = TemplateContractDetailsDefaults( + summary = "Donation", + amount = Amount.fromJSONString("KUDOS:10.0"), + ), +) + @Preview @Composable fun PayTemplateDefaultPreview() { TalerSurface { PayTemplateOrderComposable( - defaultSummary = "Donation", - amountStatus = AmountFieldStatus.Default("20", "ARS"), - currencies = listOf("KUDOS", "ARS"), + templateDetails = defaultTemplateDetails, + usableCurrencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, - onSubmit = { _, _ -> }, + onSubmit = { _ -> }, onError = { }, ) } @@ -147,13 +193,12 @@ fun PayTemplateDefaultPreview() { fun PayTemplateFixedAmountPreview() { TalerSurface { PayTemplateOrderComposable( - defaultSummary = "default summary", - amountStatus = AmountFieldStatus.FixedAmount, - currencies = listOf("KUDOS", "ARS"), + templateDetails = defaultTemplateDetails, + usableCurrencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, - onSubmit = { _, _ -> }, + onSubmit = { _ -> }, onError = { }, ) } @@ -164,13 +209,12 @@ fun PayTemplateFixedAmountPreview() { fun PayTemplateBlankSubjectPreview() { TalerSurface { PayTemplateOrderComposable( - defaultSummary = "", - amountStatus = AmountFieldStatus.FixedAmount, - currencies = listOf("KUDOS", "ARS"), + templateDetails = defaultTemplateDetails, + usableCurrencies = listOf("KUDOS", "ARS"), onCreateAmount = { text, currency -> AmountResult.Success(amount = Amount.fromString(currency, text)) }, - onSubmit = { _, _ -> }, + onSubmit = { _ -> }, onError = { }, ) } diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt index 35cd9e6..647c98c 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -22,9 +22,12 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString import net.taler.common.Amount import net.taler.common.ContractTerms 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.payment.PayStatus.AlreadyPaid @@ -37,8 +40,8 @@ import org.json.JSONObject val REGEX_PRODUCT_IMAGE = Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$") sealed class PayStatus { - object None : PayStatus() - object Loading : PayStatus() + data object None : PayStatus() + data object Loading : PayStatus() data class Prepared( val contractTerms: ContractTerms, val transactionId: String, @@ -46,6 +49,11 @@ sealed class PayStatus { val amountEffective: Amount, ) : PayStatus() + data class Checked( + val details: WalletTemplateDetails, + val supportedCurrencies: List<String>, + ) : PayStatus() + data class InsufficientBalance( val contractTerms: ContractTerms, val amountRaw: Amount, @@ -65,6 +73,12 @@ sealed class PayStatus { ) : PayStatus() } +@Serializable +data class CheckPayTemplateResponse( + val templateDetails: WalletTemplateDetails, + val supportedCurrencies: List<String>, +) + class PaymentManager( private val api: WalletBackendApi, private val scope: CoroutineScope, @@ -113,14 +127,25 @@ class PaymentManager( } } - fun preparePayForTemplate(url: String, summary: String?, amount: Amount?) = scope.launch { + fun checkPayForTemplate(url: String) = scope.launch { + mPayStatus.value = PayStatus.Loading + api.request("checkPayForTemplate", CheckPayTemplateResponse.serializer()) { + put("talerPayTemplateUri", url) + }.onError { + handleError("checkPayForTemplate", it) + }.onSuccess { response -> + mPayStatus.value = PayStatus.Checked( + details = response.templateDetails, + supportedCurrencies = response.supportedCurrencies, + ) + } + } + + fun preparePayForTemplate(url: String, params: TemplateParams) = scope.launch { mPayStatus.value = PayStatus.Loading api.request("preparePayForTemplate", PreparePayResponse.serializer()) { put("talerPayTemplateUri", url) - put("templateParams", JSONObject().apply { - summary?.let { put("summary", it) } - amount?.let { put("amount", it.toJSONString()) } - }) + put("templateParams", JSONObject(BackendManager.json.encodeToString(params))) }.onError { handleError("preparePayForTemplate", it) }.onSuccess { response -> diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt index 31c26a0..1995f9d 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt @@ -98,6 +98,7 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener { private fun onPaymentStatusChanged(payStatus: PayStatus?) { when (payStatus) { null -> {} + is PayStatus.Checked -> {} // does not apply, only used for templates is PayStatus.Prepared -> { showLoading(false) val fees = payStatus.amountEffective - payStatus.amountRaw diff --git a/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt b/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt index 0f6d661..beb37d9 100644 --- a/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt @@ -82,10 +82,10 @@ fun TransactionPaymentComposable( amountType = AmountType.Neutral, ) - val fee = t.amountEffective - t.amountRaw - if (!fee.isZero()) { + if (t.amountEffective > t.amountRaw) { + val fee = t.amountEffective - t.amountRaw TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt index 1ce0175..609629e 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt @@ -134,7 +134,7 @@ fun ColumnScope.PeerPullTermsComposable( modifier = Modifier.align(End), ) { Text( - text = stringResource(id = R.string.payment_label_amount_total), + text = stringResource(id = R.string.amount_total_label), style = MaterialTheme.typography.bodyLarge, ) Text( @@ -145,22 +145,26 @@ fun ColumnScope.PeerPullTermsComposable( ) } // this gets used for credit and debit, so fee calculation differs - val fee = if (data.isCredit) { + val fee = if (data.isCredit && terms.amountRaw > terms.amountEffective) { terms.amountRaw - terms.amountEffective - } else { + } else if (terms.amountEffective > terms.amountRaw) { terms.amountEffective - terms.amountRaw + } else null + + if (fee != null) { + val feeStr = if (data.isCredit) { + stringResource(R.string.amount_negative, fee) + } else { + stringResource(R.string.amount_positive, fee) + } + Text( + modifier = Modifier.align(End), + text = feeStr, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.error, + ) } - val feeStr = if (data.isCredit) { - stringResource(R.string.amount_negative, fee) - } else { - stringResource(R.string.amount_positive, fee) - } - if (!fee.isZero()) Text( - modifier = Modifier.align(End), - text = feeStr, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.error, - ) + if (terms is IncomingAccepting) { CircularProgressIndicator( modifier = Modifier diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt index 90b520e..f3d569f 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt @@ -148,10 +148,10 @@ fun OutgoingPullIntroComposable( amountType = AmountType.Positive, ) - if (state is OutgoingChecked) { + if (state is OutgoingChecked && state.amountRaw > state.amountEffective) { val fee = state.amountRaw - state.amountEffective - if (!fee.isZero()) TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + TransactionAmountComposable( + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(amount.spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt index d39fdc8..7eba733 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt @@ -89,7 +89,7 @@ fun OutgoingPushIntroComposable( style = MaterialTheme.typography.titleLarge, ) - if (state is OutgoingChecked) { + if (state is OutgoingChecked && state.amountEffective > state.amountRaw) { val fee = state.amountEffective - state.amountRaw Text( modifier = Modifier.padding(vertical = 16.dp), diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt index 3b15b6f..59d405c 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt @@ -49,15 +49,15 @@ fun ColumnScope.TransactionPeerPullCreditComposable(t: TransactionPeerPullCredit ) TransactionAmountComposable( - label = stringResource(id = R.string.receive_peer_amount_invoiced), + label = stringResource(id = R.string.amount_invoiced), amount = t.amountRaw.withSpec(spec), amountType = AmountType.Neutral, ) - val fee = t.amountRaw - t.amountEffective - if (!fee.isZero()) { + if (t.amountRaw > t.amountEffective) { + val fee = t.amountRaw - t.amountEffective TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt index dadff4a..b8966d4 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt @@ -46,10 +46,10 @@ fun TransactionPeerPullDebitComposable(t: TransactionPeerPullDebit, spec: Curren amountType = AmountType.Neutral, ) - val fee = t.amountEffective - t.amountRaw - if (!fee.isZero()) { + if (t.amountEffective > t.amountRaw) { + val fee = t.amountEffective - t.amountRaw TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt index dbf0fb9..d407ff2 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt @@ -46,10 +46,10 @@ fun TransactionPeerPushCreditComposable(t: TransactionPeerPushCredit, spec: Curr amountType = AmountType.Neutral, ) - val fee = t.amountRaw - t.amountEffective - if (!fee.isZero()) { + if (t.amountRaw > t.amountEffective) { + val fee = t.amountRaw - t.amountEffective TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt index e592c3e..f2edc19 100644 --- a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt +++ b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt @@ -65,10 +65,10 @@ fun ColumnScope.TransactionPeerPushDebitComposable(t: TransactionPeerPushDebit, amountType = AmountType.Neutral, ) - val fee = t.amountEffective - t.amountRaw - if (!fee.isZero()) { + if (t.amountEffective > t.amountRaw) { + val fee = t.amountEffective - t.amountRaw TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt b/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt index 637b41a..b17658a 100644 --- a/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt @@ -81,10 +81,10 @@ fun TransactionRefundComposable( amount = t.amountRaw.withSpec(spec), amountType = AmountType.Neutral, ) - val fee = t.amountRaw - t.amountEffective - if (!fee.isZero()) { + if (t.amountRaw > t.amountEffective) { + val fee = t.amountRaw - t.amountEffective TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionLossFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionLossFragment.kt index 9138345..2c95880 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionLossFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionLossFragment.kt @@ -99,7 +99,7 @@ fun TransitionLossComposable( ) TransactionAmountComposable( - label = stringResource(id = R.string.loss_amount), + label = stringResource(id = R.string.amount_lost), amount = t.amountEffective.withSpec(spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt index 8f474f9..e55d887 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt @@ -91,7 +91,7 @@ private fun TransactionRefreshComposable( style = MaterialTheme.typography.bodyLarge, ) TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = t.amountEffective.withSpec(spec), amountType = AmountType.Negative, ) diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt index 7ccdbde..2bd204c 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -17,6 +17,7 @@ package net.taler.wallet.transactions import android.content.Context +import android.net.Uri import android.util.Log import androidx.annotation.DrawableRes import androidx.annotation.IdRes @@ -354,10 +355,7 @@ class TransactionRefund( @Transient override val amountType = AmountType.Positive - override fun getTitle(context: Context): String { - val merchantName = paymentInfo?.merchant?.name ?: "null" - return context.getString(R.string.transaction_refund_from, merchantName) - } + override fun getTitle(context: Context) = paymentInfo?.merchant?.name ?: context.getString(R.string.transaction_refund) override val generalTitleRes = R.string.refund_title } @@ -404,7 +402,10 @@ class TransactionDeposit( @Transient override val amountType = AmountType.Negative override fun getTitle(context: Context): String { - return context.getString(R.string.transaction_deposit) + val uri = Uri.parse(targetPaytoUri) + return uri.getQueryParameter("receiver-name")?.let { receiverName -> + context.getString(R.string.transaction_deposit_to, receiverName) + } ?: context.getString(R.string.transaction_deposit) } override val generalTitleRes = R.string.transaction_deposit diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt index 5243427..d2d0c9c 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt @@ -26,6 +26,7 @@ import android.view.MenuItem import android.view.View import android.view.View.INVISIBLE import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView.OnQueryTextListener import androidx.fragment.app.Fragment @@ -44,6 +45,8 @@ import net.taler.wallet.MainViewModel import net.taler.wallet.R import net.taler.wallet.TAG import net.taler.wallet.balances.BalanceState.Success +import net.taler.wallet.balances.ScopeInfo +import net.taler.wallet.cleanExchange import net.taler.wallet.databinding.FragmentTransactionsBinding import net.taler.wallet.showError @@ -115,7 +118,7 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. if (balances.size == 1) ui.mainFab.visibility = INVISIBLE balances.find { it.scopeInfo == scopeInfo }?.let { balance -> - ui.amount.text = balance.available.toString(showSymbol = false) + ui.actionsBar.amount.text = balance.available.toString(showSymbol = false) transactionAdapter.setCurrencySpec(balance.available.spec) } } @@ -125,10 +128,10 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. transactionManager.transactions.observe(viewLifecycleOwner) { result -> onTransactionsResult(result) } - ui.sendButton.setOnClickListener { + ui.actionsBar.sendButton.setOnClickListener { findNavController().navigate(R.id.sendFunds) } - ui.receiveButton.setOnClickListener { + ui.actionsBar.receiveButton.setOnClickListener { findNavController().navigate(R.id.action_global_receiveFunds) } ui.mainFab.setOnClickListener { @@ -154,6 +157,8 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. override fun onStart() { super.onStart() requireActivity().title = getString(R.string.transactions_detail_title_currency, scopeInfo.currency) + (requireActivity() as AppCompatActivity).supportActionBar?.subtitle = + (scopeInfo as? ScopeInfo.Exchange)?.url?.let { cleanExchange(it) } } private fun setupSearch(item: MenuItem) { @@ -261,6 +266,11 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. return true } + override fun onStop() { + super.onStop() + (requireActivity() as AppCompatActivity).supportActionBar?.subtitle = null + } + override fun onDestroyActionMode(mode: ActionMode) { tracker?.clearSelection() actionMode = null diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt index 424cc2a..1d91107 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt @@ -44,7 +44,7 @@ fun TransitionsComposable( ) { FlowRow(horizontalArrangement = Center) { t.txActions.forEach { - if (it in arrayOf(Resume, Suspend)) { + if (it in arrayOf(Resume, Suspend, Retry)) { if (devMode) TransitionComposable(it, onTransition) } else { TransitionComposable(it, onTransition) diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt index 56f56f7..9983409 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt @@ -175,8 +175,8 @@ class PromptWithdrawFragment : Fragment() { ui.chosenAmountView.text = amountRaw.toString() ui.chosenAmountView.fadeIn() - val fee = amountRaw - amountEffective - if (!fee.isZero()) { + if (amountRaw > amountEffective) { + val fee = amountRaw - amountEffective ui.feeLabel.fadeIn() ui.feeView.text = getString(R.string.amount_negative, fee.toString()) ui.feeView.fadeIn() diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt index 20f8280..9bfeda6 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt @@ -81,23 +81,25 @@ fun TransactionWithdrawalComposable( ActionButton(tx = t, listener = actionListener) - TransactionAmountComposable( - label = stringResource(R.string.amount_chosen), - amount = t.amountRaw.withSpec(spec), - amountType = AmountType.Neutral, - ) + if (t.amountRaw != t.amountEffective) { + TransactionAmountComposable( + label = stringResource(R.string.amount_chosen), + amount = t.amountRaw.withSpec(spec), + amountType = AmountType.Neutral, + ) + } - val fee = t.amountRaw - t.amountEffective - if (!fee.isZero()) { + if (t.amountRaw > t.amountEffective) { + val fee = t.amountRaw - t.amountEffective TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee.withSpec(spec), amountType = AmountType.Negative, ) } TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_total), + label = stringResource(id = R.string.amount_total), amount = t.amountEffective.withSpec(spec), amountType = AmountType.Positive, ) diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawFragment.kt index c499c3b..e829bb1 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawFragment.kt @@ -20,6 +20,7 @@ import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View +import android.view.View.GONE import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -55,6 +56,13 @@ class ManualWithdrawFragment : Fragment() { ui.amountView.setText(amount.amountStr) } + arguments?.getBoolean("hideScanQr")?.let { + if (it) { + ui.qrCodeButton.visibility = GONE + ui.orView.visibility = GONE + } + } + ui.qrCodeButton.setOnClickListener { model.scanCode() } diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt index 75d03b5..00495fb 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt @@ -193,33 +193,33 @@ fun WithdrawalAmountTransfer( horizontalAlignment = Alignment.CenterHorizontally, ) { TransactionAmountComposable( - label = stringResource(R.string.withdraw_transfer), + label = stringResource(R.string.amount_transfer), amount = conversionAmountRaw, amountType = AmountType.Neutral, ) if (amountRaw.currency != conversionAmountRaw.currency) { TransactionAmountComposable( - label = stringResource(R.string.withdraw_conversion), + label = stringResource(R.string.amount_conversion), amount = amountRaw, amountType = AmountType.Neutral, ) } - val fee = amountRaw - amountEffective - if (!fee.isZero()) { + if (amountRaw > amountEffective) { + val fee = amountRaw - amountEffective TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_fees), + label = stringResource(id = R.string.amount_fee), amount = fee, amountType = AmountType.Negative, ) - } - TransactionAmountComposable( - label = stringResource(id = R.string.withdraw_total), - amount = amountEffective, - amountType = AmountType.Positive, - ) + TransactionAmountComposable( + label = stringResource(id = R.string.amount_total), + amount = amountEffective, + amountType = AmountType.Positive, + ) + } } } diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt index d0bc893..1698530 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt @@ -73,11 +73,11 @@ fun TransferIBAN( .padding(all = 16.dp) ) + DetailRow(stringResource(R.string.withdraw_manual_ready_subject), transfer.subject) transfer.receiverName?.let { DetailRow(stringResource(R.string.withdraw_manual_ready_receiver), it) } DetailRow(stringResource(R.string.withdraw_manual_ready_iban), transfer.iban) - DetailRow(stringResource(R.string.withdraw_manual_ready_subject), transfer.subject) TransactionInfoComposable( label = stringResource(R.string.withdraw_exchange), diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferTaler.kt b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferTaler.kt index 2ec43b9..089d0de 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferTaler.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferTaler.kt @@ -73,11 +73,11 @@ fun TransferTaler( .padding(all = 16.dp) ) + DetailRow(stringResource(R.string.withdraw_manual_ready_subject), transfer.subject) transfer.receiverName?.let { DetailRow(stringResource(R.string.withdraw_manual_ready_receiver), it) } DetailRow(stringResource(R.string.withdraw_manual_ready_account), transfer.account) - DetailRow(stringResource(R.string.withdraw_manual_ready_subject), transfer.subject) TransactionInfoComposable( label = stringResource(R.string.withdraw_exchange), |