Updated on 2026-08-14

This commit is contained in:
Tangem 2022-05-17 00:12:17 +03:00
parent 03428178ff
commit bd3ab3e125
8 changed files with 204 additions and 21 deletions

View file

@ -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
}
}

View file

@ -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<WalletStore>): WalletState {
val walletStores = walletStores.toMutableList()
private fun updateWalletStores(walletStores: List<WalletStore>): 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 {

View file

@ -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<WalletData>.findTotalBalanceState(): TotalBalance.State {
return this.mapToTotalBalanceState()
.fold(initial = TotalBalance.State.Loading) { accState, newState ->
accState or newState
}
}
private fun List<WalletData>.calculateTotalFiatAmount(): BigDecimal {
return this.map { it.currencyData.fiatAmount ?: BigDecimal.ZERO }
.reduce(BigDecimal::plus)
}
private fun List<WalletData>.mapToTotalBalanceState(): List<TotalBalance.State> {
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
}
}
}
}

View file

@ -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 */
}
}
}

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fl_balance"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minWidth="300dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_balance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="4dp"
android:elevation="3dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="18dp"
android:paddingBottom="18dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
>
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textColor="@color/iconGray"
android:textSize="14sp"
android:text="@string/main_page_balance"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/tv_currency_name" />
<TextView
android:id="@+id/tv_balance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/darkGray6"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/tv_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="$ 22 325.40" />
<TextView
android:id="@+id/tv_currency_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/darkGray1"
android:textSize="16sp"
app:drawableEndCompat="@drawable/ic_arrow_angle_down"
app:drawableTint="@color/darkGray1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="USD"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>

View file

@ -101,16 +101,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier" />
<include
android:id="@+id/l_card_total_balance"
layout="@layout/card_total_balance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/rv_warning_messages"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pending_transaction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:layout_goneMarginTop="12dp"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" />
app:layout_constraintTop_toBottomOf="@id/l_card_total_balance" />
<include
android:id="@+id/l_card_balance"
@ -204,4 +214,4 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -382,4 +382,5 @@
<string name="feedback_preface_support">Привет, команда поддержки,</string>
<string name="feedback_preface_tx_push_failed">Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной.</string>
<string name="feedback_data_collection_message">Информация ниже не является обязательной. Вы можете стереть её, если хотите.</string>
</resources>
<string name="main_page_balance">Баланс</string>
</resources>

View file

@ -206,4 +206,5 @@
<string name="xtz_withdrawal_message_warning">To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ</string>
<string name="xtz_withdrawal_message_reduce">Reduce by %s XTZ</string>
<string name="xtz_withdrawal_message_ignore">No, send all</string>
</resources>
<string name="main_page_balance">Total balance</string>
</resources>