Updated on 2026-08-14
This commit is contained in:
parent
bde99e272b
commit
d8348e54df
14 changed files with 448 additions and 27 deletions
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.tangem.core.ui.utils
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.util.Currency
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
object BigDecimalFormatter {
|
||||||
|
|
||||||
|
private const val TEMP_CURRENCY_CODE = "USD"
|
||||||
|
|
||||||
|
fun formatCryptoAmount(
|
||||||
|
cryptoAmount: BigDecimal,
|
||||||
|
cryptoCurrency: String,
|
||||||
|
decimals: Int,
|
||||||
|
locale: Locale = Locale.getDefault(),
|
||||||
|
): String {
|
||||||
|
val formatterCurrency = getCurrency(cryptoCurrency)
|
||||||
|
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
|
||||||
|
currency = formatterCurrency
|
||||||
|
maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
|
||||||
|
minimumFractionDigits = 2
|
||||||
|
roundingMode = RoundingMode.DOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatter.format(cryptoAmount)
|
||||||
|
.replace(formatterCurrency.getSymbol(locale), cryptoCurrency)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatFiatAmount(
|
||||||
|
fiatAmount: BigDecimal,
|
||||||
|
fiatCurrencyCode: String,
|
||||||
|
fiatCurrencySymbol: String,
|
||||||
|
locale: Locale = Locale.getDefault(),
|
||||||
|
): String {
|
||||||
|
val formatterCurrency = getCurrency(fiatCurrencyCode)
|
||||||
|
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
|
||||||
|
currency = formatterCurrency
|
||||||
|
maximumFractionDigits = 2
|
||||||
|
minimumFractionDigits = 2
|
||||||
|
roundingMode = RoundingMode.HALF_UP
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatter.format(fiatAmount)
|
||||||
|
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatPercent(percent: BigDecimal, useAbsoluteValue: Boolean, locale: Locale = Locale.getDefault()): String {
|
||||||
|
val formatter = NumberFormat.getPercentInstance(locale).apply {
|
||||||
|
maximumFractionDigits = 2
|
||||||
|
minimumFractionDigits = 2
|
||||||
|
roundingMode = RoundingMode.HALF_UP
|
||||||
|
}
|
||||||
|
val value = if (useAbsoluteValue) percent.abs() else percent
|
||||||
|
|
||||||
|
return formatter.format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCurrency(code: String): Currency {
|
||||||
|
return runCatching { Currency.getInstance(code) }
|
||||||
|
.getOrElse { e ->
|
||||||
|
// Currency code is not valid ISO 4217 code
|
||||||
|
if (e is IllegalArgumentException) {
|
||||||
|
Currency.getInstance(TEMP_CURRENCY_CODE)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package com.tangem.domain.tokens.model
|
package com.tangem.domain.tokens.model
|
||||||
|
|
||||||
import arrow.core.NonEmptySet
|
|
||||||
|
|
||||||
data class NetworkGroup(
|
data class NetworkGroup(
|
||||||
val networkId: Network.ID,
|
val networkId: Network.ID,
|
||||||
val name: String,
|
val name: String,
|
||||||
val tokens: NonEmptySet<TokenStatus>,
|
val tokens: Set<TokenStatus>,
|
||||||
)
|
)
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.tangem.domain.tokens.model
|
package com.tangem.domain.tokens.model
|
||||||
|
|
||||||
import arrow.core.NonEmptySet
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
sealed class TokenList {
|
sealed class TokenList {
|
||||||
|
|
@ -8,13 +7,13 @@ sealed class TokenList {
|
||||||
open val sortedBy: SortType = SortType.NONE
|
open val sortedBy: SortType = SortType.NONE
|
||||||
|
|
||||||
data class GroupedByNetwork(
|
data class GroupedByNetwork(
|
||||||
val groups: NonEmptySet<NetworkGroup>,
|
val groups: Set<NetworkGroup>,
|
||||||
override val totalFiatBalance: FiatBalance,
|
override val totalFiatBalance: FiatBalance,
|
||||||
override val sortedBy: SortType,
|
override val sortedBy: SortType,
|
||||||
) : TokenList()
|
) : TokenList()
|
||||||
|
|
||||||
data class Ungrouped(
|
data class Ungrouped(
|
||||||
val tokens: NonEmptySet<TokenStatus>,
|
val tokens: Set<TokenStatus>,
|
||||||
override val totalFiatBalance: FiatBalance,
|
override val totalFiatBalance: FiatBalance,
|
||||||
override val sortedBy: SortType,
|
override val sortedBy: SortType,
|
||||||
) : TokenList()
|
) : TokenList()
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ internal class TokenListOperations(
|
||||||
): NonEmptySet<NetworkGroup> {
|
): NonEmptySet<NetworkGroup> {
|
||||||
val groupsWithSortedTokens = groupTokens(tokens, networks)
|
val groupsWithSortedTokens = groupTokens(tokens, networks)
|
||||||
.map { group ->
|
.map { group ->
|
||||||
group.copy(tokens = sortTokensByBalance(group.tokens))
|
group.copy(tokens = sortTokensByBalance(group.tokens as NonEmptySet<TokenStatus>))
|
||||||
}
|
}
|
||||||
.toNonEmptySet()
|
.toNonEmptySet()
|
||||||
val sortedGroups = if (isAnyTokenLoading) {
|
val sortedGroups = if (isAnyTokenLoading) {
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ internal object MockTokenLists {
|
||||||
|
|
||||||
val loadingUngroupedTokenList = with(ungroupedTokenList) {
|
val loadingUngroupedTokenList = with(ungroupedTokenList) {
|
||||||
copy(
|
copy(
|
||||||
tokens = tokens.map { it.copy(value = TokenStatus.Loading) }.toNonEmptySet(),
|
tokens = tokens.map { it.copy(value = TokenStatus.Loading) }.toSet(),
|
||||||
totalFiatBalance = TokenList.FiatBalance.Loading,
|
totalFiatBalance = TokenList.FiatBalance.Loading,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ internal object MockTokenLists {
|
||||||
|
|
||||||
val sortedGroupedTokenList: TokenList.GroupedByNetwork
|
val sortedGroupedTokenList: TokenList.GroupedByNetwork
|
||||||
get() {
|
get() {
|
||||||
val groups = sortedNetworksGroups
|
val groups = sortedNetworksGroups.toSet()
|
||||||
|
|
||||||
return groupedTokenList.copy(
|
return groupedTokenList.copy(
|
||||||
groups = groups,
|
groups = groups,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ dependencies {
|
||||||
implementation(deps.kotlin.immutable.collections)
|
implementation(deps.kotlin.immutable.collections)
|
||||||
implementation(deps.tangem.card.core)
|
implementation(deps.tangem.card.core)
|
||||||
implementation(deps.tangem.blockchain)
|
implementation(deps.tangem.blockchain)
|
||||||
|
implementation(deps.arrow.core)
|
||||||
|
|
||||||
/** DI */
|
/** DI */
|
||||||
implementation(deps.hilt.android)
|
implementation(deps.hilt.android)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
|
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
|
||||||
import com.tangem.core.ui.components.transactions.TransactionState
|
import com.tangem.core.ui.components.transactions.TransactionState
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||||
import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
|
import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
|
||||||
|
|
@ -51,14 +52,16 @@ internal object WalletPreviewData {
|
||||||
onClick = null,
|
onClick = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val wallets = mapOf(
|
||||||
|
UserWalletId(stringValue = "123") to walletCardContentState,
|
||||||
|
UserWalletId(stringValue = "321") to walletCardLoadingState,
|
||||||
|
UserWalletId(stringValue = "42") to walletCardHiddenContentState,
|
||||||
|
UserWalletId(stringValue = "24") to walletCardErrorState,
|
||||||
|
)
|
||||||
|
|
||||||
val walletListConfig = WalletsListConfig(
|
val walletListConfig = WalletsListConfig(
|
||||||
selectedWalletIndex = 0,
|
selectedWalletIndex = 0,
|
||||||
wallets = persistentListOf(
|
wallets = wallets.values.toPersistentList(),
|
||||||
walletCardContentState,
|
|
||||||
walletCardLoadingState,
|
|
||||||
walletCardHiddenContentState,
|
|
||||||
walletCardErrorState,
|
|
||||||
),
|
|
||||||
onWalletChange = {},
|
onWalletChange = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||||
|
|
||||||
|
import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount
|
||||||
|
import com.tangem.domain.tokens.model.TokenList
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
|
internal class FiatBalanceToWalletCardConverter(
|
||||||
|
private val currentState: WalletCardState,
|
||||||
|
private val isWalletContentHidden: Boolean,
|
||||||
|
private val fiatCurrencyCode: String,
|
||||||
|
private val fiatCurrencySymbol: String,
|
||||||
|
) : Converter<TokenList.FiatBalance, WalletCardState> {
|
||||||
|
|
||||||
|
override fun convert(value: TokenList.FiatBalance): WalletCardState {
|
||||||
|
// TODO: [REDACTED_JIRA]
|
||||||
|
return when (value) {
|
||||||
|
is TokenList.FiatBalance.Loading -> with(currentState) {
|
||||||
|
WalletCardState.Loading(id, title, additionalInfo, imageResId, onClick)
|
||||||
|
}
|
||||||
|
is TokenList.FiatBalance.Failed -> with(currentState) {
|
||||||
|
WalletCardState.Error(id, title, additionalInfo, imageResId, onClick)
|
||||||
|
}
|
||||||
|
is TokenList.FiatBalance.Loaded -> with(currentState) {
|
||||||
|
if (isWalletContentHidden) {
|
||||||
|
WalletCardState.HiddenContent(id, title, additionalInfo, imageResId, onClick)
|
||||||
|
} else {
|
||||||
|
WalletCardState.Content(
|
||||||
|
id = id,
|
||||||
|
title = title,
|
||||||
|
additionalInfo = additionalInfo,
|
||||||
|
imageResId = imageResId,
|
||||||
|
onClick = onClick,
|
||||||
|
balance = formatFiatAmount(value.amount, fiatCurrencyCode, fiatCurrencySymbol),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||||
|
|
||||||
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState.MultiCurrencyItem
|
||||||
|
import kotlinx.collections.immutable.PersistentList
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
|
internal object LoadingItemsProvider {
|
||||||
|
|
||||||
|
fun getLoadingMultiCurrencyTokens(): PersistentList<MultiCurrencyItem> {
|
||||||
|
return List(size = 5) { TokenItemState.Loading }
|
||||||
|
.map { MultiCurrencyItem.Token(it) }
|
||||||
|
.toPersistentList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||||
|
|
||||||
|
import com.tangem.domain.tokens.error.TokensError
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
|
internal class TokenErrorToWalletStateConverter(
|
||||||
|
private val currentState: WalletStateHolder,
|
||||||
|
) : Converter<TokensError, WalletStateHolder> {
|
||||||
|
|
||||||
|
// TODO: [REDACTED_JIRA]
|
||||||
|
override fun convert(value: TokensError): WalletStateHolder {
|
||||||
|
return currentState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||||
|
|
||||||
|
import com.tangem.domain.tokens.model.NetworkGroup
|
||||||
|
import com.tangem.domain.tokens.model.TokenList
|
||||||
|
import com.tangem.domain.tokens.model.TokenStatus
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState.MultiCurrencyItem
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.PersistentList
|
||||||
|
import kotlinx.collections.immutable.mutate
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
internal class TokenListToContentItemsConverter(
|
||||||
|
isWalletContentHidden: Boolean,
|
||||||
|
fiatCurrencyCode: String,
|
||||||
|
fiatCurrencySymbol: String,
|
||||||
|
) : Converter<TokenList, ImmutableList<MultiCurrencyItem>> {
|
||||||
|
|
||||||
|
private val tokenStatusConverter = TokenStatusToTokenItemConverter(
|
||||||
|
isWalletContentHidden,
|
||||||
|
fiatCurrencyCode,
|
||||||
|
fiatCurrencySymbol,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun convert(value: TokenList): ImmutableList<MultiCurrencyItem> {
|
||||||
|
return when (value) {
|
||||||
|
is TokenList.GroupedByNetwork -> value.mapToMultiCurrencyItems()
|
||||||
|
is TokenList.Ungrouped -> value.mapToMultiCurrencyItems()
|
||||||
|
is TokenList.NotInitialized -> getLoadingMultiCurrencyTokens()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TokenList.GroupedByNetwork.mapToMultiCurrencyItems(): PersistentList<MultiCurrencyItem> {
|
||||||
|
return groups.fold(initial = persistentListOf()) { acc, group ->
|
||||||
|
acc.mutate { it.addGroup(group) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TokenList.Ungrouped.mapToMultiCurrencyItems(): PersistentList<MultiCurrencyItem> {
|
||||||
|
return tokens.fold(initial = persistentListOf()) { acc, token ->
|
||||||
|
acc.mutate { it.addToken(token) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<MultiCurrencyItem>.addGroup(group: NetworkGroup): List<MultiCurrencyItem> {
|
||||||
|
this.add(MultiCurrencyItem.NetworkGroupTitle(group.name))
|
||||||
|
|
||||||
|
group.tokens.forEach { token ->
|
||||||
|
this.addToken(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<MultiCurrencyItem>.addToken(token: TokenStatus): List<MultiCurrencyItem> {
|
||||||
|
val tokenItemState = tokenStatusConverter.convert(token)
|
||||||
|
|
||||||
|
this.add(MultiCurrencyItem.Token(tokenItemState))
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||||
|
|
||||||
|
import com.tangem.domain.tokens.model.TokenList
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.MultiCurrencyContent
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.SingleCurrencyContent
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletsListConfig
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
|
internal class TokenListToWalletStateConverter(
|
||||||
|
private val currentState: WalletStateHolder,
|
||||||
|
private val isWalletContentHidden: Boolean,
|
||||||
|
private val fiatCurrencyCode: String,
|
||||||
|
private val fiatCurrencySymbol: String,
|
||||||
|
) : Converter<TokenList, WalletStateHolder> {
|
||||||
|
|
||||||
|
override fun convert(value: TokenList): WalletStateHolder {
|
||||||
|
return when (currentState) {
|
||||||
|
is MultiCurrencyContent -> currentState.updateWithTokenList(value)
|
||||||
|
is SingleCurrencyContent -> currentState.updateWithTokenList(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MultiCurrencyContent.updateWithTokenList(tokenList: TokenList): MultiCurrencyContent {
|
||||||
|
val converter = TokenListToContentItemsConverter(isWalletContentHidden, fiatCurrencyCode, fiatCurrencySymbol)
|
||||||
|
|
||||||
|
return this.copy(
|
||||||
|
walletsListConfig = updateSelectedWallet(tokenList.totalFiatBalance),
|
||||||
|
contentItems = converter.convert(tokenList),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SingleCurrencyContent.updateWithTokenList(tokenList: TokenList): SingleCurrencyContent {
|
||||||
|
return this.copy(
|
||||||
|
walletsListConfig = updateSelectedWallet(tokenList.totalFiatBalance),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun WalletStateHolder.updateSelectedWallet(fiatBalance: TokenList.FiatBalance): WalletsListConfig {
|
||||||
|
val selectedWalletIndex = walletsListConfig.selectedWalletIndex
|
||||||
|
val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex]
|
||||||
|
val converter = FiatBalanceToWalletCardConverter(
|
||||||
|
selectedWalletCard,
|
||||||
|
isWalletContentHidden,
|
||||||
|
fiatCurrencyCode,
|
||||||
|
fiatCurrencySymbol,
|
||||||
|
)
|
||||||
|
|
||||||
|
return walletsListConfig.copy(
|
||||||
|
wallets = walletsListConfig.wallets
|
||||||
|
.toPersistentList()
|
||||||
|
.set(selectedWalletIndex, converter.convert(fiatBalance)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
|
||||||
|
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||||
|
import com.tangem.domain.tokens.model.TokenStatus
|
||||||
|
import com.tangem.feature.wallet.impl.R
|
||||||
|
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
internal class TokenStatusToTokenItemConverter(
|
||||||
|
private val isWalletContentHidden: Boolean,
|
||||||
|
private val fiatCurrencyCode: String,
|
||||||
|
private val fiatCurrencySymbol: String,
|
||||||
|
) : Converter<TokenStatus, TokenItemState> {
|
||||||
|
|
||||||
|
private val TokenStatus.networkIconResId: Int?
|
||||||
|
@DrawableRes get() {
|
||||||
|
// TODO: [REDACTED_JIRA]
|
||||||
|
return if (isCoin) null else R.drawable.img_eth_22
|
||||||
|
}
|
||||||
|
|
||||||
|
private val TokenStatus.tokenIconResId: Int
|
||||||
|
@DrawableRes get() {
|
||||||
|
// TODO: [REDACTED_JIRA]
|
||||||
|
return R.drawable.img_eth_22
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convert(value: TokenStatus): TokenItemState {
|
||||||
|
return when (value.value) {
|
||||||
|
is TokenStatus.Loading -> TokenItemState.Loading
|
||||||
|
is TokenStatus.Loaded,
|
||||||
|
is TokenStatus.Custom,
|
||||||
|
-> value.mapToTokenItemState()
|
||||||
|
// TODO: Add other token item states, currently not designed
|
||||||
|
is TokenStatus.MissedDerivation,
|
||||||
|
is TokenStatus.NoAccount,
|
||||||
|
is TokenStatus.Unreachable,
|
||||||
|
-> value.mapToUnreachableTokenItemState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TokenStatus.mapToTokenItemState(): TokenItemState.Content {
|
||||||
|
return TokenItemState.Content(
|
||||||
|
id = this.id.value,
|
||||||
|
name = this.name,
|
||||||
|
tokenIconUrl = this.iconUrl,
|
||||||
|
tokenIconResId = this.tokenIconResId,
|
||||||
|
networkIconResId = this.networkIconResId,
|
||||||
|
amount = getFormattedAmount(),
|
||||||
|
hasPending = value.hasTransactionsInProgress,
|
||||||
|
tokenOptions = if (isWalletContentHidden) {
|
||||||
|
TokenItemState.TokenOptionsState.Hidden(getPriceChangeConfig())
|
||||||
|
} else {
|
||||||
|
TokenItemState.TokenOptionsState.Visible(
|
||||||
|
fiatAmount = getFormattedFiatAmount(),
|
||||||
|
priceChange = getPriceChangeConfig(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TokenStatus.getFormattedAmount(): String {
|
||||||
|
val amount = value.amount ?: return UNKNOWN_AMOUNT_SIGN
|
||||||
|
|
||||||
|
return BigDecimalFormatter.formatCryptoAmount(amount, symbol, decimals)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TokenStatus.getFormattedFiatAmount(): String {
|
||||||
|
val fiatAmount = value.fiatAmount ?: return UNKNOWN_AMOUNT_SIGN
|
||||||
|
|
||||||
|
return BigDecimalFormatter.formatFiatAmount(fiatAmount, fiatCurrencyCode, fiatCurrencySymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TokenStatus.mapToUnreachableTokenItemState() = TokenItemState.Unreachable(
|
||||||
|
id = this.id.value,
|
||||||
|
name = this.name,
|
||||||
|
tokenIconUrl = this.iconUrl,
|
||||||
|
tokenIconResId = this.tokenIconResId,
|
||||||
|
networkIconResId = this.networkIconResId,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun TokenStatus.getPriceChangeConfig(): PriceChangeConfig {
|
||||||
|
val priceChange = value.priceChange
|
||||||
|
?: return PriceChangeConfig(UNKNOWN_AMOUNT_SIGN, PriceChangeConfig.Type.DOWN)
|
||||||
|
|
||||||
|
return PriceChangeConfig(
|
||||||
|
valueInPercent = BigDecimalFormatter.formatPercent(priceChange, useAbsoluteValue = true),
|
||||||
|
type = priceChange.getPriceChangeType(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BigDecimal?.getPriceChangeType(): PriceChangeConfig.Type {
|
||||||
|
return when {
|
||||||
|
this == null -> PriceChangeConfig.Type.DOWN
|
||||||
|
this < BigDecimal.ZERO -> PriceChangeConfig.Type.DOWN
|
||||||
|
else -> PriceChangeConfig.Type.UP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val UNKNOWN_AMOUNT_SIGN = "—"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,16 +6,26 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import arrow.core.Either
|
||||||
import com.tangem.common.doOnFailure
|
import com.tangem.common.doOnFailure
|
||||||
import com.tangem.common.doOnSuccess
|
import com.tangem.common.doOnSuccess
|
||||||
import com.tangem.domain.card.ScanCardProcessor
|
import com.tangem.domain.card.ScanCardProcessor
|
||||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||||
|
import com.tangem.domain.tokens.error.TokensError
|
||||||
|
import com.tangem.domain.tokens.model.TokenList
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.utils.TokenErrorToWalletStateConverter
|
||||||
|
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
@ -27,7 +37,6 @@ import kotlin.properties.Delegates
|
||||||
*/
|
*/
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
internal class WalletViewModel @Inject constructor(
|
internal class WalletViewModel @Inject constructor(
|
||||||
@Suppress("unused") // TODO: [REDACTED_JIRA]
|
|
||||||
private val getTokenListUseCase: GetTokenListUseCase,
|
private val getTokenListUseCase: GetTokenListUseCase,
|
||||||
private val scanCardProcessor: ScanCardProcessor,
|
private val scanCardProcessor: ScanCardProcessor,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
|
@ -40,14 +49,29 @@ internal class WalletViewModel @Inject constructor(
|
||||||
var uiState by mutableStateOf(getInitialState())
|
var uiState by mutableStateOf(getInitialState())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private var getTokenListJob: Job? = null
|
||||||
|
set(value) {
|
||||||
|
field?.cancel()
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData
|
// TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData
|
||||||
private fun getInitialState(): WalletStateHolder = WalletPreviewData.multicurrencyWalletScreenState.copy(
|
private fun getInitialState(): WalletStateHolder {
|
||||||
onBackClick = ::onBackClick,
|
val state = WalletPreviewData.multicurrencyWalletScreenState.copy(
|
||||||
topBarConfig = createTopBarConfig(),
|
onBackClick = ::onBackClick,
|
||||||
walletsListConfig = WalletPreviewData.multicurrencyWalletScreenState.walletsListConfig.copy(
|
topBarConfig = createTopBarConfig(),
|
||||||
onWalletChange = ::selectWallet,
|
walletsListConfig = WalletPreviewData.multicurrencyWalletScreenState.walletsListConfig.copy(
|
||||||
),
|
onWalletChange = ::selectWallet,
|
||||||
)
|
),
|
||||||
|
contentItems = getLoadingMultiCurrencyTokens(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val selectedWalletIndex = state.walletsListConfig.selectedWalletIndex
|
||||||
|
val selectedWalletId = WalletPreviewData.wallets.keys.elementAt(selectedWalletIndex)
|
||||||
|
launchGetTokenListJob(selectedWalletId)
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
private fun onBackClick() {
|
private fun onBackClick() {
|
||||||
router.popBackStack()
|
router.popBackStack()
|
||||||
|
|
@ -74,14 +98,45 @@ internal class WalletViewModel @Inject constructor(
|
||||||
|
|
||||||
Log.i("WalletViewModel", "selectWallet: $index")
|
Log.i("WalletViewModel", "selectWallet: $index")
|
||||||
|
|
||||||
uiState = if (index % 2 == 0) {
|
uiState = when (val state = uiState) {
|
||||||
WalletPreviewData.multicurrencyWalletScreenState.copy(
|
is WalletStateHolder.MultiCurrencyContent -> state.copy(
|
||||||
walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index),
|
walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index),
|
||||||
)
|
)
|
||||||
} else {
|
is WalletStateHolder.SingleCurrencyContent -> state.copy(
|
||||||
WalletPreviewData.singleWalletScreenState.copy(
|
|
||||||
walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index),
|
walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val selectedWalletId = WalletPreviewData.wallets.keys.elementAt(index)
|
||||||
|
launchGetTokenListJob(selectedWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchGetTokenListJob(userWalletId: UserWalletId) {
|
||||||
|
getTokenListJob = getTokenListUseCase(userWalletId)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.mapLatest(::updateStateWithTokenListOrError)
|
||||||
|
.onEach { uiState = it }
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateStateWithTokenListOrError(tokenList: Either<TokensError, TokenList>): WalletStateHolder {
|
||||||
|
val updateStateWithError = { error: TokensError ->
|
||||||
|
val converter = TokenErrorToWalletStateConverter(uiState)
|
||||||
|
|
||||||
|
converter.convert(error)
|
||||||
|
}
|
||||||
|
val updateState = { list: TokenList ->
|
||||||
|
val converter = TokenListToWalletStateConverter(
|
||||||
|
uiState,
|
||||||
|
isWalletContentHidden = false, // TODO: [REDACTED_JIRA]
|
||||||
|
fiatCurrencyCode = "USD", // TODO: [REDACTED_JIRA]
|
||||||
|
fiatCurrencySymbol = "$", // TODO: [REDACTED_JIRA]
|
||||||
|
)
|
||||||
|
|
||||||
|
converter.convert(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenList.fold(ifLeft = updateStateWithError, ifRight = updateState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue