Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-24 16:32:09 +07:00
parent 796267c863
commit 0d2574225c
15 changed files with 385 additions and 101 deletions

View file

@ -1,5 +1,6 @@
package com.tangem.domain.account.models
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.wallet.UserWallet
import kotlinx.serialization.Serializable
@ -18,4 +19,13 @@ data class AccountStatusList(
val userWallet: UserWallet,
val accountStatuses: Set<AccountStatus>,
val totalAccounts: Int,
)
val totalFiatBalance: TotalFiatBalance = TotalFiatBalance.Failed,
) {
val mainAccount: AccountStatus
get() = accountStatuses.first { accountStatus ->
when (accountStatus) {
is AccountStatus.CryptoPortfolio -> accountStatus.account.isMainAccount
}
}
}

View file

@ -63,6 +63,8 @@ dependencies {
implementation(projects.libs.blockchainSdk)
/** Domain modules */
implementation(projects.domain.account)
implementation(projects.domain.account.status)
implementation(projects.domain.analytics)
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)

View file

@ -4,6 +4,7 @@ import arrow.core.getOrElse
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
@ -16,6 +17,7 @@ import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
@ -46,6 +48,10 @@ internal interface WalletContentClickIntents {
fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onAccountExpandClick(account: Account)
fun onAccountCollapseClick(account: Account)
fun onTransactionClick(txHash: String)
fun onDissmissBottomSheet()
@ -78,6 +84,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
private val walletEventSender: WalletEventSender,
private val analyticsEventHandler: AnalyticsEventHandler,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val accountDependencies: AccountDependencies,
) : BaseWalletClickIntents(), WalletContentClickIntents {
override fun onDetailsClick() {
@ -157,6 +164,16 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
}
override fun onAccountExpandClick(account: Account) {
val userWalletId = stateHolder.getSelectedWalletId()
accountDependencies.expandedAccountsHolder.expandAccount(userWalletId, account.accountId)
}
override fun onAccountCollapseClick(account: Account) {
val userWalletId = stateHolder.getSelectedWalletId()
accountDependencies.expandedAccountsHolder.collapseAccount(userWalletId, account.accountId)
}
private fun showActionsBottomSheet(tokenActionsState: TokenActionsState, userWallet: UserWallet) {
stateHolder.showBottomSheet(
ActionsBottomSheetConfig(

View file

@ -0,0 +1,15 @@
package com.tangem.feature.wallet.presentation.account
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import javax.inject.Inject
@ModelScoped
internal class AccountDependencies @Inject constructor(
val accountsFeatureToggles: AccountsFeatureToggles,
val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
val expandedAccountsHolder: ExpandedAccountsHolder,
val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
)

View file

@ -0,0 +1,54 @@
package com.tangem.feature.wallet.presentation.account
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.*
import javax.inject.Inject
@ModelScoped
internal class ExpandedAccountsHolder @Inject constructor(
private val singleAccountListSupplier: SingleAccountListSupplier,
) {
private val expandedAccounts = MutableStateFlow<Map<UserWalletId, Set<AccountId>>>(mapOf())
fun expandedAccounts(userWallet: UserWallet): Flow<Set<AccountId>> = channelFlow {
walletAccounts(userWallet)
.onEach { accountList ->
val isSingleAccount = accountList.totalAccounts == 1
val defaultExpanded = when {
isSingleAccount -> setOf(accountList.mainAccount.accountId)
else -> setOf()
}
expandedAccounts.update { map ->
var expandedSet = map[userWallet.walletId] ?: defaultExpanded
// force expand for single account
if (isSingleAccount) expandedSet = defaultExpanded
map.plus(userWallet.walletId to expandedSet)
}
}.launchIn(this)
expandedAccounts
.mapNotNull { map -> map[userWallet.walletId] }
.onEach { expanded -> channel.send(expanded) }
.collect()
}
fun expandAccount(userWalletId: UserWalletId, accountId: AccountId) = expandedAccounts.update { map ->
val expandedSet = map[userWalletId]?.plus(accountId) ?: return@update map
map.plus(userWalletId to expandedSet)
}
fun collapseAccount(userWalletId: UserWalletId, accountId: AccountId) = expandedAccounts.update { map ->
val expandedSet = map[userWalletId]?.minus(accountId) ?: return@update map
map.plus(userWalletId to expandedSet)
}
private fun walletAccounts(userWallet: UserWallet): Flow<AccountList> =
singleAccountListSupplier(SingleAccountListProducer.Params(userWallet.walletId))
}

View file

@ -19,6 +19,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.feature.wallet.presentation.account.AccountDependencies
@Suppress("LongParameterList")
@ModelScoped
@ -40,6 +41,7 @@ internal class MultiWalletContentLoader(
private val getStoryContentUseCase: GetStoryContentUseCase,
private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
private val accountDependencies: AccountDependencies,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -54,6 +56,7 @@ internal class MultiWalletContentLoader(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
accountDependencies = accountDependencies,
).let(::add)
WalletNFTListSubscriber(

View file

@ -18,6 +18,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import javax.inject.Inject
@Suppress("LongParameterList")
@ -38,6 +39,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val walletsRepository: WalletsRepository,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val currenciesRepository: CurrenciesRepository,
private val accountDependencies: AccountDependencies,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
@ -59,6 +61,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
walletsRepository = walletsRepository,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
currenciesRepository = currenciesRepository,
accountDependencies = accountDependencies,
)
}
}

View file

@ -14,6 +14,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.feature.wallet.presentation.account.AccountDependencies
@Suppress("LongParameterList")
internal class SingleWalletWithTokenContentLoader(
@ -30,6 +31,7 @@ internal class SingleWalletWithTokenContentLoader(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val accountDependencies: AccountDependencies,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -43,6 +45,7 @@ internal class SingleWalletWithTokenContentLoader(
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
accountDependencies = accountDependencies,
).let(::add)
MultiWalletWarningsSubscriber(
userWallet = userWallet,

View file

@ -14,6 +14,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import javax.inject.Inject
// TODO: Refactor
@ -31,6 +32,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val accountDependencies: AccountDependencies,
) {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
@ -48,6 +50,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
getStoryContentUseCase = getStoryContentUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
accountDependencies = accountDependencies,
)
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
@ -13,7 +12,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
import timber.log.Timber
internal class SetTokenListTransformer(
private val tokenList: TokenList,
private val params: TokenConverterParams,
private val userWallet: UserWallet,
private val appCurrency: AppCurrency,
private val clickIntents: WalletClickIntents,
@ -42,8 +41,12 @@ internal class SetTokenListTransformer(
}
private fun WalletCardState.toLoadedState(): WalletCardState {
val fiatBalance = when (params) {
is TokenConverterParams.Account -> params.accountList.totalFiatBalance
is TokenConverterParams.Wallet -> params.tokenList.totalFiatBalance
}
return MultiWalletCardStateConverter(
fiatBalance = tokenList.totalFiatBalance,
fiatBalance = fiatBalance,
selectedWallet = userWallet,
appCurrency = appCurrency,
).convert(value = this)
@ -51,7 +54,7 @@ internal class SetTokenListTransformer(
private fun WalletTokensListState.toLoadedState(): WalletTokensListState {
return TokenListStateConverter(
tokenList = tokenList,
params = params,
selectedWallet = userWallet,
appCurrency = appCurrency,
clickIntents = clickIntents,

View file

@ -0,0 +1,16 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.tokenlist.TokenList
sealed interface TokenConverterParams {
data class Wallet(
val tokenList: TokenList,
) : TokenConverterParams
data class Account(
val accountList: AccountStatusList,
val expandedAccounts: Set<AccountId>,
) : TokenConverterParams
}

View file

@ -1,12 +1,16 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.card.common.util.cardTypesResolver
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.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup
@ -14,39 +18,98 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
internal class TokenListStateConverter(
appCurrency: AppCurrency,
private val tokenList: TokenList,
private val appCurrency: AppCurrency,
private val params: TokenConverterParams,
private val selectedWallet: UserWallet,
private val clickIntents: WalletClickIntents,
) : Converter<WalletTokensListState, WalletTokensListState> {
private val tokenStatusConverter = TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = { _, status -> clickIntents.onTokenItemClick(status) },
onItemLongClick = { _, status -> clickIntents.onTokenItemLongClick(status) },
)
private val tokenStatusConverter = when (params) {
is TokenConverterParams.Account, // todo account Click with accountId param
is TokenConverterParams.Wallet,
-> TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = { _, status -> clickIntents.onTokenItemClick(status) },
onItemLongClick = { _, status -> clickIntents.onTokenItemLongClick(status) },
)
}
override fun convert(value: WalletTokensListState): WalletTokensListState {
return when (tokenList) {
is TokenList.Empty -> WalletTokensListState.Empty
is TokenList.GroupedByNetwork -> WalletTokensListState.ContentState.Content(
items = tokenList.toGroupedItems(),
organizeTokensButtonConfig = getOrganizeTokensButtonState(
currenciesSize = tokenList.groups.flatMap(NetworkGroup::currencies).size,
),
return when (params) {
is TokenConverterParams.Account -> convertAccountList(params)
is TokenConverterParams.Wallet -> convertTokenList(params.tokenList)
}
}
private fun convertTokenList(tokenList: TokenList): WalletTokensListState = when (tokenList) {
is TokenList.Empty -> WalletTokensListState.Empty
is TokenList.GroupedByNetwork -> WalletTokensListState.ContentState.Content(
items = tokenList.toGroupedItems(),
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
)
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
items = tokenList.toUngroupedItems(),
organizeTokensButtonConfig = getOrganizeTokensButtonState(tokenList = tokenList),
)
}
private fun convertAccountList(params: TokenConverterParams.Account): WalletTokensListState {
val accountList = params.accountList
// todo account null for firs iteration?
val organizeTokensButtonConfig: OrganizeTokensButtonConfig? = null
fun AccountStatus.CryptoPortfolio.map(): TokensListItemUM.Portfolio {
val tokenList: TokenList = this.tokenList
val account: Account.CryptoPortfolio = this.account
val isExtend = params.expandedAccounts.contains(account.accountId)
val onItemClick: (Account.CryptoPortfolio) -> Unit = {
if (isExtend) {
clickIntents.onAccountCollapseClick(it)
} else {
clickIntents.onAccountExpandClick(it)
}
}
val converter = AccountCryptoPortfolioItemStateConverter(
appCurrency = appCurrency,
account = account,
onItemClick = onItemClick,
)
is TokenList.Ungrouped -> WalletTokensListState.ContentState.Content(
items = tokenList.toUngroupedItems(),
organizeTokensButtonConfig = getOrganizeTokensButtonState(currenciesSize = tokenList.currencies.size),
val accountItem = converter.convert(tokenList.totalFiatBalance)
val tokensListState = convertTokenList(tokenList)
val items = when (tokensListState) {
is WalletTokensListState.ContentState.PortfolioContent -> tokensListState.items
is WalletTokensListState.ContentState.Content -> tokensListState.items
is WalletTokensListState.ContentState.Loading -> tokensListState.items
is WalletTokensListState.ContentState.Locked -> tokensListState.items
is WalletTokensListState.Empty -> listOf()
}
return TokensListItemUM.Portfolio(
state = accountItem,
isExpanded = isExtend,
tokens = items.filterIsInstance<PortfolioTokensListItemUM>(),
)
}
val accountItems = accountList.accountStatuses
.map { accountStatus ->
when (accountStatus) {
is AccountStatus.CryptoPortfolio -> accountStatus.map()
}
}
return WalletTokensListState.ContentState.PortfolioContent(
items = accountItems.toPersistentList(),
organizeTokensButtonConfig = organizeTokensButtonConfig,
)
}
private fun TokenList.GroupedByNetwork.toGroupedItems(): PersistentList<TokensListItemUM> {
@ -83,7 +146,12 @@ internal class TokenListStateConverter(
return this
}
private fun getOrganizeTokensButtonState(currenciesSize: Int): WalletOrganizeTokensButtonConfig? {
private fun getOrganizeTokensButtonState(tokenList: TokenList): WalletOrganizeTokensButtonConfig? {
val currenciesSize = when (tokenList) {
TokenList.Empty -> return null
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies).size
is TokenList.Ungrouped -> tokenList.currencies.size
}
return if (currenciesSize > 1 && !isSingleCurrencyWalletWithToken()) {
WalletOrganizeTokensButtonConfig(
isEnabled = tokenList.totalFiatBalance !is TotalFiatBalance.Loading,

View file

@ -1,50 +1,61 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import arrow.core.getOrElse
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import timber.log.Timber
@Suppress("LongParameterList")
internal abstract class BasicTokenListSubscriber(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val clickIntents: WalletClickIntents,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
) : WalletSubscriber() {
internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
protected abstract val userWallet: UserWallet
protected abstract val stateHolder: WalletStateController
protected abstract val clickIntents: WalletClickIntents
protected abstract val tokenListAnalyticsSender: TokenListAnalyticsSender
protected abstract val walletWithFundsChecker: WalletWithFundsChecker
protected abstract val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase
protected abstract val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase
protected abstract val accountDependencies: AccountDependencies
private val sendAnalyticsJobHolder = JobHolder()
private val onTokenListReceivedJobHolder = JobHolder()
protected val isAccountsEnabled get() = accountDependencies.accountsFeatureToggles.isFeatureEnabled
protected abstract fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList>
protected abstract suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>)
protected abstract fun accountListFlow(coroutineScope: CoroutineScope): Flow<AccountStatusList>
override fun create(coroutineScope: CoroutineScope): Flow<*> {
protected abstract suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>)
protected open suspend fun onAccountListReceived() = {
// todo account updateSortingIfNeeded? like [onTokenListReceived]
}
override fun create(coroutineScope: CoroutineScope): Flow<*> =
if (isAccountsEnabled) createAccountListFlow(coroutineScope) else createTokenListFlow(coroutineScope)
private fun createTokenListFlow(coroutineScope: CoroutineScope): Flow<*> {
return combine(
flow = tokenListFlow(coroutineScope)
.onEach { maybeTokenList ->
@ -60,40 +71,40 @@ internal abstract class BasicTokenListSubscriber(
coroutineScope.launch { startCheck(maybeTokenList) }
},
flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(),
transform = { maybeTokenList, maybeAppCurrency ->
val appCurrency = maybeAppCurrency.getOrElse { e ->
Timber.e("Failed to load app currency: $e")
AppCurrency.Default
}
flow2 = appCurrencyFlow(),
transform = { maybeTokenList, appCurrency -> singleAccountTransform(maybeTokenList, appCurrency) },
)
}
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
?.pullToRefreshConfig
?.isRefreshing == true
private suspend fun singleAccountTransform(
maybeTokenList: Lce<TokenListError, TokenList>,
appCurrency: AppCurrency,
) {
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
?.pullToRefreshConfig
?.isRefreshing == true
maybeContent
?.takeIf { !isRefreshing }
?: return@combine
},
ifError = { e ->
Timber.e("Failed to load token list: $e")
stateHolder.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = e,
appCurrency = appCurrency,
),
)
return@combine
},
maybeContent
?.takeIf { !isRefreshing }
?: return
},
ifError = { e ->
Timber.e("Failed to load token list: $e")
stateHolder.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = e,
appCurrency = appCurrency,
),
)
updateContent(tokenList, appCurrency)
walletWithFundsChecker.check(tokenList)
return
},
)
updateContent(TokenConverterParams.Wallet(tokenList), appCurrency)
walletWithFundsChecker.check(tokenList)
}
private suspend fun startCheck(maybeTokenList: Lce<TokenListError, TokenList>) {
@ -110,6 +121,69 @@ internal abstract class BasicTokenListSubscriber(
}
}
private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine(
flow = accountListFlow(coroutineScope)
// todo account analytics for account total balance
/*.onEach { maybeTokenList ->
coroutineScope.launch {
sendTokenListAnalytics(maybeTokenList)
}.saveIn(sendAnalyticsJobHolder)
}*/
.distinctUntilChanged()
.onEach { accountList ->
// todo account see[onAccountListReceived]
// coroutineScope.launch { onAccountListReceived() }.saveIn(onTokenListReceivedJobHolder)
accountList.flattenTokens()
.forEach { tokenList -> startCheck(Lce.Content(tokenList)) }
},
flow2 = appCurrencyFlow(),
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
transform = { accountList, appCurrency, expandedAccounts, isAccountMode ->
val accountFlattenTokensList = accountList.flattenTokens()
val accountFlattenCurrencies = accountFlattenTokensList
.map { it.flattenCurrencies() }
.flatten()
val mainAccount: AccountStatus.CryptoPortfolio = when (val mainAccount = accountList.mainAccount) {
is AccountStatus.CryptoPortfolio -> mainAccount
}
suspend fun singleAccountTransform(maybeTokenList: Lce<TokenListError, TokenList>) =
this.singleAccountTransform(maybeTokenList, appCurrency)
when {
!isAccountMode -> when (mainAccount.tokenList.flattenCurrencies().isEmpty()) {
true -> singleAccountTransform(Lce.Error(TokenListError.EmptyTokens))
false -> singleAccountTransform(Lce.Content(mainAccount.tokenList))
}
isAccountMode -> when (accountFlattenCurrencies.isEmpty()) {
true -> stateHolder.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = TokenListError.EmptyTokens,
appCurrency = appCurrency,
),
)
false -> {
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
updateContent(convertParams, appCurrency)
accountFlattenTokensList
.map { tokenList -> coroutineScope.launch { walletWithFundsChecker.check(tokenList) } }
.joinAll()
}
}
}
},
)
private fun AccountStatusList.flattenTokens(): List<TokenList> = this.accountStatuses.map {
when (it) {
is AccountStatus.CryptoPortfolio -> it.tokenList
}
}
private suspend fun sendTokenListAnalytics(maybeTokenList: Lce<TokenListError, TokenList>) {
val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId)
@ -120,14 +194,23 @@ internal abstract class BasicTokenListSubscriber(
)
}
private fun updateContent(tokenList: TokenList, appCurrency: AppCurrency) {
private fun updateContent(params: TokenConverterParams, appCurrency: AppCurrency) {
stateHolder.update(
SetTokenListTransformer(
tokenList = tokenList,
params = params,
userWallet = userWallet,
appCurrency = appCurrency,
clickIntents = clickIntents,
),
)
}
private fun appCurrencyFlow(): Flow<AppCurrency> = getSelectedAppCurrencyUseCase()
.map {
it.getOrElse { e ->
Timber.e("Failed to load app currency: $e")
AppCurrency.Default
}
}
.distinctUntilChanged()
}

View file

@ -1,5 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
@ -12,32 +14,27 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@Suppress("LongParameterList")
internal class MultiWalletTokenListSubscriber(
private val userWallet: UserWallet,
override val userWallet: UserWallet,
private val tokenListStore: MultiWalletTokenListStore,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
stateHolder: WalletStateController,
clickIntents: WalletClickIntents,
tokenListAnalyticsSender: TokenListAnalyticsSender,
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
) {
override val stateHolder: WalletStateController,
override val clickIntents: WalletClickIntents,
override val tokenListAnalyticsSender: TokenListAnalyticsSender,
override val walletWithFundsChecker: WalletWithFundsChecker,
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
override val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
override val accountDependencies: AccountDependencies,
) : BasicTokenListSubscriber() {
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
tokenListStore.addIfNot(userWallet.walletId, coroutineScope)
@ -45,6 +42,11 @@ internal class MultiWalletTokenListSubscriber(
return tokenListStore.getOrThrow(userWallet.walletId)
}
override fun accountListFlow(coroutineScope: CoroutineScope): Flow<AccountStatusList> {
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
return accountDependencies.singleAccountStatusListSupplier(params)
}
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
updateSortingIfNeeded(maybeTokenList)
}

View file

@ -1,5 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
@ -8,31 +10,26 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@Suppress("LongParameterList")
internal class SingleWalletWithTokenListSubscriber(
private val userWallet: UserWallet.Cold,
override val userWallet: UserWallet.Cold,
private val tokenListStore: MultiWalletTokenListStore,
stateHolder: WalletStateController,
clickIntents: WalletClickIntents,
tokenListAnalyticsSender: TokenListAnalyticsSender,
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
) {
override val stateHolder: WalletStateController,
override val clickIntents: WalletClickIntents,
override val tokenListAnalyticsSender: TokenListAnalyticsSender,
override val walletWithFundsChecker: WalletWithFundsChecker,
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
override val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
override val accountDependencies: AccountDependencies,
) : BasicTokenListSubscriber() {
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
tokenListStore.addIfNot(userWallet.walletId, coroutineScope)
@ -40,5 +37,10 @@ internal class SingleWalletWithTokenListSubscriber(
return tokenListStore.getOrThrow(userWallet.walletId)
}
override fun accountListFlow(coroutineScope: CoroutineScope): Flow<AccountStatusList> {
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
return accountDependencies.singleAccountStatusListSupplier(params)
}
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) = Unit
}