Updated on 2026-08-14
This commit is contained in:
parent
5251fc5179
commit
25d468b7b5
9 changed files with 179 additions and 79 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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", "$"),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue