From 512e79eaf07eadd24914eb4a41b52c824866c528 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Mon, 15 Jan 2024 17:50:24 -0600 Subject: [wallet] Refactor balances into BalanceManager --- .../src/main/java/net/taler/wallet/MainFragment.kt | 11 ++-- .../main/java/net/taler/wallet/MainViewModel.kt | 30 ++------- .../net/taler/wallet/balances/BalanceAdapter.kt | 12 ---- .../net/taler/wallet/balances/BalanceManager.kt | 76 ++++++++++++++++++++++ .../net/taler/wallet/balances/BalanceResponse.kt | 24 ------- .../java/net/taler/wallet/balances/Balances.kt | 30 +++++++++ .../net/taler/wallet/balances/BalancesFragment.kt | 35 +++++++--- .../wallet/transactions/TransactionsFragment.kt | 7 +- 8 files changed, 148 insertions(+), 77 deletions(-) create mode 100644 wallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt delete mode 100644 wallet/src/main/java/net/taler/wallet/balances/BalanceResponse.kt create mode 100644 wallet/src/main/java/net/taler/wallet/balances/Balances.kt (limited to 'wallet') diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt b/wallet/src/main/java/net/taler/wallet/MainFragment.kt index 2521e29..656db63 100644 --- a/wallet/src/main/java/net/taler/wallet/MainFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/MainFragment.kt @@ -26,7 +26,8 @@ import androidx.navigation.fragment.findNavController import net.taler.common.EventObserver import net.taler.wallet.CurrencyMode.MULTI import net.taler.wallet.CurrencyMode.SINGLE -import net.taler.wallet.balances.BalanceItem +import net.taler.wallet.balances.BalanceState +import net.taler.wallet.balances.BalanceState.Success import net.taler.wallet.balances.BalancesFragment import net.taler.wallet.databinding.FragmentMainBinding import net.taler.wallet.transactions.TransactionsFragment @@ -50,7 +51,7 @@ class MainFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - model.balances.observe(viewLifecycleOwner) { + model.balanceManager.state.observe(viewLifecycleOwner) { onBalancesChanged(it) } model.transactionsEvent.observe(viewLifecycleOwner, EventObserver { currency -> @@ -72,10 +73,12 @@ class MainFragment : Fragment() { override fun onStart() { super.onStart() - model.loadBalances() + model.balanceManager.loadBalances() } - private fun onBalancesChanged(balances: List) { + private fun onBalancesChanged(state: BalanceState) { + if (state !is Success) return + val balances = state.balances val mode = if (balances.size == 1) SINGLE else MULTI if (currencyMode != mode) { val f = if (mode == SINGLE) { diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt index 3c2c4ae..c28c027 100644 --- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt +++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt @@ -22,10 +22,8 @@ import androidx.annotation.UiThread import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import net.taler.common.Amount import net.taler.common.AmountParserException @@ -37,8 +35,7 @@ import net.taler.wallet.backend.NotificationReceiver import net.taler.wallet.backend.VersionReceiver import net.taler.wallet.backend.WalletBackendApi import net.taler.wallet.backend.WalletCoreVersion -import net.taler.wallet.balances.BalanceItem -import net.taler.wallet.balances.BalanceResponse +import net.taler.wallet.balances.BalanceManager import net.taler.wallet.deposit.DepositManager import net.taler.wallet.exchanges.ExchangeManager import net.taler.wallet.payment.PaymentManager @@ -60,9 +57,6 @@ class MainViewModel( app: Application, ) : AndroidViewModel(app), VersionReceiver, NotificationReceiver { - private val mBalances = MutableLiveData>() - val balances: LiveData> = mBalances.distinctUntilChanged() - val devMode = MutableLiveData(BuildConfig.DEBUG) val showProgressBar = MutableLiveData() var walletVersion: String? = null @@ -83,6 +77,7 @@ class MainViewModel( PendingOperationsManager(api, viewModelScope) val transactionManager: TransactionManager = TransactionManager(api, viewModelScope) val refundManager = RefundManager(api, viewModelScope) + val balanceManager = BalanceManager(api, viewModelScope) val exchangeManager: ExchangeManager = ExchangeManager(api, viewModelScope) val peerManager: PeerManager = PeerManager(api, exchangeManager, viewModelScope) val settingsManager: SettingsManager = SettingsManager(app.applicationContext, api, viewModelScope) @@ -108,7 +103,7 @@ class MainViewModel( // Only update balances when we're told they changed if (payload.type == "balance-change") { - loadBalances() + balanceManager.loadBalances() } if (payload.type in transactionNotifications) viewModelScope.launch(Dispatchers.Main) { @@ -122,19 +117,6 @@ class MainViewModel( } } - @UiThread - fun loadBalances(): Job = viewModelScope.launch { - showProgressBar.value = true - val response = api.request("getBalances", BalanceResponse.serializer()) - showProgressBar.value = false - response.onError { - Log.e(TAG, "Error retrieving balances: $it") - } - response.onSuccess { - mBalances.value = it.balances - } - } - /** * Navigates to the given currency's transaction list, when [MainFragment] is shown. */ @@ -145,7 +127,7 @@ class MainViewModel( @UiThread fun getCurrencies(): List { - return balances.value?.map { balanceItem -> + return balanceManager.balancesOrNull?.map { balanceItem -> balanceItem.currency } ?: emptyList() } @@ -163,7 +145,7 @@ class MainViewModel( @UiThread fun hasSufficientBalance(amount: Amount): Boolean { - balances.value?.forEach { balanceItem -> + balanceManager.balancesOrNull?.forEach { balanceItem -> if (balanceItem.currency == amount.currency) { return balanceItem.available >= amount } @@ -177,7 +159,7 @@ class MainViewModel( api.sendRequest("clearDb") } withdrawManager.testWithdrawalStatus.value = null - mBalances.value = emptyList() + balanceManager.resetBalances() } fun startTunnel() { 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 24ee1a1..6f3d79b 100644 --- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt +++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt @@ -24,21 +24,9 @@ import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.Adapter -import kotlinx.serialization.Serializable -import net.taler.common.Amount import net.taler.wallet.R import net.taler.wallet.balances.BalanceAdapter.BalanceViewHolder -@Serializable -data class BalanceItem( - val available: Amount, - val pendingIncoming: Amount, - val pendingOutgoing: Amount -) { - val currency: String get() = available.currency - val hasPending: Boolean get() = !pendingIncoming.isZero() || !pendingOutgoing.isZero() -} - class BalanceAdapter(private val listener: BalanceClickListener) : Adapter() { private var items = emptyList() diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt new file mode 100644 index 0000000..3321cd1 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt @@ -0,0 +1,76 @@ +/* + * 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.wallet.balances + +import android.util.Log +import androidx.annotation.UiThread +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.distinctUntilChanged +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import net.taler.wallet.TAG +import net.taler.wallet.backend.TalerErrorInfo +import net.taler.wallet.backend.WalletBackendApi + +@Serializable +data class BalanceResponse( + val balances: List +) + +sealed class BalanceState { + data object None: BalanceState() + data object Loading: BalanceState() + + data class Success( + val balances: List, + ): BalanceState() + + data class Error( + val error: TalerErrorInfo, + ): BalanceState() +} + +class BalanceManager( + private val api: WalletBackendApi, + private val scope: CoroutineScope, +) { + private val mState = MutableLiveData(BalanceState.None) + val state: LiveData = mState.distinctUntilChanged() + + val balancesOrNull get() = (state.value as? BalanceState.Success)?.balances + + @UiThread + fun loadBalances() { + mState.value = BalanceState.Loading + scope.launch { + val response = api.request("getBalances", BalanceResponse.serializer()) + response.onError { + Log.e(TAG, "Error retrieving balances: $it") + mState.value = BalanceState.Error(it) + } + response.onSuccess { + mState.value = BalanceState.Success(it.balances) + } + } + } + + fun resetBalances() { + mState.value = BalanceState.None + } +} \ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceResponse.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceResponse.kt deleted file mode 100644 index d1a111f..0000000 --- a/wallet/src/main/java/net/taler/wallet/balances/BalanceResponse.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * This file is part of GNU Taler - * (C) 2020 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.Serializable - -@Serializable -data class BalanceResponse( - val balances: List -) diff --git a/wallet/src/main/java/net/taler/wallet/balances/Balances.kt b/wallet/src/main/java/net/taler/wallet/balances/Balances.kt new file mode 100644 index 0000000..2954f5b --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/balances/Balances.kt @@ -0,0 +1,30 @@ +/* + * 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.wallet.balances + +import kotlinx.serialization.Serializable +import net.taler.common.Amount + +@Serializable +data class BalanceItem( + val available: Amount, + val pendingIncoming: Amount, + val pendingOutgoing: Amount, +) { + val currency: String get() = available.currency + val hasPending: Boolean get() = !pendingIncoming.isZero() || !pendingOutgoing.isZero() +} \ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt index c1be674..466246d 100644 --- a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt @@ -30,7 +30,12 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL import net.taler.common.fadeIn import net.taler.wallet.MainViewModel +import net.taler.wallet.balances.BalanceState.Error +import net.taler.wallet.balances.BalanceState.Loading +import net.taler.wallet.balances.BalanceState.None +import net.taler.wallet.balances.BalanceState.Success import net.taler.wallet.databinding.FragmentBalancesBinding +import net.taler.wallet.showError interface BalanceClickListener { fun onBalanceClick(currency: String) @@ -59,20 +64,30 @@ class BalancesFragment : Fragment(), addItemDecoration(DividerItemDecoration(context, VERTICAL)) } - model.balances.observe(viewLifecycleOwner) { + model.balanceManager.state.observe(viewLifecycleOwner) { onBalancesChanged(it) } } - private fun onBalancesChanged(balances: List) { - beginDelayedTransition(view as ViewGroup) - if (balances.isEmpty()) { - ui.mainEmptyState.visibility = VISIBLE - ui.mainList.visibility = GONE - } else { - balancesAdapter.setItems(balances) - ui.mainEmptyState.visibility = INVISIBLE - ui.mainList.fadeIn() + private fun onBalancesChanged(state: BalanceState) { + model.showProgressBar.value = false + when (state) { + is None -> {} + is Loading -> { + model.showProgressBar.value = true + } + is Success -> { + beginDelayedTransition(view as ViewGroup) + if (state.balances.isEmpty()) { + ui.mainEmptyState.visibility = VISIBLE + ui.mainList.visibility = GONE + } else { + balancesAdapter.setItems(state.balances) + ui.mainEmptyState.visibility = INVISIBLE + ui.mainList.fadeIn() + } + } + is Error -> showError(state.error) } } 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 2f00bf8..b898bec 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt @@ -43,10 +43,9 @@ import net.taler.common.fadeOut 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.databinding.FragmentTransactionsBinding import net.taler.wallet.showError -import net.taler.wallet.transactions.TransactionMajorState.* -import net.taler.wallet.transactions.TransactionMinorState.* interface OnTransactionClickListener { fun onTransactionClicked(transaction: Transaction) @@ -108,7 +107,9 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. } }) - model.balances.observe(viewLifecycleOwner) { balances -> + model.balanceManager.state.observe(viewLifecycleOwner) { state -> + if (state !is Success) return@observe + 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 -> -- cgit v1.2.3