summaryrefslogtreecommitdiff
path: root/wallet/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/main/java')
-rw-r--r--wallet/src/main/java/net/taler/wallet/HandleUriFragment.kt1
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainActivity.kt1
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt8
-rw-r--r--wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt17
-rw-r--r--wallet/src/main/java/net/taler/wallet/compose/AmountInputField.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/MakeBitcoinDepositComposable.kt18
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/MakeDepositComposable.kt16
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/PayToUriFragment.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/deposit/TransactionDepositComposable.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PayTemplateComposable.kt61
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PayTemplateDetails.kt126
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PayTemplateFragment.kt43
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PayTemplateOrderComposable.kt110
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt39
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt1
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/TransactionPaymentComposable.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/IncomingComposable.kt32
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPullComposable.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/OutgoingPushComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullCredit.kt8
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPullDebit.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushCredit.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/refund/TransactionRefundComposable.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionLossFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt11
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt16
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt20
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/manual/ManualWithdrawFragment.kt8
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/manual/ScreenTransfer.kt22
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferIBAN.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/manual/TransferTaler.kt2
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),