diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt index ff3b869c1f..989637b8eb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt @@ -60,7 +60,7 @@ fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: M @Composable fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { if (state.isExpanded) { - ExpandedPortfolioHeader(state.state, modifier) + ExpandedPortfolioHeader(state = state.state, isCollapsable = state.isCollapsable, modifier = modifier) } else { TokenItem( state = state.state, @@ -83,7 +83,7 @@ fun PortfolioTokensListItem(state: PortfolioTokensListItemUM, isBalanceHidden: B } @Composable -private fun ExpandedPortfolioHeader(state: TokenItemState, modifier: Modifier = Modifier) { +private fun ExpandedPortfolioHeader(state: TokenItemState, isCollapsable: Boolean, modifier: Modifier = Modifier) { Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier @@ -130,11 +130,13 @@ private fun ExpandedPortfolioHeader(state: TokenItemState, modifier: Modifier = ) } - Icon( - modifier = Modifier.size(TangemTheme.dimens.size16), - painter = painterResource(id = R.drawable.ic_minimize_24), - tint = TangemTheme.colors.icon.inactive, - contentDescription = null, - ) + if (isCollapsable) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size16), + painter = painterResource(id = R.drawable.ic_minimize_24), + tint = TangemTheme.colors.icon.inactive, + contentDescription = null, + ) + } } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt index 24da6e9d28..23b2ef9da6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt @@ -43,6 +43,7 @@ sealed interface TokensListItemUM { data class Portfolio( val state: TokenItemState, val isExpanded: Boolean, + val isCollapsable: Boolean, val tokens: List, ) : TokensListItemUM { override val id: String = state.id diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 8280707bae..e1d7b97469 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -46,6 +46,7 @@ dependencies { implementation(projects.domain.wallets.models) implementation(projects.domain.settings) implementation(projects.domain.transaction.models) + implementation(projects.domain.account.status) /** DI */ implementation(deps.hilt.android) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingAccountTokenItemConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingAccountTokenItemConverter.kt new file mode 100644 index 0000000000..8d9c2042a9 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingAccountTokenItemConverter.kt @@ -0,0 +1,29 @@ +package com.tangem.features.onramp.swap.availablepairs.entity.converters + +import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.AccountStatus +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toPersistentList + +internal class LoadingAccountTokenItemConverter( + private val appCurrency: AppCurrency, +) : Converter { + + override fun convert(value: AccountStatus.CryptoPortfolio): TokensListItemUM.Portfolio { + val (account, currencies) = value + + return TokensListItemUM.Portfolio( + state = AccountCryptoPortfolioItemStateConverter( + appCurrency = appCurrency, + account = account, + onItemClick = null, + ).convert(TotalFiatBalance.Failed), + isExpanded = true, + isCollapsable = false, + tokens = currencies.flattenCurrencies().map(LoadingTokenListItemConverter::convert).toPersistentList(), + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetNoAvailablePairsTransformerV2.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetNoAvailablePairsTransformerV2.kt new file mode 100644 index 0000000000..669604724c --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetNoAvailablePairsTransformerV2.kt @@ -0,0 +1,61 @@ +package com.tangem.features.onramp.swap.availablepairs.entity.transformers + +import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMData +import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer +import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList + +internal class SetNoAvailablePairsTransformerV2( + private val appCurrency: AppCurrency, + private val accountList: Map>, + private val isBalanceHidden: Boolean, + private val isAccountsMode: Boolean, + private val unavailableErrorText: TextReference, +) : TokenListUMTransformer { + private val unavailableConverter = OnrampTokenItemStateConverterFactory + .createUnavailableItemConverterV2(appCurrency = appCurrency, unavailableErrorText = unavailableErrorText) + + override fun transform(prevState: TokenListUM): TokenListUM { + return prevState.copy( + availableItems = persistentListOf(), + unavailableItems = persistentListOf(), + tokensListData = if (isAccountsMode) { + TokenListUMData.AccountList( + tokensList = accountList.map { (account, cryptoCurrencies) -> + TokensListItemUM.Portfolio( + state = AccountCryptoPortfolioItemStateConverter( + appCurrency = appCurrency, + account = account, + onItemClick = null, + ).convert(TotalFiatBalance.Failed), + isExpanded = true, + isCollapsable = false, + tokens = unavailableConverter.convertList(cryptoCurrencies) + .map(TokensListItemUM::Token) + .toPersistentList(), + ) + }.toPersistentList(), + ) + } else { + TokenListUMData.TokenList( + tokensList = accountList.flatMap { (_, cryptoCurrencies) -> + unavailableConverter.convertList(cryptoCurrencies) + .map(TokensListItemUM::Token) + }.toPersistentList(), + ) + }, + isBalanceHidden = isBalanceHidden, + warning = NotificationUM.Warning.SwapNoAvailablePair, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt index d62cce58ae..8e19216bf3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt @@ -1,12 +1,15 @@ package com.tangem.features.onramp.swap.availablepairs.model -import arrow.core.getOrElse import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.fields.InputManager import com.tangem.core.ui.extensions.capitalize import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles +import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer +import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier +import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -15,6 +18,8 @@ import com.tangem.domain.core.utils.getOrElse import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.tokenlist.TokenList @@ -28,11 +33,13 @@ import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponen import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetErrorWarningTransformer import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetLoadingTokenItemsTransformer import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetNoAvailablePairsTransformer +import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetNoAvailablePairsTransformerV2 +import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM +import com.tangem.features.onramp.swap.entity.AccountCurrencyUM import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMController import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer -import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFoundStateTransformer -import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer +import com.tangem.features.onramp.tokenlist.entity.transformer.* import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer @@ -43,7 +50,7 @@ import javax.inject.Inject private typealias AvailablePairsState = Lce> -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class AvailableSwapPairsModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, @@ -53,7 +60,10 @@ internal class AvailableSwapPairsModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getAvailablePairsUseCase: GetAvailablePairsUseCase, - private val getWalletsUseCase: GetWalletsUseCase, + private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier, + private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, + private val accountsFeatureToggles: AccountsFeatureToggles, + getWalletsUseCase: GetWalletsUseCase, ) : Model() { val state: StateFlow = tokenListUMController.state @@ -62,13 +72,17 @@ internal class AvailableSwapPairsModel @Inject constructor( private val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId } private val tokenListFlow = getTokenListUseCaseFlow() - + private val accountListFlow = getAccountListUseCaseFlow() private val availablePairsByNetworkFlow = MutableStateFlow>(emptyMap()) init { - initializeSearchBarCallbacks() + if (accountsFeatureToggles.isFeatureEnabled) { + subscribeOnUpdateStateV2() + } else { + subscribeOnUpdateState() + } - subscribeOnUpdateState() + initializeSearchBarCallbacks() subscribeOnAvailablePairsUpdates() } @@ -79,12 +93,20 @@ internal class AvailableSwapPairsModel @Inject constructor( maybeTokenList.getOrElse( ifLoading = { it ?: TokenList.Empty }, ifError = { TokenList.Empty }, - ) - .flattenCurrencies() + ).flattenCurrencies() } .shareIn(scope = modelScope, started = SharingStarted.Eagerly, replay = 1) } + private fun getAccountListUseCaseFlow(): SharedFlow> { + return singleAccountStatusListSupplier(SingleAccountStatusListProducer.Params(params.userWalletId)) + .distinctUntilChanged() + .map { accountStatusList -> + accountStatusList.accountStatuses.toList() + }.flowOn(dispatchers.default) + .shareIn(scope = modelScope, started = SharingStarted.Eagerly, replay = 1) + } + private fun initializeSearchBarCallbacks() { tokenListUMController.update( transformer = UpdateSearchBarCallbacksTransformer( @@ -130,6 +152,53 @@ internal class AvailableSwapPairsModel @Inject constructor( .launchIn(modelScope) } + private fun subscribeOnUpdateStateV2() { + combine( + flow = getAccountsAndModeFlow(), + flow2 = getAppCurrencyAndBalanceHidingFlow(), + flow3 = params.selectedStatus, + flow4 = searchManager.query, + flow5 = availablePairsByNetworkFlow + .map { it[params.selectedStatus.value?.toLeastTokenInfo()] } + .distinctUntilChanged(), + ) { accountListAndMode, appCurrencyAndBalanceHiding, selectedStatus, query, availablePairsState -> + val (accountList, isAccountsMode) = accountListAndMode + availablePairsState?.fold( + ifLoading = { + SetLoadingAccountTokenListTransformer( + appCurrency = appCurrencyAndBalanceHiding.first, + accountList = accountList, + isAccountsMode = isAccountsMode, + ) + }, + ifContent = { pairs -> + handleContentStateV2( + appCurrencyAndBalanceHiding = appCurrencyAndBalanceHiding, + accountList = accountList, + selectedStatus = selectedStatus, + query = query, + availablePairs = pairs, + isAccountsMode = isAccountsMode, + ) + }, + ifError = { + handleErrorStateV2( + cause = it, + networkInfo = params.selectedStatus.value?.toLeastTokenInfo(), + accountList = accountList, + ) + }, + ) ?: SetLoadingAccountTokenListTransformer( + appCurrency = appCurrencyAndBalanceHiding.first, + accountList = accountList, + isAccountsMode = isAccountsMode, + ) + } + .onEach(tokenListUMController::update) + .flowOn(dispatchers.default) + .launchIn(modelScope) + } + private fun handleContentState( appCurrencyAndBalanceHiding: Pair, currencies: List, @@ -176,6 +245,53 @@ internal class AvailableSwapPairsModel @Inject constructor( } } + private fun handleContentStateV2( + appCurrencyAndBalanceHiding: Pair, + accountList: List, + selectedStatus: CryptoCurrencyStatus?, + query: String, + availablePairs: List, + isAccountsMode: Boolean, + ): TokenListUMTransformer { + val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding + + val filterByQueryAccountList = accountList.associate { accountStatus -> + when (accountStatus) { + is AccountStatus.CryptoPortfolio -> accountStatus.account to accountStatus.tokenList.flattenCurrencies() + .filter { it.currency != selectedStatus?.currency } + .filterByQuery(query = query) + } + } + + if (availablePairs.isEmpty()) { + return SetNoAvailablePairsTransformerV2( + appCurrency = appCurrency, + accountList = filterByQueryAccountList, + unavailableErrorText = resourceReference(R.string.tokens_list_unavailable_to_swap_source_header), + isBalanceHidden = isBalanceHidden, + isAccountsMode = isAccountsMode, + ) + } + + return if (query.isNotEmpty() && filterByQueryAccountList.isEmpty()) { + SetNothingToFoundStateTransformerV2( + isBalanceHidden = isBalanceHidden, + emptySearchMessageReference = resourceReference( + id = R.string.action_buttons_swap_empty_search_message, + ), + ) + } else { + UpdateAccountTokenListTransformer( + appCurrency = appCurrency, + onItemClick = params.onTokenClick, + accountList = filterByQueryAccountList.filterByAvailability(availablePairs = availablePairs), + isBalanceHidden = isBalanceHidden, + unavailableErrorText = resourceReference(R.string.tokens_list_unavailable_to_swap_source_header), + isAccountsMode = isAccountsMode, + ) + } + } + private fun handleErrorState( cause: Throwable, networkInfo: LeastTokenInfo?, @@ -193,6 +309,26 @@ internal class AvailableSwapPairsModel @Inject constructor( ) } + private fun handleErrorStateV2( + cause: Throwable, + networkInfo: LeastTokenInfo?, + accountList: List, + ): SetErrorWarningTransformer { + return SetErrorWarningTransformer( + cause = cause, + onRefresh = { + modelScope.launch { + if (networkInfo != null) { + accountList.filterIsInstance() + .forEach { (_, currencies) -> + updateAvailablePairs(networkInfo, currencies.flattenCurrencies()) + } + } + } + }, + ) + } + private fun subscribeOnAvailablePairsUpdates() { modelScope.launch { params.selectedStatus @@ -203,9 +339,19 @@ internal class AvailableSwapPairsModel @Inject constructor( val isAlreadyLoaded = availablePairsByNetworkFlow.value[networkInfo]?.isContent() == true if (isAlreadyLoaded) return@collectLatest - val statuses = tokenListFlow.firstOrNull() ?: return@collectLatest - - updateAvailablePairs(networkInfo = networkInfo, statuses = statuses) + if (accountsFeatureToggles.isFeatureEnabled) { + val accountList = accountListFlow.firstOrNull() ?: return@collectLatest + updateAvailablePairs( + networkInfo = networkInfo, + statuses = accountList.filterIsInstance() + .flatMap { accountStatus -> + accountStatus.flattenCurrencies() + }.toSet().toList(), + ) + } else { + val statuses = tokenListFlow.firstOrNull() ?: return@collectLatest + updateAvailablePairs(networkInfo = networkInfo, statuses = statuses) + } } } } @@ -247,6 +393,14 @@ internal class AvailableSwapPairsModel @Inject constructor( ) } + private fun getAccountsAndModeFlow(): Flow, Boolean>> { + return combine( + flow = accountListFlow.distinctUntilChanged(), + flow2 = isAccountsModeEnabledUseCase().distinctUntilChanged(), + transform = ::Pair, + ) + } + private fun onSearchQueryChange(newQuery: String) { if (state.value.searchBarUM.query == newQuery) return @@ -286,6 +440,29 @@ internal class AvailableSwapPairsModel @Inject constructor( } } + private fun Map>.filterByAvailability( + availablePairs: List, + ): List { + return map { (account, currencies) -> + AccountAvailabilityUM( + account = account, + currencyList = currencies.map { status -> + val isAvailable = availablePairs.map(SwapPairLeast::to).contains(status.toLeastTokenInfo()) + + val isAvailableToSwap = isAvailable && + status.value !is CryptoCurrencyStatus.MissedDerivation && + status.value !is CryptoCurrencyStatus.Unreachable && + !status.currency.isCustom + + AccountCurrencyUM( + cryptoCurrencyStatus = status, + isAvailable = isAvailableToSwap, + ) + }, + ) + } + } + private fun CryptoCurrencyStatus.toLeastTokenInfo(): LeastTokenInfo { return LeastTokenInfo( contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0", diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/AccountAvailabilityTokenUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/AccountAvailabilityTokenUM.kt new file mode 100644 index 0000000000..f0856222ba --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/AccountAvailabilityTokenUM.kt @@ -0,0 +1,14 @@ +package com.tangem.features.onramp.swap.entity + +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrencyStatus + +internal data class AccountAvailabilityUM( + val account: Account.CryptoPortfolio, + val currencyList: List, +) + +internal data class AccountCurrencyUM( + val isAvailable: Boolean, + val cryptoCurrencyStatus: CryptoCurrencyStatus, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt index 615823f4e4..3f88743a05 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt @@ -19,6 +19,19 @@ internal data class TokenListUM( val searchBarUM: SearchBarUM, val availableItems: ImmutableList, val unavailableItems: ImmutableList, + val tokensListData: TokenListUMData, val isBalanceHidden: Boolean, val warning: NotificationUM? = null, -) \ No newline at end of file +) + +internal sealed interface TokenListUMData { + data class AccountList( + val tokensList: ImmutableList, + ) : TokenListUMData + + data class TokenList( + val tokensList: ImmutableList, + ) : TokenListUMData + + data object EmptyList : TokenListUMData +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt index 80bb1d02f0..583697cca2 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt @@ -31,6 +31,7 @@ internal class TokenListUMController @Inject constructor() { ), availableItems = persistentListOf(), unavailableItems = persistentListOf(), + tokensListData = TokenListUMData.EmptyList, isBalanceHidden = false, ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt new file mode 100644 index 0000000000..48d7b7331d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt @@ -0,0 +1,45 @@ +package com.tangem.features.onramp.tokenlist.entity.transformer + +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.account.AccountStatus +import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingAccountTokenItemConverter +import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingTokenListItemConverter +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMData +import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList + +internal class SetLoadingAccountTokenListTransformer( + appCurrency: AppCurrency, + private val accountList: List, + private val isAccountsMode: Boolean, +) : TokenListUMTransformer { + + private val accountListItemConverter = LoadingAccountTokenItemConverter(appCurrency) + + override fun transform(prevState: TokenListUM): TokenListUM { + return prevState.copy( + availableItems = persistentListOf(), + unavailableItems = persistentListOf(), + tokensListData = if (isAccountsMode) { + TokenListUMData.AccountList( + tokensList = accountListItemConverter.convertList( + accountList.filterIsInstance(), + ).toPersistentList(), + ) + } else { + TokenListUMData.TokenList( + tokensList = accountList.flatMap { account -> + when (account) { + is AccountStatus.CryptoPortfolio -> LoadingTokenListItemConverter.convertList( + account.tokenList.flattenCurrencies(), + ) + } + }.toPersistentList(), + ) + }, + warning = null, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformer.kt index 2b2c7963eb..fcaa9d6147 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformer.kt @@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMData import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -28,6 +29,7 @@ internal class SetNothingToFoundStateTransformer( ).let(::add) }.toImmutableList(), unavailableItems = persistentListOf(), + tokensListData = TokenListUMData.EmptyList, isBalanceHidden = isBalanceHidden, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformerV2.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformerV2.kt new file mode 100644 index 0000000000..dafa2ac842 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformerV2.kt @@ -0,0 +1,29 @@ +package com.tangem.features.onramp.tokenlist.entity.transformer + +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMData +import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +internal class SetNothingToFoundStateTransformerV2( + private val isBalanceHidden: Boolean, + private val emptySearchMessageReference: TextReference, +) : TokenListUMTransformer { + + override fun transform(prevState: TokenListUM): TokenListUM { + return prevState.copy( + availableItems = persistentListOf(), + unavailableItems = persistentListOf(), + tokensListData = TokenListUMData.TokenList(tokensList = buildList { + TokensListItemUM.Text( + id = emptySearchMessageReference.hashCode(), + text = emptySearchMessageReference, + ).let(::add) + }.toImmutableList()), + isBalanceHidden = isBalanceHidden, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenItemConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenItemConverter.kt new file mode 100644 index 0000000000..2389d1aec9 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenItemConverter.kt @@ -0,0 +1,45 @@ +package com.tangem.features.onramp.tokenlist.entity.transformer + +import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM +import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toPersistentList + +internal class UpdateAccountTokenItemConverter( + private val appCurrency: AppCurrency, + private val unavailableErrorText: TextReference, + onItemClick: (TokenItemState, CryptoCurrencyStatus) -> Unit, +) : Converter { + + private val availableConverter = OnrampTokenItemStateConverterFactory + .createAvailableItemConverter(appCurrency, onItemClick) + + private val unavailableConverter = OnrampTokenItemStateConverterFactory + .createUnavailableItemConverterV2(appCurrency = appCurrency, unavailableErrorText = unavailableErrorText) + + override fun convert(value: AccountAvailabilityUM): TokensListItemUM.Portfolio { + return TokensListItemUM.Portfolio( + state = AccountCryptoPortfolioItemStateConverter( + appCurrency = appCurrency, + account = value.account, + onItemClick = null, + ).convert(TotalFiatBalance.Failed), + isExpanded = true, + isCollapsable = false, + tokens = value.currencyList.asSequence().map { (isAvailable, status) -> + if (isAvailable) { + availableConverter.convert(status) + } else { + unavailableConverter.convert(status) + } + }.map(TokensListItemUM::Token).toPersistentList(), + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenListTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenListTransformer.kt new file mode 100644 index 0000000000..5b13ba643c --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenListTransformer.kt @@ -0,0 +1,64 @@ +package com.tangem.features.onramp.tokenlist.entity.transformer + +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMData +import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer +import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList + +internal class UpdateAccountTokenListTransformer( + private val appCurrency: AppCurrency, + private val onItemClick: (TokenItemState, CryptoCurrencyStatus) -> Unit, + private val accountList: List, + private val isBalanceHidden: Boolean, + private val unavailableErrorText: TextReference, + private val warning: NotificationUM? = null, + private val isAccountsMode: Boolean, +) : TokenListUMTransformer { + + private val accountListItemConverter = UpdateAccountTokenItemConverter( + appCurrency = appCurrency, + onItemClick = onItemClick, + unavailableErrorText = unavailableErrorText, + ) + + private val availableConverter = OnrampTokenItemStateConverterFactory + .createAvailableItemConverter(appCurrency, onItemClick) + + private val unavailableConverter = OnrampTokenItemStateConverterFactory + .createUnavailableItemConverterV2(appCurrency = appCurrency, unavailableErrorText = unavailableErrorText) + + override fun transform(prevState: TokenListUM): TokenListUM { + return prevState.copy( + availableItems = persistentListOf(), + unavailableItems = persistentListOf(), + tokensListData = if (isAccountsMode) { + TokenListUMData.AccountList( + tokensList = accountListItemConverter.convertList(accountList).toPersistentList(), + ) + } else { + TokenListUMData.TokenList( + tokensList = accountList.flatMap { (_, currencyList) -> + currencyList.asSequence().map { (isAvailable, status) -> + if (isAvailable) { + availableConverter.convert(status) + } else { + unavailableConverter.convert(status) + } + }.map(TokensListItemUM::Token).toPersistentList() + }.toPersistentList(), + ) + }, + isBalanceHidden = isBalanceHidden, + warning = warning, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index ae9b3f2e32..a61e5837a8 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -6,6 +6,11 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.fields.InputManager import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer +import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier +import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -13,6 +18,8 @@ import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.utils.getOrElse import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.settings.usercountry.GetUserCountryUseCase @@ -23,13 +30,11 @@ import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM +import com.tangem.features.onramp.swap.entity.AccountCurrencyUM import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent -import com.tangem.features.onramp.tokenlist.entity.OnrampOperation -import com.tangem.features.onramp.tokenlist.entity.TokenListUM -import com.tangem.features.onramp.tokenlist.entity.TokenListUMController -import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer -import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFoundStateTransformer -import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer +import com.tangem.features.onramp.tokenlist.entity.* +import com.tangem.features.onramp.tokenlist.entity.transformer.* import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer @@ -42,7 +47,9 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject -@Suppress("LongParameterList") +typealias AccountCryptoList = Map> + +@Suppress("LargeClass", "LongParameterList") internal class OnrampTokenListModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, @@ -55,6 +62,9 @@ internal class OnrampTokenListModel @Inject constructor( private val rampStateManager: RampStateManager, private val getUserCountryUseCase: GetUserCountryUseCase, private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase, + private val accountsFeatureToggles: AccountsFeatureToggles, + private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier, + private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, ) : Model() { val state: StateFlow = tokenListUMController.state @@ -71,8 +81,11 @@ internal class OnrampTokenListModel @Inject constructor( onActiveChange = ::onSearchBarActiveChange, ), ) - - subscribeOnUpdateState() + if (accountsFeatureToggles.isFeatureEnabled) { + subscribeOnUpdateStateV2() + } else { + subscribeOnUpdateState() + } } private fun subscribeOnUpdateState() { @@ -95,12 +108,7 @@ internal class OnrampTokenListModel @Inject constructor( if (query.isNotEmpty() && filterByQueryTokenList.isEmpty()) { SetNothingToFoundStateTransformer( isBalanceHidden = isBalanceHidden, - emptySearchMessageReference = when (params.filterOperation) { - OnrampOperation.BUY -> R.string.action_buttons_buy_empty_search_message - OnrampOperation.SELL -> R.string.action_buttons_sell_empty_search_message - OnrampOperation.SWAP -> R.string.action_buttons_swap_empty_search_message - } - .let(::resourceReference), + emptySearchMessageReference = getEmptySearchMessageReference(), ) } else { val isInsufficientBalanceForSell = if (params.filterOperation == OnrampOperation.SELL) { @@ -134,6 +142,62 @@ internal class OnrampTokenListModel @Inject constructor( .launchIn(modelScope) } + private fun subscribeOnUpdateStateV2() { + combine( + flow = singleAccountStatusListSupplier( + SingleAccountStatusListProducer.Params(params.userWalletId), + ).distinctUntilChanged(), + flow2 = getAppCurrencyAndBalanceHidingFlow(), + flow3 = isAccountsModeEnabledUseCase(), + flow4 = searchManager.query, + flow5 = hasRestrictionForSellFlow(), + ) { accountList, appCurrencyAndBalanceHiding, isAccountsMode, query, hasRestrictionForSell -> + val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding + val filterByQueryAccountList = accountList.filterAccountsByQuery(query) + + if (query.isNotEmpty() && filterByQueryAccountList.isEmpty()) { + updateTokenListUM( + SetNothingToFoundStateTransformerV2( + isBalanceHidden = isBalanceHidden, + emptySearchMessageReference = getEmptySearchMessageReference(), + ), + ) + } else { + updateTokenListUM( + SetLoadingAccountTokenListTransformer( + appCurrency = appCurrency, + accountList = accountList.accountStatuses.toList(), + isAccountsMode = isAccountsMode, + ), + ) + updateTokenListUM( + UpdateAccountTokenListTransformer( + appCurrency = appCurrency, + onItemClick = params.onTokenClick, + accountList = filterByQueryAccountList.filterByAvailability(), + isBalanceHidden = isBalanceHidden, + unavailableErrorText = getUnavailableTokensHeaderReference(), + warning = getSellWarning( + hasRestrictionForSell = hasRestrictionForSell, + isInsufficientBalanceForSell = accountList.isInsufficientBalanceForSell(), + ), + isAccountsMode = isAccountsMode, + ), + ) + } + } + .flowOn(dispatchers.default) + .launchIn(modelScope) + } + + private fun getAppCurrencyAndBalanceHidingFlow(): Flow> { + return combine( + flow = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(), + flow2 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(), + transform = ::Pair, + ) + } + private fun hasRestrictionForSellFlow(): Flow { return if (params.filterOperation == OnrampOperation.SELL) { getUserCountryUseCase().map { maybe -> @@ -154,25 +218,52 @@ internal class OnrampTokenListModel @Inject constructor( } } + private fun AccountStatusList.isInsufficientBalanceForSell(): Boolean { + return if (params.filterOperation == OnrampOperation.SELL) { + (totalFiatBalance as? TotalFiatBalance.Loaded)?.amount?.isZero() == true + } else { + false + } + } + private fun getUnavailableTokensHeaderReference() = when (params.filterOperation) { OnrampOperation.BUY -> R.string.tokens_list_unavailable_to_purchase_header OnrampOperation.SELL -> R.string.tokens_list_unavailable_to_sell_header OnrampOperation.SWAP -> R.string.tokens_list_unavailable_to_swap_source_header }.let(::resourceReference) + private fun getEmptySearchMessageReference() = when (params.filterOperation) { + OnrampOperation.BUY -> R.string.action_buttons_buy_empty_search_message + OnrampOperation.SELL -> R.string.action_buttons_sell_empty_search_message + OnrampOperation.SWAP -> R.string.action_buttons_swap_empty_search_message + }.let(::resourceReference) + private fun updateTokenListUM(transformer: TokenListUMTransformer) { - tokenListUMController.update { prevState -> - transformer.transform(prevState).apply { - if (isFirstInitialization(prevState = prevState, newState = this)) { - params.onTokenListInitialized() + modelScope.launch { + tokenListUMController.update { prevState -> + transformer.transform(prevState).apply { + if (isFirstInitialization(prevState = prevState, newState = this)) { + params.onTokenListInitialized() + } } } } } + private fun getSellWarning(hasRestrictionForSell: Boolean, isInsufficientBalanceForSell: Boolean) = when { + hasRestrictionForSell -> NotificationUM.Warning.SellingRegionalRestriction + isInsufficientBalanceForSell -> NotificationUM.Warning.InsufficientBalanceForSelling + else -> null + } + private fun isFirstInitialization(prevState: TokenListUM, newState: TokenListUM): Boolean { - return prevState.availableItems.isEmpty() && prevState.unavailableItems.isEmpty() && - (newState.availableItems.isNotEmpty() || newState.unavailableItems.isNotEmpty()) + return if (accountsFeatureToggles.isFeatureEnabled) { + prevState.tokensListData == TokenListUMData.EmptyList && + newState.tokensListData != TokenListUMData.EmptyList + } else { + prevState.availableItems.isEmpty() && prevState.unavailableItems.isEmpty() && + (newState.availableItems.isNotEmpty() || newState.unavailableItems.isNotEmpty()) + } } private fun onSearchQueryChange(newQuery: String) { @@ -195,6 +286,16 @@ internal class OnrampTokenListModel @Inject constructor( ) } + private fun AccountStatusList.filterAccountsByQuery(query: String) = accountStatuses.asSequence() + .associate { accountStatus -> + when (accountStatus) { + is AccountStatus.CryptoPortfolio -> { + val filteredList = accountStatus.tokenList.flattenCurrencies().filterByQuery(query = query) + accountStatus.account to filteredList + } + } + }.filter { (_, value) -> value.isNotEmpty() } + private fun List.filterByQuery(query: String): List { return filter { it.currency.name.contains(other = query, ignoreCase = true) || @@ -237,6 +338,49 @@ internal class OnrampTokenListModel @Inject constructor( } } + private suspend fun AccountCryptoList.filterByAvailability(): List { + return coroutineScope { + map { (account, currencies) -> + async { + AccountAvailabilityUM( + account = account, + currencyList = currencies.map { status -> + val isOperationAvailable = checkAvailabilityByOperation(status = status) + val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation + val isNotLoading = status.value !is CryptoCurrencyStatus.Loading + + val requirements = getAssetRequirementsUseCase( + userWalletId = userWallet.walletId, + currency = status.currency, + ).getOrNull() + + val isAvailableForBuy = rampStateManager.checkAssetRequirements(requirements) + val isNotUnreachable = status.value !is CryptoCurrencyStatus.Unreachable + + val isAvailable = when (params.filterOperation) { + OnrampOperation.BUY -> { + isAvailableForBuy + } // unreachable state is available for Buy operation + OnrampOperation.SELL -> isNotUnreachable + OnrampOperation.SWAP -> { + isNotUnreachable && isAvailableForBuy + } + } + + val isTotalAvailable = + isOperationAvailable && isNotMissedDerivation && isNotLoading && isAvailable + + AccountCurrencyUM( + cryptoCurrencyStatus = status, + isAvailable = isTotalAvailable, + ) + }, + ) + } + }.awaitAll() + } + } + private suspend fun checkAvailabilityByOperation(status: CryptoCurrencyStatus): Boolean { return when (params.filterOperation) { OnrampOperation.BUY -> { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt index 8aeb293ca5..aefd164597 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt @@ -30,6 +30,7 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.BuyTokenScreenTestTags import com.tangem.core.ui.utils.lazyListItemPosition import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMData import com.tangem.features.onramp.tokenlist.ui.preview.PreviewTokenListUMProvider import kotlinx.collections.immutable.ImmutableList @@ -70,6 +71,22 @@ internal fun LazyListScope.onrampTokenList(state: TokenListUM) { tokensList(items = state.availableItems, isBalanceHidden = state.isBalanceHidden) tokensList(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden) + + when (val list = state.tokensListData) { + is TokenListUMData.AccountList -> list.tokensList.forEach { item -> + portfolioTokensList( + portfolio = item, + isBalanceHidden = state.isBalanceHidden, + ) + } + is TokenListUMData.TokenList -> { + tokensList( + items = list.tokensList, + isBalanceHidden = state.isBalanceHidden, + ) + } + TokenListUMData.EmptyList -> Unit + } } private fun LazyListScope.searchBarItem(searchBarUM: SearchBarUM, modifier: Modifier = Modifier) { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt index 684e6d0cc3..2f7e48ad75 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt @@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMData import kotlinx.collections.immutable.persistentListOf internal class PreviewTokenListUMProvider : PreviewParameterProvider { @@ -42,6 +43,7 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider checkSwapCryptoAvailability( + tokenCount = tokenListState.items.count { it is TokensListItemUM.Token }, ) - - return + is WalletTokensListState.ContentState.PortfolioContent -> checkSwapCryptoAvailability( + tokenCount = tokenListState.items.sumOf { it.tokens.count { it is TokensListItemUM.Token } }, + ) + WalletTokensListState.ContentState.Loading, + WalletTokensListState.ContentState.Locked, + WalletTokensListState.Empty, + -> return } modelScope.launch { @@ -808,4 +808,15 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( ) } } + + private fun checkSwapCryptoAvailability(tokenCount: Int) { + if (tokenCount < 2) { + handleError( + alertState = WalletAlertState.InsufficientTokensCountForSwapping, + eventCreator = MainScreenAnalyticsEvent::ButtonSwap, + ) + + return + } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index cd5f108efd..37319208bd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -88,12 +88,14 @@ internal object WalletScreenPreviewData { TokensListItemUM.Portfolio( tokens = textContentTokensState.items.filterIsInstance(), isExpanded = false, + isCollapsable = true, state = AccountItemPreviewData.accountItem .copy(iconState = AccountItemPreviewData.accountLetterIcon), ), TokensListItemUM.Portfolio( tokens = textContentTokensState.items.filterIsInstance(), isExpanded = true, + isCollapsable = true, state = AccountItemPreviewData.accountItem, ), ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 931921c425..07afba9a94 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -114,6 +114,7 @@ internal class TokenListStateConverter( return TokensListItemUM.Portfolio( state = accountItem, isExpanded = isExtend, + isCollapsable = true, tokens = items.filterIsInstance(), ) }