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:
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"><b>Step %1$s:<b/> %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"><b>Step %1$s:</b> %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>