Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-14 11:48:31 +03:00
parent 5251fc5179
commit 25d468b7b5
9 changed files with 179 additions and 79 deletions

View file

@ -13,11 +13,12 @@ sealed interface TotalFiatBalance {
override val amount: BigDecimal? = null
}
data class Error(
override val amount: BigDecimal?,
) : TotalFiatBalance
object Failed : TotalFiatBalance {
override val amount: BigDecimal? = null
}
data class Loaded(
override val amount: BigDecimal,
val isWarning: Boolean,
) : TotalFiatBalance
}

View file

@ -22,13 +22,15 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
val walletsData = walletStores
.asSequence()
.flatMap { it.walletsData }
val calculateAmount = { walletsData.calculateTotalFiatAmount() }
when (walletsData.findStatus()) {
when (val status = walletsData.findStatus()) {
TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading
TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount())
TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(
amount = calculateAmount() ?: BigDecimal.ZERO,
TotalFiatBalanceStatus.Failed -> TotalFiatBalance.Failed
TotalFiatBalanceStatus.Warning,
TotalFiatBalanceStatus.Loaded,
-> TotalFiatBalance.Loaded(
amount = walletsData.calculateTotalFiatAmount(),
isWarning = status == TotalFiatBalanceStatus.Warning,
)
}
}
@ -50,29 +52,30 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
is WalletDataModel.SameCurrencyTransactionInProgress,
is WalletDataModel.TransactionInProgress,
is WalletDataModel.NoAccount,
-> if (walletData.isCustom && walletData.fiatRate == null) {
TotalFiatBalanceStatus.Error
-> if (walletData.isWarningCase()) {
TotalFiatBalanceStatus.Warning
} else {
TotalFiatBalanceStatus.Loaded
}
is WalletDataModel.Unreachable,
is WalletDataModel.MissedDerivation,
-> TotalFiatBalanceStatus.Error
-> TotalFiatBalanceStatus.Failed
is WalletDataModel.Loading -> TotalFiatBalanceStatus.Loading
}
}
}
private fun Sequence<WalletDataModel>.calculateTotalFiatAmount(): BigDecimal? {
private fun Sequence<WalletDataModel>.calculateTotalFiatAmount(): BigDecimal {
return this
.filterNot { it.isCustom && it.fiatRate == null }
.filterNot { it.isWarningCase() }
.map { walletData ->
walletData.fiatRate
?.takeUnless { walletData.status.isErrorStatus }
?.let { walletData.status.amount.toFiatValue(it) }
?: BigDecimal.ZERO
}
.reduce { acc, value ->
value?.let { acc?.plus(it) }
acc + value
}
}
@ -80,20 +83,22 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
prevStatus: TotalFiatBalanceStatus,
newStatus: TotalFiatBalanceStatus,
): TotalFiatBalanceStatus {
return when (prevStatus) {
TotalFiatBalanceStatus.Loading -> prevStatus
TotalFiatBalanceStatus.Loaded,
TotalFiatBalanceStatus.Error,
-> when (newStatus) {
TotalFiatBalanceStatus.Loading,
TotalFiatBalanceStatus.Error,
-> newStatus
TotalFiatBalanceStatus.Loaded -> prevStatus
}
}
return TotalFiatBalanceStatus[minOf(prevStatus.ordinal, newStatus.ordinal)]
}
private fun WalletDataModel.isWarningCase(): Boolean = isCustom && fiatRate == null
private enum class TotalFiatBalanceStatus {
Loading, Error, Loaded,
Loading,
Failed,
Warning,
Loaded,
;
companion object {
private val allValues = values()
operator fun get(index: Int) = allValues[index]
}
}
}

View file

@ -10,21 +10,11 @@ class WalletAnalyticsEventsMapper : Converter<TotalFiatBalance, AnalyticsParam.C
override fun convert(value: TotalFiatBalance): AnalyticsParam.CardBalanceState? {
return when (value) {
is TotalFiatBalance.Error -> {
if (value.amount == null) {
// if fiatAmount is null while ProgressState.Error it means error occurs when loading blockchain
AnalyticsParam.CardBalanceState.BlockchainError
} else {
// if fiatAmount is not null while ProgressState.Error it means couldn't load custom token amount
AnalyticsParam.CardBalanceState.CustomToken
}
}
is TotalFiatBalance.Loaded -> {
if (value.amount.isGreaterThan(BigDecimal.ZERO)) {
AnalyticsParam.CardBalanceState.Full
} else {
AnalyticsParam.CardBalanceState.Empty
}
is TotalFiatBalance.Failed -> AnalyticsParam.CardBalanceState.BlockchainError
is TotalFiatBalance.Loaded -> when {
value.isWarning -> AnalyticsParam.CardBalanceState.CustomToken
value.amount.isGreaterThan(BigDecimal.ZERO) -> AnalyticsParam.CardBalanceState.Full
else -> AnalyticsParam.CardBalanceState.Empty
}
is TotalFiatBalance.Loading -> null
}

View file

@ -86,8 +86,7 @@ internal class TotalBalanceCard @JvmOverloads constructor(
private fun updateState(status: TotalFiatBalance?, fiatCurrency: FiatCurrency, onChangeCurrencyClick: () -> Unit) {
state = when (status) {
null -> TotalBalanceCardState.Empty
is TotalFiatBalance.Error -> TotalBalanceCardState.Failure(
amount = status.amount,
is TotalFiatBalance.Failed -> TotalBalanceCardState.Failure(
fiatCurrency = fiatCurrency,
onChangeFiatCurrencyClick = onChangeCurrencyClick,
)
@ -97,8 +96,9 @@ internal class TotalBalanceCard @JvmOverloads constructor(
)
is TotalFiatBalance.Loaded -> TotalBalanceCardState.Success(
amount = status.amount,
fiatCurrency = fiatCurrency,
showWarning = status.isWarning,
onChangeFiatCurrencyClick = onChangeCurrencyClick,
fiatCurrency = fiatCurrency,
)
}
}
@ -138,8 +138,8 @@ private fun TotalBalanceCardContent(state: TotalBalanceCardState, modifier: Modi
)
}
},
failureText = {
AnimatedVisibility(visible = state is TotalBalanceCardState.Failure) {
warningText = {
AnimatedVisibility(visible = state.showWarning) {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.main_processing_full_amount),
@ -156,7 +156,7 @@ private fun TotalBalanceCardScaffold(
title: @Composable () -> Unit,
amount: @Composable () -> Unit,
currencySelector: @Composable () -> Unit,
failureText: @Composable () -> Unit,
warningText: @Composable () -> Unit,
modifier: Modifier = Modifier,
amountWeight: Float = 0.8f,
) {
@ -192,7 +192,7 @@ private fun TotalBalanceCardScaffold(
horizontal = TangemTheme.dimens.spacing16,
),
) {
failureText()
warningText()
}
SpacerH12()
}
@ -252,11 +252,13 @@ private fun buildAmountString(amount: BigDecimal?, fiatCurrencySymbol: String):
private sealed interface TotalBalanceCardState {
val amount: BigDecimal?
val onChangeFiatCurrencyClick: () -> Unit
val showWarning: Boolean
val fiatCurrency: FiatCurrency
val onChangeFiatCurrencyClick: () -> Unit
object Empty : TotalBalanceCardState {
override val amount: BigDecimal? = null
override val showWarning: Boolean = false
override val fiatCurrency: FiatCurrency = FiatCurrency.Default
override val onChangeFiatCurrencyClick: () -> Unit = { /* no-op */ }
}
@ -266,16 +268,20 @@ private sealed interface TotalBalanceCardState {
override val onChangeFiatCurrencyClick: () -> Unit,
) : TotalBalanceCardState {
override val amount: BigDecimal = BigDecimal.ZERO
override val showWarning: Boolean = false
}
data class Failure(
override val amount: BigDecimal?,
override val fiatCurrency: FiatCurrency,
override val onChangeFiatCurrencyClick: () -> Unit,
) : TotalBalanceCardState
) : TotalBalanceCardState {
override val amount: BigDecimal? = null
override val showWarning: Boolean = true
}
data class Success(
override val amount: BigDecimal,
override val showWarning: Boolean,
override val fiatCurrency: FiatCurrency,
override val onChangeFiatCurrencyClick: () -> Unit,
) : TotalBalanceCardState
@ -297,7 +303,6 @@ private fun TotalBalanceCardContentSample(modifier: Modifier = Modifier) {
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
TotalBalanceCardContent(
state = TotalBalanceCardState.Failure(
amount = BigDecimal("9917.72"),
onChangeFiatCurrencyClick = {},
fiatCurrency = FiatCurrency("USD", "USD", "$"),
),
@ -306,6 +311,7 @@ private fun TotalBalanceCardContentSample(modifier: Modifier = Modifier) {
TotalBalanceCardContent(
state = TotalBalanceCardState.Success(
amount = BigDecimal("9917.72"),
showWarning = false,
onChangeFiatCurrencyClick = {},
fiatCurrency = FiatCurrency("USD", "USD", "$"),
),

View file

@ -108,7 +108,7 @@ internal class WalletSelectorMiddleware {
) {
if (updatedWalletStores.isNotEmpty()) {
scope.launch(Dispatchers.Default) {
val updatedWallets = state.wallets.updateWalletStoresAndCalculateFiatBalance(updatedWalletStores)
val updatedWallets = state.wallets.calculateBalanceAndUpdateWalletStores(updatedWalletStores)
if (updatedWallets != state.wallets) {
store.dispatchOnMain(WalletSelectorAction.BalancesLoaded(updatedWallets))
}
@ -362,17 +362,17 @@ internal class WalletSelectorMiddleware {
}
}
private suspend fun List<UserWalletModel>.updateWalletStoresAndCalculateFiatBalance(
private suspend fun List<UserWalletModel>.calculateBalanceAndUpdateWalletStores(
walletStores: Map<UserWalletId, List<WalletStoreModel>>,
): List<UserWalletModel> {
return this
.associateWith { walletStores[it.id] }
.map { (wallet, walletStores) ->
wallet.updateWalletStoresAndCalculateFiatBalance(walletStores)
wallet.calculateBalanceAndUpdateWalletStores(walletStores)
}
}
private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance(
private suspend fun UserWalletModel.calculateBalanceAndUpdateWalletStores(
walletStores: List<WalletStoreModel>?,
): UserWalletModel {
return this.copy(

View file

@ -3,7 +3,6 @@ package com.tangem.tap.features.walletSelector.ui
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.toFormattedFiatValue
import com.tangem.tap.domain.model.TotalFiatBalance
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.features.walletSelector.redux.UserWalletModel
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem
@ -13,13 +12,12 @@ internal fun List<UserWalletModel>.toUiModels(appCurrency: FiatCurrency): Sequen
return this.asSequence().map { userWalletModel ->
with(userWalletModel) {
val balance = when (fiatBalance) {
is TotalFiatBalance.Error -> UserWalletItem.Balance.Error(
amount = fiatBalance.amount?.toFormattedFiatValue(appCurrency.symbol) ?: UNKNOWN_AMOUNT_SIGN,
)
is TotalFiatBalance.Failed -> UserWalletItem.Balance.Failed
is TotalFiatBalance.Loading -> UserWalletItem.Balance.Loading
is TotalFiatBalance.Loaded -> UserWalletItem.Balance.Loaded(
amount = fiatBalance.amount.toFormattedFiatValue(appCurrency.symbol),
showWarning = fiatBalance.isWarning,
)
is TotalFiatBalance.Loading -> UserWalletItem.Balance.Loading
}
when (type) {
is UserWalletModel.Type.MultiCurrency -> MultiCurrencyUserWalletItem(

View file

@ -11,6 +11,7 @@ internal object MockData {
id = UserWalletId("wallet_1"),
balance = UserWalletItem.Balance.Loaded(
amount = "6781.05 $",
showWarning = true,
),
name = "Wallet",
imageUrl = "https://app.tangem.com/cards/card_default.png",
@ -23,6 +24,7 @@ internal object MockData {
id = UserWalletId("wallet_4"),
balance = UserWalletItem.Balance.Loaded(
amount = "6781.05 $",
showWarning = false,
),
name = "Wallet",
imageUrl = "https://app.tangem.com/cards/card_default.png",

View file

@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -178,18 +179,13 @@ private fun RowScope.TokensInfo(isLocked: Boolean, balance: UserWalletItem.Balan
LockedPlaceholder()
} else {
when (balance) {
is UserWalletItem.Balance.Error -> {
is UserWalletItem.Balance.Failed,
is UserWalletItem.Balance.Loaded,
-> {
LoadedTokensInfo(
balanceAmount = balance.amount,
tokensCount = tokensCount,
showWarning = true,
)
}
is UserWalletItem.Balance.Loaded -> {
LoadedTokensInfo(
balanceAmount = balance.amount,
tokensCount = tokensCount,
showWarning = false,
showWarning = balance.showWarning,
)
}
is UserWalletItem.Balance.Loading -> {
@ -360,16 +356,23 @@ private fun CheckedWalletMark(modifier: Modifier = Modifier) {
}
}
@Preview(widthDp = 360, heightDp = 72, showBackground = true)
// region Preview
@Composable
private fun PreviewWalletItem() {
TangemTheme {
@Suppress("LongMethod") // preview
private fun WalletItemSample(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(TangemTheme.colors.background.primary),
) {
WalletItem(
wallet = MultiCurrencyUserWalletItem(
id = UserWalletId(value = null),
name = "Tangem Card",
imageUrl = "",
balance = UserWalletItem.Balance.Loaded("141212121888 BTC"),
balance = UserWalletItem.Balance.Loaded(
amount = "1412121218 BTC",
showWarning = false,
),
isLocked = false,
tokensCount = 2,
cardsInWallet = 1,
@ -379,5 +382,91 @@ private fun PreviewWalletItem() {
onWalletClick = {},
onWalletLongClick = {},
)
Divider(modifier = Modifier.padding(vertical = 4.dp))
WalletItem(
wallet = MultiCurrencyUserWalletItem(
id = UserWalletId(value = null),
name = "Tangem Card",
imageUrl = "",
balance = UserWalletItem.Balance.Loaded(
amount = "1412121218 BTC",
showWarning = true,
),
isLocked = false,
tokensCount = 2,
cardsInWallet = 1,
),
isSelected = false,
isChecked = false,
onWalletClick = {},
onWalletLongClick = {},
)
Divider(modifier = Modifier.padding(vertical = 4.dp))
WalletItem(
wallet = MultiCurrencyUserWalletItem(
id = UserWalletId(value = null),
name = "Tangem Card",
imageUrl = "",
balance = UserWalletItem.Balance.Loading,
isLocked = true,
tokensCount = 2,
cardsInWallet = 1,
),
isSelected = false,
isChecked = false,
onWalletClick = {},
onWalletLongClick = {},
)
Divider(modifier = Modifier.padding(vertical = 4.dp))
WalletItem(
wallet = MultiCurrencyUserWalletItem(
id = UserWalletId(value = null),
name = "Tangem Card",
imageUrl = "",
balance = UserWalletItem.Balance.Failed,
isLocked = false,
tokensCount = 2,
cardsInWallet = 1,
),
isSelected = true,
isChecked = false,
onWalletClick = {},
onWalletLongClick = {},
)
Divider(modifier = Modifier.padding(vertical = 4.dp))
WalletItem(
wallet = SingleCurrencyUserWalletItem(
id = UserWalletId(value = null),
name = "Tangem Card",
imageUrl = "",
balance = UserWalletItem.Balance.Loaded(
amount = "141212121888 BTC",
showWarning = false,
),
isLocked = false,
tokenName = "Bitcoin",
),
isSelected = false,
isChecked = true,
onWalletClick = {},
onWalletLongClick = {},
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun WalletItemPreview_Light() {
TangemTheme {
WalletItemSample()
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun WalletItemPreview_Dark() {
TangemTheme(isDark = true) {
WalletItemSample()
}
}
// endregion Preview

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.walletSelector.ui.model
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
internal sealed interface UserWalletItem {
val id: UserWalletId
@ -9,12 +10,20 @@ internal sealed interface UserWalletItem {
val balance: Balance
val isLocked: Boolean
sealed interface Balance {
object Loading : Balance
sealed class Balance {
open val amount: String = UNKNOWN_AMOUNT_SIGN
open val showWarning: Boolean = false
data class Error(val amount: String) : Balance
object Loading : Balance()
data class Loaded(val amount: String) : Balance
object Failed : Balance() {
override val showWarning: Boolean = true
}
data class Loaded(
override val amount: String,
override val showWarning: Boolean,
) : Balance()
}
}