From d8348e54df0cbcc2e5cb7d8fa68c7c648bf4a5bf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Jul 2023 09:50:38 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../core/ui/utils/BigDecimalFormatter.kt | 71 ++++++++++++ .../domain/tokens/model/NetworkGroup.kt | 4 +- .../tangem/domain/tokens/model/TokenList.kt | 5 +- .../tokens/utils/TokenListOperations.kt | 2 +- .../domain/tokens/mock/MockTokenLists.kt | 4 +- features/wallet/impl/build.gradle.kts | 1 + .../presentation/common/WalletPreviewData.kt | 15 ++- .../utils/FiatBalanceToWalletCardConverter.kt | 40 +++++++ .../wallet/utils/LoadingItemsProvider.kt | 15 +++ .../utils/TokenErrorToWalletStateConverter.kt | 15 +++ .../utils/TokenListToContentItemsConverter.kt | 63 +++++++++++ .../utils/TokenListToWalletStateConverter.kt | 56 ++++++++++ .../utils/TokenStatusToTokenItemConverter.kt | 105 ++++++++++++++++++ .../wallet/viewmodels/WalletViewModel.kt | 79 +++++++++++-- 14 files changed, 448 insertions(+), 27 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenStatusToTokenItemConverter.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt new file mode 100644 index 0000000000..d10357cd54 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt index ea33623175..acc05ff374 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt @@ -1,9 +1,7 @@ package com.tangem.domain.tokens.model -import arrow.core.NonEmptySet - data class NetworkGroup( val networkId: Network.ID, val name: String, - val tokens: NonEmptySet, + val tokens: Set, ) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt index e94a89de44..3f1ffaaf75 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt @@ -1,6 +1,5 @@ package com.tangem.domain.tokens.model -import arrow.core.NonEmptySet import java.math.BigDecimal sealed class TokenList { @@ -8,13 +7,13 @@ sealed class TokenList { open val sortedBy: SortType = SortType.NONE data class GroupedByNetwork( - val groups: NonEmptySet, + val groups: Set, override val totalFiatBalance: FiatBalance, override val sortedBy: SortType, ) : TokenList() data class Ungrouped( - val tokens: NonEmptySet, + val tokens: Set, override val totalFiatBalance: FiatBalance, override val sortedBy: SortType, ) : TokenList() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt index 01a126b942..f5d8bf0d5c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt @@ -84,7 +84,7 @@ internal class TokenListOperations( ): NonEmptySet { val groupsWithSortedTokens = groupTokens(tokens, networks) .map { group -> - group.copy(tokens = sortTokensByBalance(group.tokens)) + group.copy(tokens = sortTokensByBalance(group.tokens as NonEmptySet)) } .toNonEmptySet() val sortedGroups = if (isAnyTokenLoading) { diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt index 78b4810d7b..c705c614f2 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt @@ -68,7 +68,7 @@ internal object MockTokenLists { val loadingUngroupedTokenList = with(ungroupedTokenList) { copy( - tokens = tokens.map { it.copy(value = TokenStatus.Loading) }.toNonEmptySet(), + tokens = tokens.map { it.copy(value = TokenStatus.Loading) }.toSet(), totalFiatBalance = TokenList.FiatBalance.Loading, ) } @@ -91,7 +91,7 @@ internal object MockTokenLists { val sortedGroupedTokenList: TokenList.GroupedByNetwork get() { - val groups = sortedNetworksGroups + val groups = sortedNetworksGroups.toSet() return groupedTokenList.copy( groups = groups, diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index c0f88c2b7e..c216409615 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(deps.kotlin.immutable.collections) implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) + implementation(deps.arrow.core) /** DI */ implementation(deps.hilt.android) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index c89bcc47ee..dab4813f30 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -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.PriceChangeConfig 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.TokenOptionsState import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem @@ -51,14 +52,16 @@ internal object WalletPreviewData { 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( selectedWalletIndex = 0, - wallets = persistentListOf( - walletCardContentState, - walletCardLoadingState, - walletCardHiddenContentState, - walletCardErrorState, - ), + wallets = wallets.values.toPersistentList(), onWalletChange = {}, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt new file mode 100644 index 0000000000..2fe69a783c --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt @@ -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 { + + 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), + ) + } + } + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt new file mode 100644 index 0000000000..c00f864c32 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt @@ -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 { + return List(size = 5) { TokenItemState.Loading } + .map { MultiCurrencyItem.Token(it) } + .toPersistentList() + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt new file mode 100644 index 0000000000..0f8cb0ca2f --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt @@ -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 { + + // TODO: [REDACTED_JIRA] + override fun convert(value: TokensError): WalletStateHolder { + return currentState + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt new file mode 100644 index 0000000000..731c766675 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt @@ -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> { + + private val tokenStatusConverter = TokenStatusToTokenItemConverter( + isWalletContentHidden, + fiatCurrencyCode, + fiatCurrencySymbol, + ) + + override fun convert(value: TokenList): ImmutableList { + return when (value) { + is TokenList.GroupedByNetwork -> value.mapToMultiCurrencyItems() + is TokenList.Ungrouped -> value.mapToMultiCurrencyItems() + is TokenList.NotInitialized -> getLoadingMultiCurrencyTokens() + } + } + + private fun TokenList.GroupedByNetwork.mapToMultiCurrencyItems(): PersistentList { + return groups.fold(initial = persistentListOf()) { acc, group -> + acc.mutate { it.addGroup(group) } + } + } + + private fun TokenList.Ungrouped.mapToMultiCurrencyItems(): PersistentList { + return tokens.fold(initial = persistentListOf()) { acc, token -> + acc.mutate { it.addToken(token) } + } + } + + private fun MutableList.addGroup(group: NetworkGroup): List { + this.add(MultiCurrencyItem.NetworkGroupTitle(group.name)) + + group.tokens.forEach { token -> + this.addToken(token) + } + + return this + } + + private fun MutableList.addToken(token: TokenStatus): List { + val tokenItemState = tokenStatusConverter.convert(token) + + this.add(MultiCurrencyItem.Token(tokenItemState)) + + return this + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt new file mode 100644 index 0000000000..af7cf9373e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt @@ -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 { + + 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)), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenStatusToTokenItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenStatusToTokenItemConverter.kt new file mode 100644 index 0000000000..b654b47713 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenStatusToTokenItemConverter.kt @@ -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 { + + 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 = "—" + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 2a2f30dcbb..9ee5a8a430 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -6,16 +6,26 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import arrow.core.Either import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.domain.card.ScanCardProcessor 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.router.InnerWalletRouter 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.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 dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.properties.Delegates @@ -27,7 +37,6 @@ import kotlin.properties.Delegates */ @HiltViewModel internal class WalletViewModel @Inject constructor( - @Suppress("unused") // TODO: [REDACTED_JIRA] private val getTokenListUseCase: GetTokenListUseCase, private val scanCardProcessor: ScanCardProcessor, private val dispatchers: CoroutineDispatcherProvider, @@ -40,14 +49,29 @@ internal class WalletViewModel @Inject constructor( var uiState by mutableStateOf(getInitialState()) private set + private var getTokenListJob: Job? = null + set(value) { + field?.cancel() + field = value + } + // TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData - private fun getInitialState(): WalletStateHolder = WalletPreviewData.multicurrencyWalletScreenState.copy( - onBackClick = ::onBackClick, - topBarConfig = createTopBarConfig(), - walletsListConfig = WalletPreviewData.multicurrencyWalletScreenState.walletsListConfig.copy( - onWalletChange = ::selectWallet, - ), - ) + private fun getInitialState(): WalletStateHolder { + val state = WalletPreviewData.multicurrencyWalletScreenState.copy( + onBackClick = ::onBackClick, + topBarConfig = createTopBarConfig(), + 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() { router.popBackStack() @@ -74,14 +98,45 @@ internal class WalletViewModel @Inject constructor( Log.i("WalletViewModel", "selectWallet: $index") - uiState = if (index % 2 == 0) { - WalletPreviewData.multicurrencyWalletScreenState.copy( + uiState = when (val state = uiState) { + is WalletStateHolder.MultiCurrencyContent -> state.copy( walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), ) - } else { - WalletPreviewData.singleWalletScreenState.copy( + is WalletStateHolder.SingleCurrencyContent -> state.copy( 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): 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) } } \ No newline at end of file