From 1e9ee99cb07c595132fa96935e3f2b7c88dd586a Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Mon, 12 Feb 2024 16:43:50 -0600 Subject: [wallet] DD51: initial rendering based on currency spec bug 0008329 (cherry picked from commit 4789103406f44ba797c7bfee3112cc6aaa228683) --- .../src/main/java/net/taler/common/Amount.kt | 46 +++++++++++++++++++++- .../java/net/taler/common/CurrencySpecification.kt | 36 +++++++++++++++++ .../net/taler/wallet/balances/BalanceAdapter.kt | 7 +--- .../net/taler/wallet/balances/BalanceManager.kt | 38 +++++++++++++++++- .../java/net/taler/wallet/balances/Balances.kt | 1 + .../taler/wallet/balances/CurrencySpecification.kt | 33 ---------------- .../java/net/taler/wallet/exchanges/Exchanges.kt | 2 + .../wallet/transactions/TransactionAdapter.kt | 2 +- .../wallet/transactions/TransactionPeerFragment.kt | 2 +- .../net/taler/wallet/transactions/Transactions.kt | 2 +- .../wallet/transactions/TransactionsFragment.kt | 6 +-- .../withdraw/TransactionWithdrawalComposable.kt | 4 +- .../net/taler/wallet/withdraw/WithdrawManager.kt | 2 + .../taler/wallet/withdraw/manual/ScreenTransfer.kt | 2 +- wallet/src/main/res/layout/list_item_balance.xml | 15 +------ 15 files changed, 134 insertions(+), 64 deletions(-) create mode 100644 taler-kotlin-android/src/main/java/net/taler/common/CurrencySpecification.kt delete mode 100644 wallet/src/main/java/net/taler/wallet/balances/CurrencySpecification.kt diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt index 5fb36fa..d6188cf 100644 --- a/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt +++ b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt @@ -16,11 +16,14 @@ package net.taler.common +import android.os.Build import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Serializer import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import java.text.DecimalFormat +import java.text.NumberFormat import kotlin.math.floor import kotlin.math.pow import kotlin.math.roundToInt @@ -54,6 +57,11 @@ public data class Amount( * of 50_000_000 would correspond to 50 cents. */ val fraction: Int, + + /** + * Currency specification for amount + */ + val spec: CurrencySpecification? = null, ) : Comparable { public companion object { @@ -170,6 +178,8 @@ public data class Amount( return Amount(checkCurrency(currency), this.value, this.fraction) } + fun withSpec(spec: CurrencySpecification?) = copy(spec = spec) + public operator fun minus(other: Amount): Amount { check(currency == other.currency) { "Can only subtract from same currency" } var resultValue = value @@ -196,8 +206,40 @@ public data class Amount( return "$currency:$amountStr" } - override fun toString(): String { - return "$amountStr $currency" + override fun toString() = toString( + showSymbol = true, + negative = false, + ) + + fun toString( + showSymbol: Boolean = true, + negative: Boolean = false, + ): String { + val symbols = DecimalFormat().decimalFormatSymbols + + val format = if (showSymbol) { + NumberFormat.getCurrencyInstance() + } else { + // Make sure monetary separators are the ones used! + symbols.decimalSeparator = symbols.monetaryDecimalSeparator + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + symbols.groupingSeparator = symbols.monetaryGroupingSeparator + } + + NumberFormat.getInstance() + } + + if (spec != null) { + symbols.currencySymbol = spec.symbol(this) + format.maximumFractionDigits = spec.numFractionalNormalDigits + format.minimumFractionDigits = spec.numFractionalTrailingZeroDigits + } else { + symbols.currencySymbol = currency + } + + (format as DecimalFormat).decimalFormatSymbols = symbols + + return format.format((if (negative) "-$amountStr" else amountStr).toBigDecimal()) } override fun compareTo(other: Amount): Int { diff --git a/taler-kotlin-android/src/main/java/net/taler/common/CurrencySpecification.kt b/taler-kotlin-android/src/main/java/net/taler/common/CurrencySpecification.kt new file mode 100644 index 0000000..4aa8968 --- /dev/null +++ b/taler-kotlin-android/src/main/java/net/taler/common/CurrencySpecification.kt @@ -0,0 +1,36 @@ +/* + * 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 + */ + +package net.taler.common + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CurrencySpecification( + val name: String, + @SerialName("num_fractional_input_digits") + val numFractionalInputDigits: Int, + @SerialName("num_fractional_normal_digits") + val numFractionalNormalDigits: Int, + @SerialName("num_fractional_trailing_zero_digits") + val numFractionalTrailingZeroDigits: Int, + @SerialName("alt_unit_names") + val altUnitNames: Map, +) { + // TODO: add support for alt units + fun symbol(amount: Amount): String = altUnitNames[0] ?: amount.currency +} \ No newline at end of file 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 efb3d48..0b87dee 100644 --- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt +++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt @@ -58,7 +58,6 @@ class BalanceAdapter(private val listener: BalanceClickListener) : Adapter ) +@Serializable +data class GetCurrencySpecificationResponse( + val currencySpecification: CurrencySpecification, +) + sealed class BalanceState { data object None: BalanceState() data object Loading: BalanceState() @@ -67,11 +76,38 @@ class BalanceManager( } response.onSuccess { mState.postValue(BalanceState.Success(it.balances)) - mBalances.postValue(it.balances) + scope.launch { + // Get currency spec for all balances) + mState.postValue( + BalanceState.Success(it.balances.map { balance -> + val spec = getCurrencySpecification(balance.scopeInfo) + balance.copy( + available = balance.available.withSpec(spec), + pendingIncoming = balance.pendingIncoming.withSpec(spec), + pendingOutgoing = balance.pendingOutgoing.withSpec(spec), + ) + }), + ) + } } } } + private suspend fun getCurrencySpecification(scopeInfo: ScopeInfo): CurrencySpecification? { + var spec: CurrencySpecification? = null + api.request("getCurrencySpecification", GetCurrencySpecificationResponse.serializer()) { + val json = Json.encodeToString(scopeInfo) + Log.d(TAG, "BalanceManager: $json") + put("scope", JSONObject(json)) + }.onSuccess { + spec = it.currencySpecification + }.onError { + Log.e(TAG, "Error getting currency spec for scope $scopeInfo: $it") + } + + return spec + } + fun resetBalances() { mState.value = BalanceState.None } diff --git a/wallet/src/main/java/net/taler/wallet/balances/Balances.kt b/wallet/src/main/java/net/taler/wallet/balances/Balances.kt index dff2ffb..3e0acc9 100644 --- a/wallet/src/main/java/net/taler/wallet/balances/Balances.kt +++ b/wallet/src/main/java/net/taler/wallet/balances/Balances.kt @@ -19,6 +19,7 @@ package net.taler.wallet.balances import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.taler.common.Amount +import net.taler.common.CurrencySpecification @Serializable data class BalanceItem( diff --git a/wallet/src/main/java/net/taler/wallet/balances/CurrencySpecification.kt b/wallet/src/main/java/net/taler/wallet/balances/CurrencySpecification.kt deleted file mode 100644 index 5001db4..0000000 --- a/wallet/src/main/java/net/taler/wallet/balances/CurrencySpecification.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of GNU Taler - * (C) 2023 Taler Systems S.A. - * - * GNU Taler is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3, or (at your option) any later version. - * - * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * GNU Taler; see the file COPYING. If not, see - */ - -package net.taler.wallet.balances - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class CurrencySpecification( - val name: String, - @SerialName("num_fractional_input_digits") - val numFractionalInputDigits: Int, - @SerialName("num_fractional_normal_digits") - val numFractionalNormalDigits: Int, - @SerialName("num_fractional_trailing_zero_digits") - val numFractionalTrailingZeroDigits: Int, - @SerialName("alt_unit_names") - val altUnitNames: Map, -) \ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt b/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt index af373c2..ce0bd82 100644 --- a/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt +++ b/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt @@ -17,6 +17,7 @@ package net.taler.wallet.exchanges import kotlinx.serialization.Serializable +import net.taler.wallet.balances.ScopeInfo import net.taler.wallet.cleanExchange @Serializable @@ -25,6 +26,7 @@ data class ExchangeItem( // can be null before exchange info in wallet-core was fully loaded val currency: String? = null, val paytoUris: List, + val scopeInfo: ScopeInfo? = null, ) { val name: String get() = cleanExchange(exchangeBaseUrl) } \ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt index c9ae889..6fade86 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt @@ -183,7 +183,7 @@ internal class TransactionAdapter( } private fun bindAmount(transaction: Transaction) { - val amountStr = transaction.amountEffective.amountStr + val amountStr = transaction.amountEffective.toString(showSymbol = false) when (transaction.amountType) { AmountType.Positive -> { amount.text = context.getString(R.string.amount_positive, amountStr) diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt index c934d89..7feedd1 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPeerFragment.kt @@ -106,7 +106,7 @@ fun TransactionAmountComposable(label: String, amount: Amount, amountType: Amoun ) Text( modifier = Modifier.padding(top = 8.dp, start = 16.dp, end = 16.dp, bottom = 16.dp), - text = if (amountType == AmountType.Negative) "-$amount" else amount.toString(), + text = amount.toString(negative = amountType == AmountType.Negative), fontSize = 24.sp, color = when (amountType) { AmountType.Positive -> colorResource(R.color.green) 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 9017854..be36a13 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -41,7 +41,7 @@ import net.taler.wallet.R import net.taler.wallet.TAG import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo -import net.taler.wallet.balances.CurrencySpecification +import net.taler.common.CurrencySpecification import net.taler.wallet.cleanExchange import net.taler.wallet.refund.RefundPaymentInfo import net.taler.wallet.transactions.TransactionMajorState.None 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 3f610d3..d89fbb5 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt @@ -37,7 +37,6 @@ import androidx.recyclerview.selection.StorageStrategy import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL import com.google.android.material.dialog.MaterialAlertDialogBuilder -import net.taler.common.Amount import net.taler.common.fadeIn import net.taler.common.fadeOut import net.taler.common.showError @@ -113,8 +112,9 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. val balances = state.balances // hide extra fab when in single currency mode (uses MainFragment's FAB) if (balances.size == 1) ui.mainFab.visibility = INVISIBLE - balances.find { it.currency == currency }?.available?.let { amount: Amount -> - ui.amount.text = amount.amountStr + // TODO: find via scopeInfo instead of currency + balances.find { it.currency == currency }?.let { balance -> + ui.amount.text = balance.available.toString(showSymbol = false) } } transactionManager.progress.observe(viewLifecycleOwner) { show -> 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 fda1815..7e8f94f 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/TransactionWithdrawalComposable.kt @@ -38,7 +38,7 @@ import net.taler.wallet.R import net.taler.wallet.backend.TalerErrorCode import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.cleanExchange -import net.taler.wallet.balances.CurrencySpecification +import net.taler.common.CurrencySpecification import net.taler.wallet.transactions.ActionButton import net.taler.wallet.transactions.ActionListener import net.taler.wallet.transactions.AmountType @@ -133,7 +133,7 @@ fun TransactionWithdrawalComposablePreview() { numFractionalInputDigits = 2, numFractionalNormalDigits = 2, numFractionalTrailingZeroDigits = 2, - altUnitNames = mapOf("0" to "NETZBON"), + altUnitNames = mapOf(0 to "NETZBON"), ), ), ), diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt index 4c9479b..e308b2a 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt @@ -31,6 +31,7 @@ import net.taler.common.toEvent import net.taler.wallet.TAG import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.backend.WalletBackendApi +import net.taler.wallet.balances.ScopeInfo import net.taler.wallet.exchanges.ExchangeFees import net.taler.wallet.exchanges.ExchangeItem import net.taler.wallet.transactions.WithdrawalExchangeAccountDetails @@ -145,6 +146,7 @@ data class ManualWithdrawalDetails( val amountEffective: Amount, val withdrawalAccountsList: List, val ageRestrictionOptions: List? = null, + val scopeInfo: ScopeInfo, ) @Serializable 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 d75c685..86972cf 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 @@ -47,8 +47,8 @@ import androidx.compose.ui.unit.dp import net.taler.common.Amount import net.taler.wallet.CURRENCY_BTC import net.taler.wallet.R +import net.taler.common.CurrencySpecification import net.taler.wallet.compose.ShareButton -import net.taler.wallet.balances.CurrencySpecification import net.taler.wallet.compose.copyToClipBoard import net.taler.wallet.transactions.AmountType import net.taler.wallet.transactions.TransactionAmountComposable diff --git a/wallet/src/main/res/layout/list_item_balance.xml b/wallet/src/main/res/layout/list_item_balance.xml index 82e663f..483b1ce 100644 --- a/wallet/src/main/res/layout/list_item_balance.xml +++ b/wallet/src/main/res/layout/list_item_balance.xml @@ -28,26 +28,13 @@ android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:textSize="40sp" - app:layout_constraintEnd_toStartOf="@+id/balanceCurrencyView" + app:layout_constraintEnd_toStartOf="@+id/pendingView" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="100.50" /> - -