From 5e6bf6aaaeb275f53691dd6af28aa0efcd046a3e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Oct 2025 15:59:43 +0700 Subject: [PATCH] Updated on 2026-08-14 --- .../AccountPortfolioItemUMConverter.kt | 3 +- .../common/ui/userwallet/UserWalletItem.kt | 76 ++++++++++--------- .../features/account/PortfolioFetcher.kt | 14 ++-- .../account/PortfolioSelectorComponent.kt | 32 +++++++- .../fetcher/DefaultPortfolioFetcher.kt | 12 +-- .../DefaultPortfolioSelectorComponent.kt | 4 +- .../DefaultPortfolioSelectorController.kt | 20 +++-- .../selector/PortfolioSelectorModel.kt | 50 +++++------- .../selector/ui/PortfolioSelectorBS.kt | 16 ++-- .../selector/ui/PortfolioSelectorContent.kt | 22 ++++-- .../referral/DefaultReferralComponent.kt | 3 +- .../feature/referral/model/ReferralModel.kt | 11 ++- .../wallet/utils/UserWalletImageFetcher.kt | 1 + .../utils/DefaultUserWalletImageFetcher.kt | 17 +++-- 14 files changed, 172 insertions(+), 109 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt index 0f29f40095..e270e01789 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt @@ -19,6 +19,7 @@ class AccountPortfolioItemUMConverter( private val appCurrency: AppCurrency? = null, private val accountBalance: TotalFiatBalance? = null, private val isBalanceHidden: Boolean = false, + private val isEnabled: Boolean = true, private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None, ) : Converter { @@ -29,7 +30,7 @@ class AccountPortfolioItemUMConverter( name = value.accountName.toUM().value, information = getInfo(value), balance = getBalanceInfo(), - isEnabled = true, + isEnabled = isEnabled, endIcon = endIcon, onClick = { onClick(value.accountId) }, imageState = getImageState(value), diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt index 44b2fc91e9..5f431fee2f 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt @@ -57,45 +57,53 @@ fun UserWalletItem( onClick = state.onClick, enabled = state.isEnabled, ) { - Row( + UserWalletItemRow( + state = state, modifier = Modifier .fillMaxWidth() .heightIn(min = TangemTheme.dimens.size68) .padding(all = TangemTheme.dimens.spacing12), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), - ) { - CardImage(state.imageState) - NameAndInfo( - modifier = Modifier.weight(1f), - name = state.name, - information = state.information, - balance = state.balance, - ) + ) + } +} - when (state.endIcon) { - UserWalletItemUM.EndIcon.None -> Unit - UserWalletItemUM.EndIcon.Arrow -> { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24), - tint = TangemTheme.colors.icon.informative, - contentDescription = null, - ) - } - UserWalletItemUM.EndIcon.Checkmark -> { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_check_24), - tint = TangemTheme.colors.icon.accent, - contentDescription = null, - ) - } - UserWalletItemUM.EndIcon.Warning -> { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_alert_circle_24), - tint = TangemTheme.colors.icon.warning, - contentDescription = null, - ) - } +@Composable +fun UserWalletItemRow(state: UserWalletItemUM, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + CardImage(state.imageState) + NameAndInfo( + modifier = Modifier.weight(1f), + name = state.name, + information = state.information, + balance = state.balance, + ) + + when (state.endIcon) { + UserWalletItemUM.EndIcon.None -> Unit + UserWalletItemUM.EndIcon.Arrow -> { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + } + UserWalletItemUM.EndIcon.Checkmark -> { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_check_24), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + } + UserWalletItemUM.EndIcon.Warning -> { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_alert_circle_24), + tint = TangemTheme.colors.icon.warning, + contentDescription = null, + ) } } } diff --git a/features/account/api/src/main/java/com/tangem/features/account/PortfolioFetcher.kt b/features/account/api/src/main/java/com/tangem/features/account/PortfolioFetcher.kt index 22fae1a67e..f09cafe966 100644 --- a/features/account/api/src/main/java/com/tangem/features/account/PortfolioFetcher.kt +++ b/features/account/api/src/main/java/com/tangem/features/account/PortfolioFetcher.kt @@ -2,15 +2,15 @@ package com.tangem.features.account import com.tangem.domain.account.models.AccountStatusList import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.core.lce.Lce -import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.tokens.error.TokenListError import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow +/** + * How to use see [PortfolioSelectorComponent] + */ interface PortfolioFetcher { val data: Flow @@ -25,10 +25,11 @@ interface PortfolioFetcher { ) data class PortfolioBalance( - val walletBalance: Lce, + val userWallet: UserWallet, val accountsBalance: AccountStatusList, ) { - val userWalletId: UserWalletId get() = accountsBalance.userWalletId + val walletBalance get() = accountsBalance.totalFiatBalance + val userWalletId: UserWalletId get() = userWallet.walletId } sealed interface Mode { @@ -36,6 +37,9 @@ interface PortfolioFetcher { data class Wallet(val walletId: UserWalletId) : Mode } + /** + * @param[mode] supports runtime change [PortfolioFetcher.updateMode] + */ interface Factory { fun create(mode: Mode, scope: CoroutineScope): PortfolioFetcher } diff --git a/features/account/api/src/main/java/com/tangem/features/account/PortfolioSelectorComponent.kt b/features/account/api/src/main/java/com/tangem/features/account/PortfolioSelectorComponent.kt index 7216cd8ad7..0b98d6b218 100644 --- a/features/account/api/src/main/java/com/tangem/features/account/PortfolioSelectorComponent.kt +++ b/features/account/api/src/main/java/com/tangem/features/account/PortfolioSelectorComponent.kt @@ -8,27 +8,55 @@ import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.wallet.UserWallet import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +/** + * How to use + * 1) Create and keep instance of [PortfolioFetcher] and [PortfolioSelectorController] in your Feature Model + * 2) Provide them via [Params] + * 3) Now you have a bridge between your Feature and PortfolioSelector + * 4) Initial state is unselected. Select yourself [PortfolioSelectorController.selectAccount] + * or offer users to select + * + * 5) Listen [PortfolioSelectorController.selectedAccount] or [PortfolioSelectorController.selectedAccountWithData] + * + * Note: + * - Supports [PortfolioSelectorComponent.BottomSheet] and [PortfolioSelectorComponent.Content] modes + */ interface PortfolioSelectorComponent : ComposableBottomSheetComponent, ComposableContentComponent { val title: StateFlow data class Params( - val onDismiss: () -> Unit, val portfolioFetcher: PortfolioFetcher, val controller: PortfolioSelectorController, + val bsCallback: BottomSheetCallback? = null, ) + interface BottomSheetCallback { + val onDismiss: () -> Unit + val onBack: () -> Unit + } + interface Factory : ComponentFactory } /** + * How to use see [PortfolioSelectorComponent] + * * if [isAccountMode] is false it's mean [selectedAccount] emit [AccountId] for Main account */ interface PortfolioSelectorController { val isAccountMode: Flow - val selectedAccount: StateFlow + val selectedAccount: Flow + val selectedAccountSync: AccountId? + + /** + * for some Feature specific filtering + * combine and update with your Feature data and [PortfolioFetcher.data] + */ + val isEnabled: MutableStateFlow<(UserWallet, AccountStatus) -> Boolean> fun selectAccount(accountId: AccountId?) fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow?> diff --git a/features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultPortfolioFetcher.kt b/features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultPortfolioFetcher.kt index e1ae9bce5c..010ebe39f3 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultPortfolioFetcher.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultPortfolioFetcher.kt @@ -7,7 +7,6 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.isMultiCurrency -import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.account.PortfolioFetcher import com.tangem.features.account.PortfolioFetcher.* @@ -23,7 +22,6 @@ import kotlinx.coroutines.flow.* internal class DefaultPortfolioFetcher @AssistedInject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase, private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier, private val getWallets: GetWalletsUseCase, dispatchers: CoroutineDispatcherProvider, @@ -80,14 +78,8 @@ internal class DefaultPortfolioFetcher @AssistedInject constructor( return combine(balanceFlows) { pairs -> pairs.toMap() } } - private fun walletAccountsBalancesFlow(wallet: UserWallet): Flow> = combine( - flow = accountStatusListFlow(wallet), - flow2 = getWalletTotalBalanceUseCase(wallet.walletId), - transform = { accountStatusList, walletBalance -> - val portfolioBalance = PortfolioBalance(walletBalance, accountStatusList) - wallet to portfolioBalance - }, - ) + private fun walletAccountsBalancesFlow(wallet: UserWallet): Flow> = + accountStatusListFlow(wallet).map { wallet to PortfolioBalance(wallet, it) } private fun accountStatusListFlow(wallet: UserWallet): Flow = singleAccountStatusListSupplier(SingleAccountStatusListProducer.Params(wallet.walletId)) diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorComponent.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorComponent.kt index 7a67a32392..2f0229f8f3 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorComponent.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorComponent.kt @@ -31,13 +31,13 @@ internal class DefaultPortfolioSelectorComponent @AssistedInject constructor( .stateIn(componentScope, SharingStarted.Lazily, model.state.value.title) override fun dismiss() { - params.onDismiss() + params.bsCallback?.onDismiss() } @Composable override fun BottomSheet() { val state by model.state.collectAsStateWithLifecycle() - PortfolioSelectorBS(state, onDismiss = ::dismiss) + PortfolioSelectorBS(state = state, onDismiss = ::dismiss, onBack = { params.bsCallback?.onBack() }) } @Composable diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorController.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorController.kt index f607006e40..1bd36673a5 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorController.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorController.kt @@ -4,23 +4,33 @@ import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.wallet.UserWallet -import com.tangem.features.account.PortfolioSelectorController import com.tangem.features.account.PortfolioFetcher -import kotlinx.coroutines.flow.* +import com.tangem.features.account.PortfolioSelectorController +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine import javax.inject.Inject internal class DefaultPortfolioSelectorController @Inject constructor( private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, ) : PortfolioSelectorController { - private val _selectedAccount: MutableStateFlow = MutableStateFlow(null) + private val _selectedAccount: MutableSharedFlow = MutableSharedFlow( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) override val isAccountMode: Flow by lazy { isAccountsModeEnabledUseCase() } + // without StateFlow and distinctUntilChanged to allow reselect and correct navigation + override val selectedAccount: Flow get() = _selectedAccount + override val selectedAccountSync: AccountId? get() = _selectedAccount.replayCache.firstOrNull() - override val selectedAccount: StateFlow get() = _selectedAccount + override val isEnabled: MutableStateFlow<(UserWallet, AccountStatus) -> Boolean> = MutableStateFlow { _, _ -> true } override fun selectAccount(accountId: AccountId?) { - _selectedAccount.update { accountId } + _selectedAccount.tryEmit(accountId) } override fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow?> = diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/PortfolioSelectorModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/PortfolioSelectorModel.kt index bef9142229..0472196bdb 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/PortfolioSelectorModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/PortfolioSelectorModel.kt @@ -23,7 +23,6 @@ import com.tangem.operations.attestation.ArtworkSize import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* import javax.inject.Inject @@ -45,9 +44,11 @@ internal class PortfolioSelectorModel @Inject constructor( init { combine( flow = isAccountsModeEnabledUseCase(), - flow2 = loadBalanceWithArtwork(), - transform = { isAccountsMode, (portfolioData, artworks) -> - val uiList = buildUiList(isAccountsMode, portfolioData, artworks) + flow2 = balanceFetcher.data, + flow3 = walletImageFetcher.allWallets(ArtworkSize.SMALL), + flow4 = selectorController.isEnabled, + transform = { isAccountsMode, portfolioData, artworks, isEnabled -> + val uiList = buildUiList(isAccountsMode, portfolioData, artworks, isEnabled) val title = when (isAccountsMode) { true -> resourceReference(R.string.common_choose_account) false -> resourceReference(R.string.common_choose_wallet) @@ -66,24 +67,25 @@ internal class PortfolioSelectorModel @Inject constructor( isAccountsMode: Boolean, portfolioData: PortfolioFetcher.Data, artworks: Map, + isEnabled: (UserWallet, AccountStatus) -> Boolean, ): List = when (isAccountsMode) { - true -> buildAccountsList(portfolioData, artworks) - false -> buildWalletList(portfolioData, artworks) + true -> buildAccountsList(portfolioData, artworks, isEnabled) + false -> buildWalletList(portfolioData, artworks, isEnabled) } private fun buildWalletList( portfolioData: PortfolioFetcher.Data, artworks: Map, + isEnabled: (UserWallet, AccountStatus) -> Boolean, ): List = buildList { val appCurrency = portfolioData.appCurrency val isBalanceHidden = portfolioData.isBalanceHidden val lockedWallets = mutableListOf() portfolioData.balances.forEach { wallet, portfolio -> - val balance = portfolio.walletBalance.getOrNull() + val balance = portfolio.walletBalance val walletItemUM = UserWalletItemUMConverter( onClick = { - // todo account - // selectorController.selectAccount(portfolio.accountsBalance.mainAccount) + selectorController.selectAccount(portfolio.accountsBalance.mainAccount.account.accountId) }, appCurrency = appCurrency, balance = balance, @@ -92,7 +94,10 @@ internal class PortfolioSelectorModel @Inject constructor( isAuthMode = false, ).convert(wallet) if (walletItemUM.isEnabled) { - add(PortfolioSelectorItemUM.Portfolio(walletItemUM)) + val isEnabledByFeature = isEnabled(wallet, portfolio.accountsBalance.mainAccount) + val finalWalletItemUM = + if (isEnabledByFeature) walletItemUM else walletItemUM.copy(isEnabled = false) + add(PortfolioSelectorItemUM.Portfolio(finalWalletItemUM)) } else { lockedWallets.add(PortfolioSelectorItemUM.Portfolio(walletItemUM)) } @@ -110,16 +115,16 @@ internal class PortfolioSelectorModel @Inject constructor( private fun buildAccountsList( portfolioData: PortfolioFetcher.Data, artworks: Map, + isEnabled: (UserWallet, AccountStatus) -> Boolean, ): List = buildList { val appCurrency = portfolioData.appCurrency val isBalanceHidden = portfolioData.isBalanceHidden val lockedWallets = mutableListOf() portfolioData.balances.forEach { wallet, portfolio -> - val balance = portfolio.walletBalance.getOrNull() + val balance = portfolio.walletBalance val walletItemUM = UserWalletItemUMConverter( onClick = { - // todo account - // selectorController.selectAccount(portfolio.accountsBalance.mainAccount) + selectorController.selectAccount(portfolio.accountsBalance.mainAccount.account.accountId) }, appCurrency = appCurrency, balance = balance, @@ -138,8 +143,8 @@ internal class PortfolioSelectorModel @Inject constructor( ) add(walletTitle) - add(PortfolioSelectorItemUM.Portfolio(walletItemUM)) portfolio.accountsBalance.accountStatuses.forEach { accountStatus -> + val isEnabledByFeature = isEnabled(wallet, accountStatus) val account = accountStatus.account val accountBalance = when (accountStatus) { is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance @@ -148,6 +153,7 @@ internal class PortfolioSelectorModel @Inject constructor( onClick = { selectorController.selectAccount(account.accountId) }, appCurrency = appCurrency, accountBalance = accountBalance, + isEnabled = isEnabledByFeature, isBalanceHidden = isBalanceHidden, ).convert(account) add(PortfolioSelectorItemUM.Portfolio(accountItemUM)) @@ -163,22 +169,6 @@ internal class PortfolioSelectorModel @Inject constructor( } } - private fun loadBalanceWithArtwork(): - Flow>> { - val wallets = Channel>() - val portfolioFlow = balanceFetcher.data - .onEach { wallets.trySend(it.balances.keys) } - - val artworksFlow = wallets.receiveAsFlow() - .distinctUntilChanged() - .flatMapLatest { walletImageFetcher.walletsImage(wallets = it, size = ArtworkSize.SMALL) } - - return combine( - flow = portfolioFlow, - flow2 = artworksFlow, - ) { portfolioData, artworks -> portfolioData to artworks } - } - private fun emptyState() = PortfolioSelectorUM( items = persistentListOf(), title = TextReference.EMPTY, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorBS.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorBS.kt index 9044bd9845..f33617879f 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorBS.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorBS.kt @@ -19,21 +19,26 @@ import com.tangem.features.account.impl.R import com.tangem.features.account.selector.entity.PortfolioSelectorUM @Composable -internal fun PortfolioSelectorBS(state: PortfolioSelectorUM, onDismiss: () -> Unit, modifier: Modifier = Modifier) { +internal fun PortfolioSelectorBS( + state: PortfolioSelectorUM, + onDismiss: () -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier, +) { TangemModalBottomSheet( config = TangemBottomSheetConfig( isShown = true, onDismissRequest = onDismiss, content = TangemBottomSheetConfigContent.Empty, ), - onBack = onDismiss, + onBack = onBack, scrollableContent = false, - containerColor = TangemTheme.colors.background.secondary, + containerColor = TangemTheme.colors.background.tertiary, title = { TangemModalBottomSheetTitle( title = state.title, startIconRes = R.drawable.ic_back_24, - onStartClick = onDismiss, + onStartClick = onBack, ) }, content = { @@ -54,7 +59,8 @@ private fun Preview(@PreviewParameter(PortfolioSelectorPreviewStateProvider::cla PortfolioSelectorBS( state = params, onDismiss = {}, - modifier = Modifier.background(color = TangemTheme.colors.background.secondary), + modifier = Modifier, + onBack = {}, ) } } \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorContent.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorContent.kt index a1bc34cd68..918832e89b 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorContent.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorContent.kt @@ -2,8 +2,10 @@ package com.tangem.features.account.selector.ui import android.content.res.Configuration import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -18,9 +20,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.common.ui.account.AccountIconPreviewData -import com.tangem.common.ui.userwallet.UserWalletItem +import com.tangem.common.ui.userwallet.UserWalletItemRow import com.tangem.common.ui.userwallet.state.UserWalletItemUM import com.tangem.common.ui.userwallet.state.UserWalletItemUM.ImageState +import com.tangem.core.ui.extensions.conditional import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -66,12 +69,16 @@ internal fun PortfolioSelectorContent( } when (item) { - is PortfolioSelectorItemUM.Portfolio -> UserWalletItem( + is PortfolioSelectorItemUM.Portfolio -> UserWalletItemRow( state = item.item, modifier = offsetModifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size68) .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) - .background(color = TangemTheme.colors.background.primary) - .let { if (!item.item.isEnabled) it.alpha(DISABLED_WALLET_ALPHA) else it }, + .background(TangemTheme.colors.background.action) + .clickable(enabled = item.item.isEnabled, onClick = item.item.onClick) + .padding(all = TangemTheme.dimens.spacing12) + .conditional(!item.item.isEnabled) { alpha(DISABLED_WALLET_ALPHA) }, ) is PortfolioSelectorItemUM.GroupTitle -> WalletNameRow( model = item, @@ -103,7 +110,7 @@ private fun Preview(@PreviewParameter(PortfolioSelectorPreviewStateProvider::cla TangemThemePreview { PortfolioSelectorContent( state = params, - modifier = Modifier.background(color = TangemTheme.colors.background.secondary), + modifier = Modifier.background(color = TangemTheme.colors.background.tertiary), ) } } @@ -127,6 +134,9 @@ internal object PortfolioSelectorPreviewData { ), ) + private val lockedAccountItem: UserWalletItemUM + get() = accountItem.copy(isEnabled = false) + private val walletItem: UserWalletItemUM get() = UserWalletItemUM( id = UserWalletId(UUID.randomUUID().toString().encodeToByteArray()), @@ -150,7 +160,7 @@ internal object PortfolioSelectorPreviewData { accountItem .let { PortfolioSelectorItemUM.Portfolio(it) } .let(::add) - accountItem + lockedAccountItem .let { PortfolioSelectorItemUM.Portfolio(it) } .let(::add) PortfolioSelectorItemUM.GroupTitle( diff --git a/features/referral/impl/src/main/java/com/tangem/feature/referral/DefaultReferralComponent.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/DefaultReferralComponent.kt index bb045bb975..da7d10bcf7 100644 --- a/features/referral/impl/src/main/java/com/tangem/feature/referral/DefaultReferralComponent.kt +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/DefaultReferralComponent.kt @@ -6,7 +6,6 @@ import androidx.compose.ui.Modifier import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot -import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel @@ -46,7 +45,7 @@ class DefaultReferralComponent @AssistedInject constructor( params = PortfolioSelectorComponent.Params( portfolioFetcher = model.portfolioFetcher, controller = model.portfolioSelectorController, - onDismiss = model.bottomSheetNavigation::dismiss, + bsCallback = model.portfolioSelectorCallback, ), ) diff --git a/features/referral/impl/src/main/java/com/tangem/feature/referral/model/ReferralModel.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/model/ReferralModel.kt index b795448a1f..5fdfcef5ff 100644 --- a/features/referral/impl/src/main/java/com/tangem/feature/referral/model/ReferralModel.kt +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/model/ReferralModel.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.setValue import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.dismiss import com.tangem.common.routing.AppRouter import com.tangem.common.ui.userwallet.ext.walletInterationIcon import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -41,6 +42,7 @@ import com.tangem.feature.referral.models.DemoModeException import com.tangem.feature.referral.models.ReferralStateHolder import com.tangem.feature.referral.models.ReferralStateHolder.* import com.tangem.features.account.PortfolioFetcher +import com.tangem.features.account.PortfolioSelectorComponent import com.tangem.features.account.PortfolioSelectorController import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* @@ -84,6 +86,11 @@ internal class ReferralModel @Inject constructor( var uiState: ReferralStateHolder by mutableStateOf(createInitiallyUiState()) private set + val portfolioSelectorCallback = object : PortfolioSelectorComponent.BottomSheetCallback { + override val onDismiss: () -> Unit = { bottomSheetNavigation::dismiss } + override val onBack: () -> Unit = { bottomSheetNavigation::dismiss } + } + init { analyticsEventHandler.send(ReferralEvents.ReferralScreenOpened) if (accountsFeatureToggles.isFeatureEnabled) { @@ -172,7 +179,7 @@ internal class ReferralModel @Inject constructor( modelScope.launch { val lastInfoState = uiState.referralInfoState val portfolioId = when (accountsFeatureToggles.isFeatureEnabled) { - true -> PortfolioId(requireNotNull(portfolioSelectorController.selectedAccount.value)) + true -> PortfolioId(requireNotNull(portfolioSelectorController.selectedAccountSync)) false -> PortfolioId(params.userWalletId) } runCatching { referralInteractor.startReferral(portfolioId) } @@ -259,7 +266,7 @@ internal class ReferralModel @Inject constructor( private suspend fun selectAccount(referralData: ReferralData) { when (referralData) { - is ReferralData.NonParticipantData -> if (portfolioSelectorController.selectedAccount.value == null) { + is ReferralData.NonParticipantData -> if (portfolioSelectorController.selectedAccountSync == null) { portfolioSelectorController.selectAccount( accountId = walletAccounts(params.userWalletId).first().mainAccount.account.accountId, ) diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/utils/UserWalletImageFetcher.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/utils/UserWalletImageFetcher.kt index 42738f4e11..06e59f01b9 100644 --- a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/utils/UserWalletImageFetcher.kt +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/utils/UserWalletImageFetcher.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow interface UserWalletImageFetcher { + fun allWallets(size: ArtworkSize): Flow> fun walletImage(walletId: UserWalletId, size: ArtworkSize): Flow fun walletImage(cardDTO: CardDTO, size: ArtworkSize): Flow fun walletImage(wallet: UserWallet, size: ArtworkSize): Flow diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/utils/DefaultUserWalletImageFetcher.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/utils/DefaultUserWalletImageFetcher.kt index f6f11319dd..d27eb2dc8e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/utils/DefaultUserWalletImageFetcher.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/utils/DefaultUserWalletImageFetcher.kt @@ -9,6 +9,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.usecase.GetCardImageUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.wallet.utils.UserWalletImageFetcher import com.tangem.operations.attestation.ArtworkSize import kotlinx.coroutines.flow.* @@ -17,12 +18,21 @@ import javax.inject.Inject class DefaultUserWalletImageFetcher @Inject constructor( private val getCardImageUseCase: GetCardImageUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, + private val getWalletsUseCase: GetWalletsUseCase, private val artworkUMConverter: ArtworkUMConverter, ) : UserWalletImageFetcher { private val smallCache = MutableStateFlow(mapOf()) private val largeCache = MutableStateFlow(mapOf()) + override fun allWallets(size: ArtworkSize): Flow> = + getWalletsUseCase.invoke() + .map { list -> list.map { it.walletId } } + .distinctUntilChanged() + .map { list -> list.map { walletId -> walletImage(walletId, size).map { image -> walletId to image } } } + .flatMapLatest { flows -> combine(flows) { it.toMap() } } + .distinctUntilChanged() + override fun walletImage(wallet: UserWallet, size: ArtworkSize): Flow = when (wallet) { is UserWallet.Cold -> walletImage(wallet.scanResponse.card, size) is UserWallet.Hot -> flowOf(UserWalletItemUM.ImageState.MobileWallet) @@ -31,11 +41,8 @@ class DefaultUserWalletImageFetcher @Inject constructor( override fun walletsImage( wallets: Collection, size: ArtworkSize, - ): Flow> = wallets - .map { userWallet -> walletImage(userWallet, size).map { imageState -> userWallet.walletId to imageState } } - .merge() - .runningFold(mapOf()) { map, newState -> map.plus(newState) } - .filter { it.size >= wallets.size } // prevent spam, waiting full map + ): Flow> = allWallets(size) + .map { allWallets -> allWallets.filter { (walletId, _) -> wallets.any { it.walletId == walletId } } } .distinctUntilChanged() override fun walletImage(walletId: UserWalletId, size: ArtworkSize): Flow = flow {