Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-20 12:37:49 +04:00
parent 1326b5bab3
commit 778b255d2d
5 changed files with 120 additions and 35 deletions

View file

@ -27,22 +27,25 @@ interface ChooseTokenBridge : ChooseTokenBridgeLegacy, ChooseTokenBridgeInternal
/**
* for some Feature specific tokens filtering
*/
val tokenFilter: MutableStateFlow<(AccountStatus.CryptoPortfolio, CryptoCurrencyStatus) -> Boolean>
val tokenFilter: MutableStateFlow<(AccountStatus, CryptoCurrencyStatus) -> Boolean>
fun selectWalletTab(walletId: UserWalletId)
data class Settings(
val title: TextReference,
val isShowMarketBlock: Boolean,
val isShowPaymentAccount: Boolean,
) {
companion object {
val SwapFrom = Settings(
title = resourceReference(R.string.swapping_from_title),
isShowMarketBlock = false,
isShowPaymentAccount = true,
)
val SwapTo = Settings(
title = resourceReference(R.string.swapping_to_title),
isShowMarketBlock = true,
isShowPaymentAccount = true,
)
}
}

View file

@ -44,6 +44,7 @@ internal class DefaultChooseTokenBridge @AssistedInject constructor(
private val portfolioListBlockDelegate: PortfolioListBlockDelegate = portfolioListBlockDelegateFactory.create(
modelScope = modelScope,
searchQueryState = searchQueryState,
featureSettings = settings,
)
private val portfolioFullBlockDelegate: PortfolioFullBlockDelegate = portfolioFullBlockDelegateFactory.create(
@ -52,7 +53,7 @@ internal class DefaultChooseTokenBridge @AssistedInject constructor(
portfolioListBlockDelegate = portfolioListBlockDelegate,
)
override val tokenFilter: MutableStateFlow<(AccountStatus.CryptoPortfolio, CryptoCurrencyStatus) -> Boolean>
override val tokenFilter: MutableStateFlow<(AccountStatus, CryptoCurrencyStatus) -> Boolean>
get() = portfolioListBlockDelegate.tokenFilter
override val fullPortfolioBlock: StateFlow<ChooseTokenPortfolioFullBlockUM?>

View file

@ -2,19 +2,25 @@ package com.tangem.feature.swap.choosetoken.impl.converter
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
import com.tangem.common.ui.account.TokensListPortfolioItemConverter
import com.tangem.common.ui.account.toUM
import com.tangem.common.ui.tokens.TokenConverterParams
import com.tangem.common.ui.tokens.TokenItemGrouping.toGroupedItems
import com.tangem.common.ui.tokens.TokenItemGrouping.toUngroupedItems
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.icons.IconTint
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.wrappedList
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.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
@ -29,7 +35,8 @@ internal class ChooseTokenListItemConverter(
private val params: TokenConverterParams,
private val clickIntents: ClickIntents,
private val searchQuery: SearchQuery,
private val tokenFilter: (AccountStatus.CryptoPortfolio, CryptoCurrencyStatus) -> Boolean,
private val tokenFilter: (AccountStatus, CryptoCurrencyStatus) -> Boolean,
private val isShowPaymentAccount: Boolean,
) {
private val isSearchingState: Boolean get() = searchQuery.isSearchingState
@ -39,6 +46,24 @@ internal class ChooseTokenListItemConverter(
clickIntents.onTokenItemClick(account, currencyStatus)
}
private val onAccountItemClick: (Account, isExpanded: Boolean) -> Unit = { clickedAccount, isExpanded ->
if (isExpanded) {
clickIntents.onAccountCollapseClick(clickedAccount)
} else {
clickIntents.onAccountExpandClick(clickedAccount)
}
}
private val fiatAmountStateProvider: ((TotalFiatBalance, isExpanded: Boolean) -> FiatAmountState?) =
{ totalBalance, isExpanded ->
when {
isSearchingState -> FiatAmountState.Empty
!isExpanded -> FiatAmountState.Icon(R.drawable.ic_chewron_down_20, IconTint.Informative)
else -> AccountCryptoPortfolioItemStateConverter
.createFiatAmountState(totalBalance, appCurrency)
}
}
private fun tokenStatusConverter(account: AccountStatus) = TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = { _, status -> onTokenClick(account, status) },
@ -58,8 +83,12 @@ internal class ChooseTokenListItemConverter(
private fun convertAccountList(params: TokenConverterParams.Account): TokenListUMData {
val accountList = params.accountList
val accountItems = accountList.accountStatuses
.filterCryptoPortfolio()
.map { accountStatus -> accountStatus.toPortfolioItem(params) }
.mapNotNull { accountStatus ->
when (accountStatus) {
is AccountStatus.CryptoPortfolio -> accountStatus.toPortfolioItem(params)
is AccountStatus.Payment -> accountStatus.createPaymentAccountItem(params.expandedAccounts)
}
}
.filter { portfolio -> portfolio.tokens.isNotEmpty() }
if (accountItems.isEmpty()) {
return TokenListUMData.EmptyList
@ -77,19 +106,7 @@ internal class ChooseTokenListItemConverter(
val account: Account.CryptoPortfolio = this.account
val isExpanded = isSearchingState || params.expandedAccounts.contains(account.accountId)
val onItemClick: (Account.CryptoPortfolio) -> Unit = { clickedAccount ->
if (isExpanded) {
clickIntents.onAccountCollapseClick(clickedAccount)
} else {
clickIntents.onAccountExpandClick(clickedAccount)
}
}
val fiatAmountStateProvider: ((TotalFiatBalance) -> FiatAmountState?) = { totalBalance ->
when {
isSearchingState -> FiatAmountState.Empty
!isExpanded -> FiatAmountState.Icon(R.drawable.ic_chewron_down_20, IconTint.Informative)
else -> AccountCryptoPortfolioItemStateConverter
.createFiatAmountState(totalBalance, appCurrency)
}
onAccountItemClick(clickedAccount, isExpanded)
}
val converter = AccountCryptoPortfolioItemStateConverter(
@ -97,7 +114,7 @@ internal class ChooseTokenListItemConverter(
account = account,
onItemClick = onItemClick.takeIf { !isSearchingState },
priceChangeLce = this.priceChangeLce,
fiatAmountStateProvider = fiatAmountStateProvider,
fiatAmountStateProvider = { fiatBalance -> fiatAmountStateProvider(fiatBalance, isExpanded) },
subtitle2StateProvider = { _ -> null },
)
val accountItem = converter.convert(tokenList.totalFiatBalance)
@ -130,21 +147,20 @@ internal class ChooseTokenListItemConverter(
}
}
private fun filterTokenList(tokenList: TokenList, account: AccountStatus.CryptoPortfolio): TokenList {
fun List<CryptoCurrencyStatus>.filterCurrencies(): List<CryptoCurrencyStatus> = filter { currency ->
currency.filterByQuery() && tokenFilter(account, currency)
}
private fun List<CryptoCurrencyStatus>.filterCurrencies(account: AccountStatus): List<CryptoCurrencyStatus> =
filter { currency -> currency.filterByQuery() && tokenFilter(account, currency) }
private fun filterTokenList(tokenList: TokenList, account: AccountStatus.CryptoPortfolio): TokenList {
return when (tokenList) {
TokenList.Empty -> TokenList.Empty
is TokenList.Ungrouped -> {
val filtered = tokenList.currencies.filterCurrencies()
val filtered = tokenList.currencies.filterCurrencies(account)
if (filtered.isEmpty()) TokenList.Empty else tokenList.copy(currencies = filtered)
}
is TokenList.GroupedByNetwork -> {
val filteredGroups = tokenList.groups
.map { group ->
val filteredCurrencies = group.currencies.filterCurrencies()
val filteredCurrencies = group.currencies.filterCurrencies(account)
group.copy(currencies = filteredCurrencies)
}
.filter { group -> group.currencies.isNotEmpty() }
@ -159,4 +175,56 @@ internal class ChooseTokenListItemConverter(
currency.symbol.contains(searchQuery.value, ignoreCase = true)
return isSearchFilter
}
private fun AccountStatus.Payment.createPaymentAccountItem(
expandedAccounts: Set<AccountId>,
): TokensListItemUM.Portfolio? {
if (!isShowPaymentAccount) return null
val paymentCurrency: CryptoCurrencyStatus = when (val status = this.value) {
is PaymentAccountStatusValue.Error,
is PaymentAccountStatusValue.IssuingCard,
PaymentAccountStatusValue.NotCreated,
is PaymentAccountStatusValue.UnderReview,
PaymentAccountStatusValue.Loading,
-> return null
is PaymentAccountStatusValue.Loaded -> status.cryptoCurrencyStatus
is PaymentAccountStatusValue.Locked -> status.cryptoCurrencyStatus
}
val account = this.account
val tokensCount = 1
val isExpanded = isSearchingState || expandedAccounts.contains(account.accountId)
val onItemClick: (TokenItemState) -> Unit = {
onAccountItemClick(account, isExpanded)
}
val fiatBalance: TotalFiatBalance = this.value.totalFiatBalance
val fiatAmountState = fiatAmountStateProvider(fiatBalance, isExpanded)
val paymentAccountItem = TokenItemState.Content(
id = account.accountId.value,
iconState = CurrencyIconState.PaymentAccount(),
titleState = TokenItemState.TitleState.Content(text = account.accountName.toUM().value),
subtitleState = TokenItemState.SubtitleState.TextContent(
value = pluralReference(
R.plurals.common_tokens_count,
count = tokensCount,
formatArgs = wrappedList(tokensCount),
),
isAvailable = false,
),
onItemClick = onItemClick.takeIf { !isSearchingState },
fiatAmountState = fiatAmountState,
subtitle2State = null,
onItemLongClick = null,
)
val tokenConverter = tokenStatusConverter(this)
val filtered = listOf(paymentCurrency)
.filterCurrencies(this)
.map { currency -> TokensListItemUM.Token(tokenConverter.convert(currency)) }
return TokensListPortfolioItemConverter(
tokenItemUM = paymentAccountItem,
isExpanded = isExpanded,
isCollapsable = !isSearchingState,
tokens = filtered.toPersistentList(),
).convert(Unit)
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
@ -36,7 +37,7 @@ internal class PortfolioFullBlockDelegate @AssistedInject constructor(
) {
private val isSearchingState: Boolean get() = searchQueryState.isSearchingState
private val onWalletSelected = Channel<UserWalletId>()
private val onWalletSelected = Channel<UserWalletId>(capacity = Channel.BUFFERED)
val selectedWalletFlow: SharedFlow<UserWallet> = onWalletSelected.receiveAsFlow()
.distinctUntilChanged()
@ -47,14 +48,18 @@ internal class PortfolioFullBlockDelegate @AssistedInject constructor(
.flowOn(dispatchers.default)
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = null)
private fun buildFlow() = flow {
val isNoHaveSelectedWallet = selectedWalletFlow.replayCache.isEmpty()
if (isNoHaveSelectedWallet) {
val firstSelectedWallet = selectedWalletUseCase.sync().getOrNull()
?: getWalletsUseCase.invokeSync().first()
selectWalletTab(firstSelectedWallet.walletId)
init {
val globalSelectedWallet = selectedWalletUseCase.sync().getOrNull()
val allWallets = getWalletsUseCase.invokeSync().filter { it.isMultiCurrency }
val firstSelectedWallet = when {
globalSelectedWallet?.isMultiCurrency == true -> globalSelectedWallet
allWallets.isNotEmpty() -> allWallets.first()
else -> null
}
if (firstSelectedWallet != null) selectWalletTab(firstSelectedWallet.walletId)
}
private fun buildFlow() = flow {
val fullPortfolioBlockFlow = combine(
flow = getWalletsUseCase.invokeAsMap(),
flow2 = portfolioListBlockDelegate.portfolioList,

View file

@ -12,6 +12,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.swap.choosetoken.api.ChooseTokenAnalyticsPayload
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridge
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
import com.tangem.feature.swap.choosetoken.api.ChooseTokenResult
@ -26,6 +27,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
internal class PortfolioListBlockDelegate @AssistedInject constructor(
private val expandedAccountsHolder: ChooseTokenExpandedAccountsHolder,
private val settingContext: SettingContextUseCase,
@ -33,12 +35,13 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
private val getWalletsUseCase: GetWalletsUseCase,
@Assisted private val modelScope: CoroutineScope,
@Assisted private val searchQueryState: StateFlow<SearchQuery>,
@Assisted private val featureSettings: ChooseTokenBridge.Settings,
) : ClickIntents {
private val onTokenItemClick: Channel<Pair<AccountStatus, CryptoCurrencyStatus>> = Channel()
val onTokenChosen: Channel<ChooseTokenResult> = Channel()
val tokenFilter: MutableStateFlow<(AccountStatus.CryptoPortfolio, CryptoCurrencyStatus) -> Boolean> =
val tokenFilter: MutableStateFlow<(AccountStatus, CryptoCurrencyStatus) -> Boolean> =
MutableStateFlow { _, _ -> true }
val portfolioList: SharedFlow<Map<UserWalletId, TokenListUMData>> = buildDataFlow()
@ -89,6 +92,7 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
clickIntents = this@PortfolioListBlockDelegate,
searchQuery = searchQuery,
tokenFilter = tokenFilter,
isShowPaymentAccount = featureSettings.isShowPaymentAccount,
).convert()
um
@ -132,7 +136,11 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
@AssistedFactory
interface Factory {
fun create(searchQueryState: StateFlow<SearchQuery>, modelScope: CoroutineScope): PortfolioListBlockDelegate
fun create(
searchQueryState: StateFlow<SearchQuery>,
modelScope: CoroutineScope,
featureSettings: ChooseTokenBridge.Settings,
): PortfolioListBlockDelegate
}
}