diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/TotalBalance.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/TotalBalance.kt new file mode 100644 index 0000000000..31cc8a8d7f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/TotalBalance.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.features.wallet.models + +import com.tangem.tap.common.redux.global.FiatCurrencyName +import java.math.BigDecimal + +data class TotalBalance( + val state: State, + val fiatAmount: BigDecimal, + val fiatCurrencyName: FiatCurrencyName, +) { + enum class State { + Loading, + Failed, + Success + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 278ccee818..b9e63b7190 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -20,6 +20,7 @@ import com.tangem.tap.domain.tokens.BlockchainNetwork import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.toPendingTransactions import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken import com.tangem.tap.features.wallet.ui.BalanceStatus @@ -44,6 +45,7 @@ data class WalletState( val primaryBlockchain: Blockchain? = null, val primaryToken: Token? = null, val isTestnet: Boolean = false, + val totalBalance: TotalBalance? = null, ) : StateType { // if you do not delegate - the application crashes on startup, @@ -196,18 +198,20 @@ data class WalletState( return copy(wallets = replaceWalletInWallets(walletStore)) } - fun updateWalletStores(walletStores: List): WalletState { - val walletStores = walletStores.toMutableList() + private fun updateWalletStores(walletStores: List): WalletState { + val walletStoresMutable = walletStores.toMutableList() val updatedWallets = wallets.map { oldWalletStore -> - val walletStore = walletStores.find { it.blockchainNetwork == oldWalletStore.blockchainNetwork } + val walletStore = walletStoresMutable.find { + it.blockchainNetwork == oldWalletStore.blockchainNetwork + } if (walletStore != null) { - walletStores.remove(walletStore) + walletStoresMutable.remove(walletStore) walletStore } else { oldWalletStore } } - return copy(wallets = updatedWallets + walletStores) + return copy(wallets = updatedWallets + walletStoresMutable) } fun removeWallet(walletData: WalletData?): WalletState { @@ -268,6 +272,14 @@ data class WalletState( ) } } + + fun updateTotalBalance( + totalBalance: TotalBalance + ): WalletState { + return this.copy( + totalBalance = totalBalance + ) + } } sealed class WalletDialog : StateDialog { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index acd7c4d8b7..1809910e90 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -9,6 +9,7 @@ import com.tangem.tap.common.extensions.toFormattedCurrencyString import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.BlockchainNetwork +import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.removeUnknownTransactions import com.tangem.tap.features.wallet.models.toPendingTransactions import com.tangem.tap.features.wallet.redux.* @@ -16,6 +17,7 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.store +import java.math.BigDecimal import java.math.RoundingMode class OnWalletLoadedReducer { @@ -33,13 +35,12 @@ class OnWalletLoadedReducer { blockchainNetwork: BlockchainNetwork, walletState: WalletState ): WalletState { + val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState + val fiatCurrencySymbol = store.state.globalState.appCurrency val exchangeManager = store.state.globalState.currencyExchangeManager val coinAmountValue = wallet.amounts[AmountType.Coin]?.value - if (walletState.getWalletData(blockchainNetwork) == null) { - return walletState - } val formattedAmount = coinAmountValue?.toFormattedCurrencyString( wallet.blockchain.decimals(), wallet.blockchain.currency @@ -54,10 +55,9 @@ class OnWalletLoadedReducer { } else { BalanceStatus.VerifiedOnline } - val walletData = walletState.getWalletData(blockchainNetwork) - val fiatAmount = walletData?.fiatRate?.let { coinAmountValue?.toFiatValue(it) } - val newWalletData = walletData?.copy( + val fiatAmount = walletData.fiatRate?.let { coinAmountValue?.toFiatValue(it) } + val newWalletData = walletData.copy( currencyData = walletData.currencyData.copy( status = balanceStatus, currency = wallet.blockchain.fullName, currencySymbol = wallet.blockchain.currency, @@ -87,7 +87,7 @@ class OnWalletLoadedReducer { val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate) } - val tokenSendButton = newWalletData?.shouldEnableTokenSendButton() == true + val tokenSendButton = newWalletData.shouldEnableTokenSendButton() && tokenPendingTransactions.isEmpty() tokenWalletData?.copy( currencyData = tokenWalletData.currencyData.copy( @@ -106,18 +106,27 @@ class OnWalletLoadedReducer { tradeCryptoState = TradeCryptoState.from(exchangeManager, tokenWalletData), ) } - val newWallets = (tokens + newWalletData).mapNotNull { it } + val newWallets = tokens + newWalletData val wallets = walletState.replaceSomeWallets((newWallets)) + val totalBalance = TotalBalance( + state = wallets.findTotalBalanceState(), + fiatAmount = wallets.calculateTotalFiatAmount(), + fiatCurrencyName = fiatCurrencySymbol, + ) + val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { ProgressState.Loading } else { ProgressState.Done } - val newState = walletState.updateWalletsData(wallets) - return newState.copy( - state = state, error = null - ) + return walletState + .updateWalletsData(wallets) + .updateTotalBalance(totalBalance) + .copy( + state = state, + error = null + ) } private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { @@ -183,4 +192,48 @@ class OnWalletLoadedReducer { state = ProgressState.Done, error = null ) } + + private fun List.findTotalBalanceState(): TotalBalance.State { + return this.mapToTotalBalanceState() + .fold(initial = TotalBalance.State.Loading) { accState, newState -> + accState or newState + } + } + + private fun List.calculateTotalFiatAmount(): BigDecimal { + return this.map { it.currencyData.fiatAmount ?: BigDecimal.ZERO } + .reduce(BigDecimal::plus) + } + + private fun List.mapToTotalBalanceState(): List { + return this.map { + when (it.currencyData.status) { + BalanceStatus.VerifiedOnline, + BalanceStatus.SameCurrencyTransactionInProgress, + BalanceStatus.TransactionInProgress -> TotalBalance.State.Success + BalanceStatus.Unreachable, + BalanceStatus.NoAccount, + BalanceStatus.EmptyCard, + BalanceStatus.UnknownBlockchain -> TotalBalance.State.Failed + BalanceStatus.Loading, + null -> TotalBalance.State.Loading + } + } + } + + infix fun TotalBalance.State.or(newState: TotalBalance.State): TotalBalance.State { + return when (this) { + TotalBalance.State.Loading -> when (newState) { + TotalBalance.State.Loading -> this + TotalBalance.State.Failed, + TotalBalance.State.Success -> newState + } + TotalBalance.State.Success, + TotalBalance.State.Failed -> when (newState) { + TotalBalance.State.Loading, + TotalBalance.State.Failed -> newState + TotalBalance.State.Success -> this + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 16c85a88fb..fc97a44777 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.currenciesRepository import com.tangem.tap.features.tokens.redux.TokensAction +import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletDialog import com.tangem.tap.features.wallet.redux.WalletState @@ -51,6 +52,7 @@ class MultiWalletView : WalletView { btnScanMultiwallet.show() rvMultiwallet.show() btnAddToken.show() + lCardTotalBalance.root.show() setupWalletCardNumber(binding) } @@ -97,6 +99,7 @@ class MultiWalletView : WalletView { val fragment = fragment ?: return val binding = binding ?: return + state.totalBalance?.let { handleTotalBalance(binding, it) } walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken) binding.btnAddToken.setOnClickListener { @@ -127,6 +130,20 @@ class MultiWalletView : WalletView { handleDialogs(state.walletDialog) } + private fun handleTotalBalance( + binding: FragmentWalletBinding, + totalBalance: TotalBalance, + ) = with(binding.lCardTotalBalance) { + val fiatAmountFormatted = with(totalBalance) { + "${fiatAmount.stripTrailingZeros()} $fiatCurrencyName" + } + tvBalance.text = fiatAmountFormatted + tvCurrencyName.text = totalBalance.fiatCurrencyName + tvCurrencyName.setOnClickListener { + // TODO: Open app currency selector + } + } + private fun handleErrorStates( state: WalletState, binding: FragmentWalletBinding, @@ -148,6 +165,8 @@ class MultiWalletView : WalletView { fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle) ) } + else -> { /* no-op */ + } } } diff --git a/app/src/main/res/layout/card_total_balance.xml b/app/src/main/res/layout/card_total_balance.xml new file mode 100644 index 0000000000..51fa4aecb7 --- /dev/null +++ b/app/src/main/res/layout/card_total_balance.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index bc98ff9e41..829a1f019b 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -101,16 +101,26 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/barrier" /> + + + app:layout_constraintTop_toBottomOf="@id/l_card_total_balance" /> - \ No newline at end of file + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9a0a6881fd..014599d80e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -382,4 +382,5 @@ Привет, команда поддержки, Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной. Информация ниже не является обязательной. Вы можете стереть её, если хотите. - \ No newline at end of file + Баланс + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3e70dee2f..82023e7837 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -206,4 +206,5 @@ To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ Reduce by %s XTZ No, send all - \ No newline at end of file + Total balance +