Updated on 2026-08-14
This commit is contained in:
parent
971c419623
commit
231c97e371
20 changed files with 717 additions and 56 deletions
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ sealed interface TokensListItemUM {
|
|||
data class Portfolio(
|
||||
val state: TokenItemState,
|
||||
val isExpanded: Boolean,
|
||||
val isCollapsable: Boolean,
|
||||
val tokens: List<PortfolioTokensListItemUM>,
|
||||
) : TokensListItemUM {
|
||||
override val id: String = state.id
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<AccountStatus.CryptoPortfolio, TokensListItemUM.Portfolio> {
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Account.CryptoPortfolio, List<CryptoCurrencyStatus>>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Throwable, List<SwapPairLeast>>
|
||||
|
||||
@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<TokenListUM> = 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<Map<LeastTokenInfo, AvailablePairsState>>(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<List<AccountStatus>> {
|
||||
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<AppCurrency, Boolean>,
|
||||
currencies: List<CryptoCurrencyStatus>,
|
||||
|
|
@ -176,6 +245,53 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleContentStateV2(
|
||||
appCurrencyAndBalanceHiding: Pair<AppCurrency, Boolean>,
|
||||
accountList: List<AccountStatus>,
|
||||
selectedStatus: CryptoCurrencyStatus?,
|
||||
query: String,
|
||||
availablePairs: List<SwapPairLeast>,
|
||||
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<AccountStatus>,
|
||||
): SetErrorWarningTransformer {
|
||||
return SetErrorWarningTransformer(
|
||||
cause = cause,
|
||||
onRefresh = {
|
||||
modelScope.launch {
|
||||
if (networkInfo != null) {
|
||||
accountList.filterIsInstance<AccountStatus.CryptoPortfolio>()
|
||||
.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<AccountStatus.CryptoPortfolio>()
|
||||
.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<Pair<List<AccountStatus>, 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<Account.CryptoPortfolio, List<CryptoCurrencyStatus>>.filterByAvailability(
|
||||
availablePairs: List<SwapPairLeast>,
|
||||
): List<AccountAvailabilityUM> {
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -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<AccountCurrencyUM>,
|
||||
)
|
||||
|
||||
internal data class AccountCurrencyUM(
|
||||
val isAvailable: Boolean,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
)
|
||||
|
|
@ -19,6 +19,19 @@ internal data class TokenListUM(
|
|||
val searchBarUM: SearchBarUM,
|
||||
val availableItems: ImmutableList<TokensListItemUM>,
|
||||
val unavailableItems: ImmutableList<TokensListItemUM>,
|
||||
val tokensListData: TokenListUMData,
|
||||
val isBalanceHidden: Boolean,
|
||||
val warning: NotificationUM? = null,
|
||||
)
|
||||
)
|
||||
|
||||
internal sealed interface TokenListUMData {
|
||||
data class AccountList(
|
||||
val tokensList: ImmutableList<TokensListItemUM.Portfolio>,
|
||||
) : TokenListUMData
|
||||
|
||||
data class TokenList(
|
||||
val tokensList: ImmutableList<TokensListItemUM>,
|
||||
) : TokenListUMData
|
||||
|
||||
data object EmptyList : TokenListUMData
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ internal class TokenListUMController @Inject constructor() {
|
|||
),
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<AccountStatus>,
|
||||
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<AccountStatus.CryptoPortfolio>(),
|
||||
).toPersistentList(),
|
||||
)
|
||||
} else {
|
||||
TokenListUMData.TokenList(
|
||||
tokensList = accountList.flatMap { account ->
|
||||
when (account) {
|
||||
is AccountStatus.CryptoPortfolio -> LoadingTokenListItemConverter.convertList(
|
||||
account.tokenList.flattenCurrencies(),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
)
|
||||
},
|
||||
warning = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AccountAvailabilityUM, TokensListItemUM.Portfolio> {
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AccountAvailabilityUM>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Account.CryptoPortfolio, List<CryptoCurrencyStatus>>
|
||||
|
||||
@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<TokenListUM> = 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<Pair<AppCurrency, Boolean>> {
|
||||
return combine(
|
||||
flow = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(),
|
||||
flow2 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(),
|
||||
transform = ::Pair,
|
||||
)
|
||||
}
|
||||
|
||||
private fun hasRestrictionForSellFlow(): Flow<Boolean> {
|
||||
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<CryptoCurrencyStatus>.filterByQuery(query: String): List<CryptoCurrencyStatus> {
|
||||
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<AccountAvailabilityUM> {
|
||||
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 -> {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<TokenListUM> {
|
||||
|
|
@ -42,6 +43,7 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM
|
|||
createUnavailableTokenItem(),
|
||||
createUnavailableTokenItem(),
|
||||
),
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,12 +43,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
|
|||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
|
||||
import com.tangem.domain.tokens.NeedShowYieldSupplyDepositedWarningUseCase
|
||||
import com.tangem.domain.tokens.SaveViewedTokenReceiveWarningUseCase
|
||||
import com.tangem.domain.tokens.SaveViewedYieldSupplyWarningUseCase
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
|
||||
|
|
@ -57,6 +52,7 @@ import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
|
|||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.AVAILABLE
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.domain.transaction.usecase.GetEnsNameUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
||||
|
|
@ -519,15 +515,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
|
||||
val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return
|
||||
val tokenListState = selectedWallet.tokensListState as? WalletTokensListState.ContentState.Content ?: return
|
||||
val tokenListState = selectedWallet.tokensListState
|
||||
|
||||
if (tokenListState.items.count { it is TokensListItemUM.Token } < 2) {
|
||||
handleError(
|
||||
alertState = WalletAlertState.InsufficientTokensCountForSwapping,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
when (tokenListState) {
|
||||
is WalletTokensListState.ContentState.Content -> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -88,12 +88,14 @@ internal object WalletScreenPreviewData {
|
|||
TokensListItemUM.Portfolio(
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
isExpanded = false,
|
||||
isCollapsable = true,
|
||||
state = AccountItemPreviewData.accountItem
|
||||
.copy(iconState = AccountItemPreviewData.accountLetterIcon),
|
||||
),
|
||||
TokensListItemUM.Portfolio(
|
||||
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
isExpanded = true,
|
||||
isCollapsable = true,
|
||||
state = AccountItemPreviewData.accountItem,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ internal class TokenListStateConverter(
|
|||
return TokensListItemUM.Portfolio(
|
||||
state = accountItem,
|
||||
isExpanded = isExtend,
|
||||
isCollapsable = true,
|
||||
tokens = items.filterIsInstance<PortfolioTokensListItemUM>(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue