taler-android

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

commit fb8158122b602a807e3dc0c220660f3653d41e53
parent b119c37fb25a37ec466d72d3a7f2e1055058ce10
Author: Iván Ávalos <avalos@disroot.org>
Date:   Thu,  8 May 2025 16:00:15 +0200

[wallet] further withdrawal unification with iOS

bug 0009783

Diffstat:
Mwallet/src/main/java/net/taler/wallet/transactions/ActionButtonComposable.kt | 44++++++++++++++++++++++++++++++++------------
Mwallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt | 5++++-
Mwallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawSuccessFragment.kt | 4++++
Mwallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt | 183++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mwallet/src/main/java/net/taler/wallet/withdraw/manual/TransferBitcoin.kt | 4----
Mwallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt | 24++++++++----------------
Mwallet/src/main/java/net/taler/wallet/withdraw/manual/TransferTaler.kt | 3---
Mwallet/src/main/res/values/strings.xml | 10++++++----
8 files changed, 147 insertions(+), 130 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/transactions/ActionButtonComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/ActionButtonComposable.kt @@ -16,16 +16,19 @@ package net.taler.wallet.transactions +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountBalance import androidx.compose.material.icons.filled.Link +import androidx.compose.material.icons.filled.QrCode import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import net.taler.wallet.R @@ -39,7 +42,8 @@ interface ActionListener { enum class Type { COMPLETE_KYC, CONFIRM_WITH_BANK, - CONFIRM_MANUAL + CONFIRM_MANUAL, + SHOW_WIRE_QR, } fun onActionButtonClicked(tx: Transaction, type: Type) @@ -119,17 +123,33 @@ private fun ConfirmManualButton( tx: Transaction, listener: ActionListener, ) { - Button( - onClick = { listener.onActionButtonClicked(tx, ActionListener.Type.CONFIRM_MANUAL) }, - modifier = modifier, + Column( + horizontalAlignment = Alignment.CenterHorizontally, ) { - val label = stringResource(R.string.withdraw_manual_ready_details_intro) - Icon( - Icons.Default.AccountBalance, - label, - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Text(label) + Button( + onClick = { listener.onActionButtonClicked(tx, ActionListener.Type.CONFIRM_MANUAL) }, + modifier = modifier, + ) { + Icon( + Icons.Default.AccountBalance, + contentDescription = null, + modifier = Modifier.size(ButtonDefaults.IconSize) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(stringResource(R.string.withdraw_manual_ready_details_intro)) + } + + Button( + onClick = { listener.onActionButtonClicked(tx, ActionListener.Type.SHOW_WIRE_QR) }, + modifier = modifier, + ) { + Icon( + Icons.Default.QrCode, + contentDescription = null, + modifier = Modifier.size(ButtonDefaults.IconSize) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(stringResource(R.string.withdraw_manual_ready_details_qr)) + } } } diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import androidx.compose.runtime.getValue import androidx.compose.ui.platform.ComposeView +import androidx.core.os.bundleOf import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import net.taler.wallet.MainViewModel @@ -78,7 +79,8 @@ class TransactionWithdrawalFragment : TransactionDetailFragment(), ActionListene } } - ActionListener.Type.CONFIRM_MANUAL -> { + ActionListener.Type.CONFIRM_MANUAL, + ActionListener.Type.SHOW_WIRE_QR -> { if (tx !is TransactionWithdrawal) return if (tx.withdrawalDetails !is ManualTransfer) return if (tx.withdrawalDetails.exchangeCreditAccountDetails.isNullOrEmpty()) return @@ -98,6 +100,7 @@ class TransactionWithdrawalFragment : TransactionDetailFragment(), ActionListene findNavController().navigate( R.id.action_nav_transactions_detail_withdrawal_to_nav_exchange_manual_withdrawal_success, + bundleOf("showQrCodes" to (type == ActionListener.Type.SHOW_WIRE_QR)) ) } } diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawSuccessFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawSuccessFragment.kt @@ -53,11 +53,13 @@ class ManualWithdrawSuccessFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View = ComposeView(requireContext()).apply { + val showQrCodes = arguments?.getBoolean("showQrCodes") == true setContent { TalerSurface { val status by withdrawManager.withdrawStatus.collectAsStateLifecycleAware() val selectedTx by transactionManager.selectedTransaction.collectAsStateLifecycleAware() val qrCodes by withdrawManager.qrCodes.observeAsState() + val devMode by model.devMode.observeAsState() BackHandler { selectedTx?.let { navigateToDetails(it) } @@ -76,6 +78,8 @@ class ManualWithdrawSuccessFragment : Fragment() { }, bankAppClick = { onBankAppClick(it) }, shareClick = { onShareClick(it) }, + showQrCodes = showQrCodes, + devMode = devMode == true, ) } } 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 @@ -17,6 +17,7 @@ package net.taler.wallet.withdraw.manual import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -78,9 +79,11 @@ fun ScreenTransfer( status: WithdrawStatus, qrCodes: List<QrCodeSpec>, spec: CurrencySpecification?, + showQrCodes: Boolean, getQrCodes: (account: WithdrawalExchangeAccountDetails) -> Unit, bankAppClick: ((transfer: TransferData) -> Unit)?, shareClick: ((transfer: TransferData) -> Unit)?, + devMode: Boolean = false, ) { // TODO: show some placeholder if (status.withdrawalTransfers.isEmpty()) return @@ -128,63 +131,75 @@ fun ScreenTransfer( .verticalScroll(scrollState), horizontalAlignment = Alignment.CenterHorizontally, ) { + if (showQrCodes) { + Text( + text = stringResource(R.string.withdraw_manual_qr_intro), + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier + .padding( + vertical = 8.dp, + horizontal = 16.dp, + ) + ) + + qrCodes.forEach { spec -> + PaytoQrCard( + expanded = qrExpandedStates[spec]!!, + setExpanded = { expanded -> + if (expanded) { // un-expand all others + qrExpandedStates.forEach { (k, _) -> + qrExpandedStates[k] = false + } + } + // expand only toggled one + qrExpandedStates[spec] = expanded + }, + qrCode = spec, + ) + } + + BottomInsetsSpacer() + return + } + when (val transfer = selectedTransfer) { is TransferData.Taler -> TransferTaler( transfer = transfer, exchangeBaseUrl = status.exchangeBaseUrl!!, - transactionAmountRaw = status.amountInfo!!.amountRaw.withSpec(spec), - transactionAmountEffective = status.amountInfo.amountEffective.withSpec(spec), + transactionAmountEffective = status.amountInfo!!.amountEffective.withSpec(spec), ) is TransferData.IBAN -> TransferIBAN( transfer = transfer, - exchangeBaseUrl = status.exchangeBaseUrl!!, - transactionAmountRaw = status.amountInfo!!.amountRaw.withSpec(spec), - transactionAmountEffective = status.amountInfo.amountEffective.withSpec(spec), + transactionAmountEffective = status.amountInfo!!.amountEffective.withSpec(spec), ) is TransferData.Bitcoin -> TransferBitcoin( transfer = transfer, - transactionAmountRaw = status.amountInfo!!.amountRaw.withSpec(spec), - transactionAmountEffective = status.amountInfo.amountEffective.withSpec(spec), - ) - } - - qrCodes.forEach { spec -> - PaytoQrCard( - expanded = qrExpandedStates[spec]!!, - setExpanded = { expanded -> - if (expanded) { // un-expand all others - qrExpandedStates.forEach { (k, _) -> - qrExpandedStates[k] = false - } - } - // expand only toggled one - qrExpandedStates[spec] = expanded - }, - qrCode = spec, ) } Spacer(Modifier.height(24.dp)) - val paytoUri = selectedTransfer.withdrawalAccount.paytoUri - if (bankAppClick != null && LocalContext.current.canAppHandleUri(paytoUri)) { - Button( - onClick = { bankAppClick(selectedTransfer) }, - modifier = Modifier - .padding(bottom = 16.dp), - ) { - Text(text = stringResource(R.string.withdraw_manual_ready_bank_button)) + if (devMode) { + val paytoUri = selectedTransfer.withdrawalAccount.paytoUri + if (bankAppClick != null && LocalContext.current.canAppHandleUri(paytoUri)) { + Button( + onClick = { bankAppClick(selectedTransfer) }, + modifier = Modifier + .padding(bottom = 16.dp), + ) { + Text(text = stringResource(R.string.withdraw_manual_ready_bank_button)) + } } - } - if (shareClick != null) { - ShareButton( - content = selectedTransfer.withdrawalAccount.paytoUri, - modifier = Modifier - .padding(bottom = 16.dp), - ) + if (shareClick != null) { + ShareButton( + content = selectedTransfer.withdrawalAccount.paytoUri, + modifier = Modifier + .padding(bottom = 16.dp), + ) + } } BottomInsetsSpacer() @@ -234,33 +249,37 @@ fun DetailRow( style = MaterialTheme.typography.bodyMedium, ) - Text( - modifier = Modifier.padding( - top = 8.dp, - start = 6.dp, - end = 6.dp, - ), - text = content, - style = if (characterBreak) { - MaterialTheme.typography.bodyLarge.copy( - lineBreak = LineBreak.Heading, - ) - } else MaterialTheme.typography.bodyLarge, - fontFamily = if (copy) FontFamily.Monospace else FontFamily.Default, - textAlign = TextAlign.Center, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.padding( + top = 8.dp, + start = 6.dp, + end = 6.dp, + ).weight(1f), + text = content, + style = if (characterBreak) { + MaterialTheme.typography.bodyLarge.copy( + lineBreak = LineBreak.Heading, + ) + } else MaterialTheme.typography.bodyLarge, + fontFamily = if (copy) FontFamily.Monospace else FontFamily.Default, + textAlign = TextAlign.Center, + ) - if (copy) { - TextButton( - onClick = { copyToClipBoard(context, label, content) }, - ) { - Icon( - Icons.Default.ContentCopy, - contentDescription = null, - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Text(stringResource(R.string.copy)) + if (copy) { + TextButton( + onClick = { copyToClipBoard(context, label, content) }, + ) { + Icon( + Icons.Default.ContentCopy, + contentDescription = null, + modifier = Modifier.size(ButtonDefaults.IconSize), + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(stringResource(R.string.copy)) + } } } } @@ -268,8 +287,6 @@ fun DetailRow( @Composable fun WithdrawalAmountTransfer( - amountRaw: Amount, - amountEffective: Amount, conversionAmountRaw: Amount, ) { Column( @@ -281,29 +298,6 @@ fun WithdrawalAmountTransfer( amount = conversionAmountRaw, amountType = AmountType.Neutral, ) - - if (amountRaw.currency != conversionAmountRaw.currency) { - TransactionAmountComposable( - label = stringResource(R.string.amount_conversion), - amount = amountRaw, - amountType = AmountType.Neutral, - ) - } - - if (amountRaw > amountEffective) { - val fee = amountRaw - amountEffective - TransactionAmountComposable( - label = stringResource(id = R.string.amount_fee), - amount = fee, - amountType = AmountType.Negative, - ) - - TransactionAmountComposable( - label = stringResource(id = R.string.amount_total), - amount = amountEffective, - amountType = AmountType.Positive, - ) - } } } @@ -351,7 +345,9 @@ fun TransferAccountChooser( @Preview @Composable -fun ScreenTransferPreview() { +fun ScreenTransferPreview( + showQrCodes: Boolean = false, +) { Surface { ScreenTransfer( status = WithdrawStatus( @@ -414,7 +410,14 @@ fun ScreenTransferPreview() { QrCodeSpec(EpcQr, "BCD\\n002\\n1\\nSCT\\n\\n\\nGENODEM1GLS/DE54430609674049078800\\n\\n\\nTaler MJ15S835A5ENQZGJX161TS7FND6Q5DSABS8FCHB8ECF9NT1J8GH0"), QrCodeSpec(SPC, "BCD\\n002\\n1\\nSCT\\n\\n\\nGENODEM1GLS/DE54430609674049078800\\n\\n\\nTaler MJ15S835A5ENQZGJX161TS7FND6Q5DSABS8FCHB8ECF9NT1J8GH0") ), + showQrCodes = showQrCodes, getQrCodes = {}, ) } +} + +@Preview +@Composable +fun ScreenTransferQRPreview() { + ScreenTransferPreview(true) } \ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferBitcoin.kt b/wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferBitcoin.kt @@ -36,8 +36,6 @@ import net.taler.wallet.withdraw.TransferData @Composable fun TransferBitcoin( transfer: TransferData.Bitcoin, - transactionAmountRaw: Amount, - transactionAmountEffective: Amount, ) { Column( modifier = Modifier.padding(all = 16.dp), @@ -58,8 +56,6 @@ fun TransferBitcoin( transfer.withdrawalAccount.transferAmount?.let { amount -> WithdrawalAmountTransfer( - amountRaw = transactionAmountRaw, - amountEffective = transactionAmountEffective, conversionAmountRaw = amount.withSpec( transfer.withdrawalAccount.currencySpecification, ), 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 @@ -28,16 +28,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import net.taler.common.Amount import net.taler.wallet.R -import net.taler.wallet.cleanExchange import net.taler.wallet.compose.WarningLabel -import net.taler.wallet.transactions.TransactionInfoComposable import net.taler.wallet.withdraw.TransferData @Composable fun TransferIBAN( transfer: TransferData.IBAN, - exchangeBaseUrl: String, - transactionAmountRaw: Amount, transactionAmountEffective: Amount, ) { val transferAmount = transfer @@ -74,7 +70,10 @@ fun TransferIBAN( ) WarningLabel( - modifier = Modifier.padding(8.dp), + modifier = Modifier.padding( + horizontal = 8.dp, + vertical = 16.dp, + ), label = stringResource(R.string.withdraw_manual_ready_warning), ) @@ -86,20 +85,13 @@ fun TransferIBAN( DetailRow(stringResource(R.string.withdraw_manual_ready_iban), transfer.iban) - TransferStep(3, stringResource( - R.string.withdraw_manual_step_finish, - transferAmount, - )) - WithdrawalAmountTransfer( - amountRaw = transactionAmountRaw, - amountEffective = transactionAmountEffective, conversionAmountRaw = transferAmount, ) - TransactionInfoComposable( - label = stringResource(R.string.withdraw_exchange), - info = cleanExchange(exchangeBaseUrl), - ) + TransferStep(3, stringResource( + R.string.withdraw_manual_step_finish, + transferAmount, + )) } } \ No newline at end of file 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 @@ -36,7 +36,6 @@ import net.taler.wallet.withdraw.TransferData fun TransferTaler( transfer: TransferData.Taler, exchangeBaseUrl: String, - transactionAmountRaw: Amount, transactionAmountEffective: Amount, ) { val transferAmount = transfer @@ -88,8 +87,6 @@ fun TransferTaler( ) WithdrawalAmountTransfer( - amountRaw = transactionAmountRaw, - amountEffective = transactionAmountEffective, conversionAmountRaw = transferAmount, ) } diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml @@ -288,17 +288,19 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card <string name="withdraw_initiated">Withdrawal initiated</string> <string name="withdraw_manual_bitcoin_intro">Now make a split transaction with the following three outputs.</string> <string name="withdraw_manual_qr_epc">EPC QR</string> - <string name="withdraw_manual_qr_spc">Swiss QR</string> + <string name="withdraw_manual_qr_intro">If your banking software runs on another device, you can scan one of these QR codes:</string> + <string name="withdraw_manual_qr_spc">Swiss QR bill</string> <string name="withdraw_manual_ready_account">Account</string> <string name="withdraw_manual_ready_bank_button">Open in banking app</string> - <string name="withdraw_manual_ready_details_intro">Bank transfer details</string> + <string name="withdraw_manual_ready_details_intro">Wire transfer instructions</string> + <string name="withdraw_manual_ready_details_qr">Wire transfer QR codes</string> <string name="withdraw_manual_ready_iban">IBAN</string> <!-- <string name="withdraw_manual_ready_intro">To complete the process you need to wire %s to the provider\'s bank account</string>--> <string name="withdraw_manual_ready_intro">You need to transfer %1$s from your regular bank account to the payment service to receive %2$s as electronic cash in this wallet.</string> <string name="withdraw_manual_ready_receiver">Recipient</string> <string name="withdraw_manual_ready_subject">Subject</string> - <string name="withdraw_manual_ready_warning">Make sure to use the correct subject, otherwise the money will not arrive in this wallet.</string> - <string name="withdraw_manual_step">&lt;b&gt;Step %1$s:&lt;b/&gt; %2$s</string> + <string name="withdraw_manual_ready_warning">This is mandatory, otherwise your money will not arrive in this wallet.</string> + <string name="withdraw_manual_step">&lt;b&gt;Step %1$s:&lt;/b&gt; %2$s</string> <string name="withdraw_manual_step_finish">Finish the wire transfer of %1$s in your banking app or website, then this withdrawal will proceed automatically. Depending on your bank the transfer can take from minutes to two working days, please be patient.</string> <string name="withdraw_manual_step_iban">If you don\'t already have it in your banking favorites list, then copy and paste recipient and IBAN into the recipient/IBAN fields in your banking app or website (and save it as favorite for the next time):</string> <string name="withdraw_manual_step_subject">Copy this code and paste it into the subject/purpose field in your banking app or bank website:</string>