diff --git a/domain/account/src/main/java/com/tangem/domain/account/models/AccountStatusList.kt b/domain/account/src/main/java/com/tangem/domain/account/models/AccountStatusList.kt index acd8f76d35..357cbf110c 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/models/AccountStatusList.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/models/AccountStatusList.kt @@ -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, val totalAccounts: Int, -) \ No newline at end of file + val totalFiatBalance: TotalFiatBalance = TotalFiatBalance.Failed, +) { + + val mainAccount: AccountStatus + get() = accountStatuses.first { accountStatus -> + when (accountStatus) { + is AccountStatus.CryptoPortfolio -> accountStatus.account.isMainAccount + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index d06b12cd86..e0f9ecea7e 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -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) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index da6c773a77..02797b8594 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -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( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/AccountDependencies.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/AccountDependencies.kt new file mode 100644 index 0000000000..5a811786ab --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/AccountDependencies.kt @@ -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, +) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/ExpandedAccountsHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/ExpandedAccountsHolder.kt new file mode 100644 index 0000000000..9f47e3e882 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/account/ExpandedAccountsHolder.kt @@ -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>>(mapOf()) + + fun expandedAccounts(userWallet: UserWallet): Flow> = 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 = + singleAccountListSupplier(SingleAccountListProducer.Params(userWallet.walletId)) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index addbcb2860..2fc6d43584 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -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 { @@ -54,6 +56,7 @@ internal class MultiWalletContentLoader( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, applyTokenListSortingUseCase = applyTokenListSortingUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, + accountDependencies = accountDependencies, ).let(::add) WalletNFTListSubscriber( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index bed5d3487d..2bac7a7f47 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -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, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 451148fb94..9463898730 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -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 { @@ -43,6 +45,7 @@ internal class SingleWalletWithTokenContentLoader( tokenListStore = tokenListStore, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, + accountDependencies = accountDependencies, ).let(::add) MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index ed316595dd..f18029c177 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -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, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index e8b3b2446f..faa718a4c6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -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, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TokenConverterParams.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TokenConverterParams.kt new file mode 100644 index 0000000000..36fff6baf9 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TokenConverterParams.kt @@ -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, + ) : TokenConverterParams +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index ec332f336e..4726ebf77a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -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 { - 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(), ) } + + 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 { @@ -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, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 08c415f5b9..6b4a53b2b5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -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 - protected abstract suspend fun onTokenListReceived(maybeTokenList: Lce) + protected abstract fun accountListFlow(coroutineScope: CoroutineScope): Flow - override fun create(coroutineScope: CoroutineScope): Flow<*> { + protected abstract suspend fun onTokenListReceived(maybeTokenList: Lce) + 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, + 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) { @@ -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) = + 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 = this.accountStatuses.map { + when (it) { + is AccountStatus.CryptoPortfolio -> it.tokenList + } + } + private suspend fun sendTokenListAnalytics(maybeTokenList: Lce) { 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 = getSelectedAppCurrencyUseCase() + .map { + it.getOrElse { e -> + Timber.e("Failed to load app currency: $e") + AppCurrency.Default + } + } + .distinctUntilChanged() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index b719923f57..ab57c197d2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -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 { tokenListStore.addIfNot(userWallet.walletId, coroutineScope) @@ -45,6 +42,11 @@ internal class MultiWalletTokenListSubscriber( return tokenListStore.getOrThrow(userWallet.walletId) } + override fun accountListFlow(coroutineScope: CoroutineScope): Flow { + val params = SingleAccountStatusListProducer.Params(userWallet.walletId) + return accountDependencies.singleAccountStatusListSupplier(params) + } + override suspend fun onTokenListReceived(maybeTokenList: Lce) { updateSortingIfNeeded(maybeTokenList) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 80027a5ec1..cab9cfea7d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -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 { tokenListStore.addIfNot(userWallet.walletId, coroutineScope) @@ -40,5 +37,10 @@ internal class SingleWalletWithTokenListSubscriber( return tokenListStore.getOrThrow(userWallet.walletId) } + override fun accountListFlow(coroutineScope: CoroutineScope): Flow { + val params = SingleAccountStatusListProducer.Params(userWallet.walletId) + return accountDependencies.singleAccountStatusListSupplier(params) + } + override suspend fun onTokenListReceived(maybeTokenList: Lce) = Unit } \ No newline at end of file