From 71e8f409097d79b7cf896e3b1fdea4dea635c458 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 29 Sep 2025 15:58:48 +0700 Subject: [PATCH] Updated on 2026-08-14 --- .../AccountPortfolioItemUMConverter.kt | 85 +++++++ .../common/ui/userwallet/UserWalletItem.kt | 22 +- .../ui/userwallet/state/UserWalletItemUM.kt | 6 + core/res/src/main/res/values/strings.xml | 1 + features/account/api/build.gradle.kts | 1 + .../account/AccountSelectorComponent.kt | 24 -- ...sBalanceFetcher.kt => PortfolioFetcher.kt} | 17 +- .../account/PortfolioSelectorComponent.kt | 37 +++ features/account/impl/build.gradle.kts | 5 + ...eFetcher.kt => DefaultPortfolioFetcher.kt} | 63 +++-- .../account/selector/AccountSelectorModel.kt | 87 ------- .../DefaultAccountSelectorComponent.kt | 69 ------ .../DefaultAccountSelectorController.kt | 18 -- .../DefaultPortfolioSelectorComponent.kt | 59 +++++ .../DefaultPortfolioSelectorController.kt | 43 ++++ .../selector/PortfolioSelectorModel.kt | 186 +++++++++++++++ .../selector/entity/AccountSelectorUM.kt | 28 --- .../selector/entity/PortfolioSelectorUM.kt | 27 +++ .../selector/ui/AccountSelectorContent.kt | 146 ------------ ...ntSelectorBS.kt => PortfolioSelectorBS.kt} | 13 +- .../selector/ui/PortfolioSelectorContent.kt | 219 ++++++++++++++++++ 21 files changed, 734 insertions(+), 422 deletions(-) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt delete mode 100644 features/account/api/src/main/java/com/tangem/features/account/AccountSelectorComponent.kt rename features/account/api/src/main/java/com/tangem/features/account/{AccountsBalanceFetcher.kt => PortfolioFetcher.kt} (65%) create mode 100644 features/account/api/src/main/java/com/tangem/features/account/PortfolioSelectorComponent.kt rename features/account/impl/src/main/java/com/tangem/features/account/fetcher/{DefaultAccountsBalanceFetcher.kt => DefaultPortfolioFetcher.kt} (64%) delete mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/AccountSelectorModel.kt delete mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultAccountSelectorComponent.kt delete mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultAccountSelectorController.kt create mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorComponent.kt create mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorController.kt create mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/PortfolioSelectorModel.kt delete mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/entity/AccountSelectorUM.kt create mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/entity/PortfolioSelectorUM.kt delete mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/ui/AccountSelectorContent.kt rename features/account/impl/src/main/java/com/tangem/features/account/selector/ui/{AccountSelectorBS.kt => PortfolioSelectorBS.kt} (80%) create mode 100644 features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorContent.kt 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 new file mode 100644 index 0000000000..276468c38c --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt @@ -0,0 +1,85 @@ +package com.tangem.common.ui.account + +import com.tangem.common.ui.R +import com.tangem.common.ui.userwallet.state.UserWalletItemUM +import com.tangem.core.ui.extensions.pluralReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.utils.converter.Converter + +class AccountPortfolioItemUMConverter( + private val onClick: (AccountId) -> Unit, + private val appCurrency: AppCurrency? = null, + private val accountBalance: TotalFiatBalance? = null, + private val isBalanceHidden: Boolean = false, + private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None, +) : Converter { + + override fun convert(value: Account): UserWalletItemUM { + return with(value) { + UserWalletItemUM( + id = UserWalletId(value.accountId.value), + name = value.accountName.toUM().value, + information = getInfo(value), + balance = getBalanceInfo(), + isEnabled = true, + endIcon = endIcon, + onClick = { onClick(value.accountId) }, + imageState = getImageState(value), + label = null, + ) + } + } + + private fun getInfo(account: Account): UserWalletItemUM.Information.Loaded = when (account) { + is Account.CryptoPortfolio -> { + val text = pluralReference( + R.plurals.common_tokens_count, + count = account.tokensCount, + formatArgs = wrappedList(account.tokensCount), + ) + UserWalletItemUM.Information.Loaded(text) + } + } + + private fun getImageState(account: Account) = when (account) { + is Account.CryptoPortfolio -> UserWalletItemUM.ImageState.Account( + name = account.accountName.toUM().value, + icon = account.icon.toUM(), + ) + } + + private fun getBalanceInfo(): UserWalletItemUM.Balance { + return when { + isBalanceHidden -> UserWalletItemUM.Balance.Hidden + accountBalance == null -> UserWalletItemUM.Balance.Loading + else -> { + when (accountBalance) { + is TotalFiatBalance.Loading -> UserWalletItemUM.Balance.Loading + is TotalFiatBalance.Failed -> UserWalletItemUM.Balance.Failed + is TotalFiatBalance.Loaded -> { + if (appCurrency != null) { + val formattedAmount = accountBalance.amount.format { + fiat(appCurrency.code, appCurrency.symbol) + } + + UserWalletItemUM.Balance.Loaded( + value = formattedAmount, + isFlickering = accountBalance.source == StatusSource.CACHE, + ) + } else { + UserWalletItemUM.Balance.Failed + } + } + } + } + } + } +} \ No newline at end of file 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 ad44dcf7dd..fedba7d453 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 @@ -28,9 +28,12 @@ import androidx.compose.ui.unit.dp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.common.ui.R +import com.tangem.common.ui.account.AccountIcon +import com.tangem.common.ui.account.AccountIconPreviewData import com.tangem.common.ui.userwallet.state.UserWalletItemUM import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.components.account.AccountIconSize import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.components.block.TangemBlockCardColors import com.tangem.core.ui.components.label.Label @@ -198,7 +201,7 @@ private fun BalanceContent(balance: UserWalletItemUM.Balance, modifier: Modifier } } -@Suppress("MagicNumber") +@Suppress("MagicNumber", "LongMethod") @Composable fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) { val imageModifier = modifier @@ -264,6 +267,11 @@ fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modi ) } } + is UserWalletItemUM.ImageState.Account -> AccountIcon( + name = imageState.name, + icon = imageState.icon, + size = AccountIconSize.Default, + ) } } @@ -426,6 +434,18 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider%1$s left Legacy Bitcoin Locked + Locked Wallets Main network month Network fee diff --git a/features/account/api/build.gradle.kts b/features/account/api/build.gradle.kts index 5e9cc443b6..6e0c5aeb96 100644 --- a/features/account/api/build.gradle.kts +++ b/features/account/api/build.gradle.kts @@ -21,4 +21,5 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) + implementation(projects.domain.account) } \ No newline at end of file diff --git a/features/account/api/src/main/java/com/tangem/features/account/AccountSelectorComponent.kt b/features/account/api/src/main/java/com/tangem/features/account/AccountSelectorComponent.kt deleted file mode 100644 index 4e751499ed..0000000000 --- a/features/account/api/src/main/java/com/tangem/features/account/AccountSelectorComponent.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.features.account - -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.ui.decompose.ComposableBottomSheetComponent -import com.tangem.domain.models.account.Account -import kotlinx.coroutines.flow.StateFlow - -interface AccountSelectorComponent : ComposableBottomSheetComponent { - - data class Params( - val onDismiss: () -> Unit, - val accountsBalanceFetcher: AccountsBalanceFetcher, - val controller: AccountSelectorController, - ) - - interface Factory { - fun create(appComponentContext: AppComponentContext, params: Params): AccountSelectorComponent - } -} - -interface AccountSelectorController { - val selectedAccount: StateFlow - fun selectAccount(account: Account?) -} \ No newline at end of file diff --git a/features/account/api/src/main/java/com/tangem/features/account/AccountsBalanceFetcher.kt b/features/account/api/src/main/java/com/tangem/features/account/PortfolioFetcher.kt similarity index 65% rename from features/account/api/src/main/java/com/tangem/features/account/AccountsBalanceFetcher.kt rename to features/account/api/src/main/java/com/tangem/features/account/PortfolioFetcher.kt index 77283e9678..0dc73a77b7 100644 --- a/features/account/api/src/main/java/com/tangem/features/account/AccountsBalanceFetcher.kt +++ b/features/account/api/src/main/java/com/tangem/features/account/PortfolioFetcher.kt @@ -1,9 +1,9 @@ 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.account.Account import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.error.TokenListError @@ -11,7 +11,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -interface AccountsBalanceFetcher { +interface PortfolioFetcher { val data: Flow @@ -21,12 +21,15 @@ interface AccountsBalanceFetcher { data class Data( val appCurrency: AppCurrency, val isBalanceHidden: Boolean, - val balances: Map>, + val balances: Map, ) - data class AccountBalance( - val balance: Lce, - ) + data class PortfolioBalance( + val walletBalance: Lce, + val accountsBalance: AccountStatusList, + ) { + val userWallet: UserWallet get() = accountsBalance.userWallet + } sealed interface Mode { data class All(val onlyMultiCurrency: Boolean) : Mode @@ -34,6 +37,6 @@ interface AccountsBalanceFetcher { } interface Factory { - fun create(mode: Mode, scope: CoroutineScope): AccountsBalanceFetcher + fun create(mode: Mode, scope: CoroutineScope): PortfolioFetcher } } \ No newline at end of file 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 new file mode 100644 index 0000000000..825d41cf48 --- /dev/null +++ b/features/account/api/src/main/java/com/tangem/features/account/PortfolioSelectorComponent.kt @@ -0,0 +1,37 @@ +package com.tangem.features.account + +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.wallet.UserWallet +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface PortfolioSelectorComponent : ComposableBottomSheetComponent, ComposableContentComponent { + + val title: StateFlow + + data class Params( + val onDismiss: () -> Unit, + val portfolioFetcher: PortfolioFetcher, + val controller: PortfolioSelectorController, + ) + + interface Factory { + fun create(appComponentContext: AppComponentContext, params: Params): PortfolioSelectorComponent + } +} + +/** + * if [isAccountMode] is false it's mean [selectedAccount] emit [AccountId] for Main account + */ +interface PortfolioSelectorController { + val isAccountMode: Flow + val selectedAccount: Flow + + fun selectAccount(accountId: AccountId?) + fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow?> +} \ No newline at end of file diff --git a/features/account/impl/build.gradle.kts b/features/account/impl/build.gradle.kts index 924e0c0cdc..b6439a01c3 100644 --- a/features/account/impl/build.gradle.kts +++ b/features/account/impl/build.gradle.kts @@ -14,6 +14,7 @@ android { dependencies { /** Api */ implementation(projects.features.account.api) + implementation(projects.features.wallet.api) /** Core modules */ implementation(projects.core.analytics) @@ -29,6 +30,7 @@ dependencies { /** Domain */ implementation(projects.domain.models) implementation(projects.domain.account) + implementation(projects.domain.account.status) implementation(projects.domain.core) implementation(projects.domain.appCurrency) implementation(projects.domain.appCurrency.models) @@ -39,6 +41,9 @@ dependencies { implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) + /** Tangem libraries */ + implementation(tangemDeps.card.core) + /** Common */ implementation(projects.common.ui) implementation(projects.common.routing) diff --git a/features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultAccountsBalanceFetcher.kt b/features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultPortfolioFetcher.kt similarity index 64% rename from features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultAccountsBalanceFetcher.kt rename to features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultPortfolioFetcher.kt index 2d9db0f8a7..4456993f0e 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultAccountsBalanceFetcher.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/fetcher/DefaultPortfolioFetcher.kt @@ -1,17 +1,18 @@ package com.tangem.features.account.fetcher import arrow.core.getOrElse +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer +import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.core.lce.Lce -import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.account.Account 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.AccountsBalanceFetcher -import com.tangem.features.account.AccountsBalanceFetcher.* +import com.tangem.features.account.PortfolioFetcher +import com.tangem.features.account.PortfolioFetcher.* import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -20,14 +21,17 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.* -internal class DefaultAccountsBalanceFetcher @AssistedInject constructor( +@Suppress("LongParameterList") +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, - private val dispatchers: CoroutineDispatcherProvider, + dispatchers: CoroutineDispatcherProvider, @Assisted mode: Mode, @Assisted private val scope: CoroutineScope, -) : AccountsBalanceFetcher { +) : PortfolioFetcher { private val _mode = MutableStateFlow(mode) private val _data = MutableSharedFlow( @@ -73,33 +77,22 @@ internal class DefaultAccountsBalanceFetcher @AssistedInject constructor( } } - private fun balancesForWallets(wallets: List): Flow>> = - wallets.asFlow() - .map { walletAccountsBalancesFlow(it) } - .mapLatest { accountsBalances -> combine(accountsBalances) { pairs -> pairs.toMap() } } - .flattenConcat() - - private fun walletAccountsBalancesFlow(wallet: UserWallet): Flow>> = - walletAccounts(wallet) - .distinctUntilChanged() - .map { list -> list.map(::accountBalanceFlow) } - .mapLatest { balanceFlows -> combine(balanceFlows) { balances -> balances.toMap() } } - .flattenConcat() - .map { accountBalance -> wallet to accountBalance } - - private fun walletAccounts(wallet: UserWallet): Flow> = flow { - // todo account load accounts - val accounts: List = Account.CryptoPortfolio - .createMainAccount(wallet.walletId) - .let(::listOf) - emit(accounts) + private fun balancesForWallets(wallets: List): Flow> { + val balanceFlows = wallets.map { walletAccountsBalancesFlow(it) } + return combine(balanceFlows) { pairs -> pairs.toMap() } } - private fun accountBalanceFlow(account: Account): Flow> = flow { - // todo account load balance - val balance = AccountBalance(balance = Lce.Content(TotalFiatBalance.Loading)) - emit(account to balance) - } + 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 accountStatusListFlow(wallet: UserWallet): Flow = + singleAccountStatusListSupplier(SingleAccountStatusListProducer.Params(wallet.walletId)) private fun appCurrencyFlow(): Flow { return getSelectedAppCurrencyUseCase() @@ -114,7 +107,7 @@ internal class DefaultAccountsBalanceFetcher @AssistedInject constructor( } @AssistedFactory - interface Factory : AccountsBalanceFetcher.Factory { - override fun create(mode: Mode, scope: CoroutineScope): DefaultAccountsBalanceFetcher + interface Factory : PortfolioFetcher.Factory { + override fun create(mode: Mode, scope: CoroutineScope): DefaultPortfolioFetcher } } \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/AccountSelectorModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/AccountSelectorModel.kt deleted file mode 100644 index 8f3998c375..0000000000 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/AccountSelectorModel.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.tangem.features.account.selector - -import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter -import com.tangem.core.decompose.di.ModelScoped -import com.tangem.core.decompose.model.Model -import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.core.lce.Lce -import com.tangem.domain.core.utils.getOrElse -import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.wallet.isLocked -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.features.account.AccountSelectorComponent -import com.tangem.features.account.AccountsBalanceFetcher -import com.tangem.features.account.selector.entity.AccountSelectorItemUM -import com.tangem.features.account.selector.entity.AccountSelectorUM -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import javax.inject.Inject - -@ModelScoped -internal class AccountSelectorModel @Inject constructor( - paramsContainer: ParamsContainer, - override val dispatchers: CoroutineDispatcherProvider, -) : Model() { - - private val params = paramsContainer.require() - private val balanceFetcher get() = params.accountsBalanceFetcher - private val selectorController get() = params.controller - - internal val state: StateFlow - field = MutableStateFlow(emptyState()) - - init { - balanceFetcher.data - .map { data -> - AccountSelectorUM( - isSingleWallet = balanceFetcher.mode.value is AccountsBalanceFetcher.Mode.Wallet, - items = buildUiList(data).toImmutableList(), - ) - } - .launchIn(modelScope) - } - - private fun buildUiList(data: AccountsBalanceFetcher.Data) = buildList { - fun Account.accountItemState(balance: Lce): TokenItemState { - val totalFiatBalance = balance.getOrElse( - ifError = { TotalFiatBalance.Failed }, - ifLoading = { TotalFiatBalance.Loading }, - ) - return when (this) { - is Account.CryptoPortfolio -> AccountCryptoPortfolioItemStateConverter( - appCurrency = data.appCurrency, - account = this, - onItemClick = { selectorController.selectAccount(it) }, - ).convert(totalFiatBalance) - } - } - - data.balances.forEach { wallet, accounts -> - if (wallet.isLocked) return@forEach - AccountSelectorItemUM.Wallet( - id = wallet.walletId.stringValue, - name = stringReference(wallet.name), - ).let(::add) - - accounts.forEach { account, balance -> - AccountSelectorItemUM.Account( - account = account.accountItemState(balance.balance), - isBalanceHidden = data.isBalanceHidden, - ).let(::add) - } - } - } - - private fun emptyState() = AccountSelectorUM( - items = persistentListOf(), - balanceFetcher.mode.value is AccountsBalanceFetcher.Mode.Wallet, - ) -} \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultAccountSelectorComponent.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultAccountSelectorComponent.kt deleted file mode 100644 index bc53b7bfb3..0000000000 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultAccountSelectorComponent.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.tangem.features.account.selector - -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.account.AccountSelectorComponent -import com.tangem.features.account.impl.R -import com.tangem.features.account.selector.ui.AccountSelectorContent -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -internal class DefaultAccountSelectorComponent @AssistedInject constructor( - @Assisted appComponentContext: AppComponentContext, - @Assisted private val params: AccountSelectorComponent.Params, -) : AppComponentContext by appComponentContext, AccountSelectorComponent { - - private val model: AccountSelectorModel = getOrCreateModel(params) - - override fun dismiss() { - params.onDismiss() - } - - @Composable - override fun BottomSheet() { - val state by model.state.collectAsStateWithLifecycle() - TangemModalBottomSheet( - config = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = ::dismiss, - content = TangemBottomSheetConfigContent.Empty, - ), - onBack = ::dismiss, - containerColor = TangemTheme.colors.background.primary, - title = { - TangemModalBottomSheetTitle( - title = resourceReference(R.string.common_choose_wallet), - startIconRes = R.drawable.ic_back_24, - onStartClick = ::dismiss, - ) - }, - content = { - AccountSelectorContent( - state = state, - modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), - ) - }, - ) - } - - @AssistedFactory - interface Factory : AccountSelectorComponent.Factory { - override fun create( - appComponentContext: AppComponentContext, - params: AccountSelectorComponent.Params, - ): DefaultAccountSelectorComponent - } -} \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultAccountSelectorController.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultAccountSelectorController.kt deleted file mode 100644 index 9be5ae3197..0000000000 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultAccountSelectorController.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.features.account.selector - -import com.tangem.domain.models.account.Account -import com.tangem.features.account.AccountSelectorController -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.update -import javax.inject.Inject - -internal class DefaultAccountSelectorController @Inject constructor() : AccountSelectorController { - - private val _selectedAccount: MutableStateFlow = MutableStateFlow(null) - override val selectedAccount: StateFlow get() = _selectedAccount - - override fun selectAccount(account: Account?) { - _selectedAccount.update { account } - } -} \ No newline at end of file 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 new file mode 100644 index 0000000000..7a67a32392 --- /dev/null +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorComponent.kt @@ -0,0 +1,59 @@ +package com.tangem.features.account.selector + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.account.PortfolioSelectorComponent +import com.tangem.features.account.selector.ui.PortfolioSelectorBS +import com.tangem.features.account.selector.ui.PortfolioSelectorContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +internal class DefaultPortfolioSelectorComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: PortfolioSelectorComponent.Params, +) : AppComponentContext by appComponentContext, PortfolioSelectorComponent { + + private val model: PortfolioSelectorModel = getOrCreateModel(params) + + override val title: StateFlow + get() = model.state + .map { it.title } + .stateIn(componentScope, SharingStarted.Lazily, model.state.value.title) + + override fun dismiss() { + params.onDismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.state.collectAsStateWithLifecycle() + PortfolioSelectorBS(state, onDismiss = ::dismiss) + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsStateWithLifecycle() + PortfolioSelectorContent( + state = state, + modifier = modifier, + ) + } + + @AssistedFactory + interface Factory : PortfolioSelectorComponent.Factory { + override fun create( + appComponentContext: AppComponentContext, + params: PortfolioSelectorComponent.Params, + ): DefaultPortfolioSelectorComponent + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..1c55c39755 --- /dev/null +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/DefaultPortfolioSelectorController.kt @@ -0,0 +1,43 @@ +package com.tangem.features.account.selector + +import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.features.account.PortfolioSelectorController +import com.tangem.features.account.PortfolioFetcher +import kotlinx.coroutines.flow.* +import javax.inject.Inject + +internal class DefaultPortfolioSelectorController @Inject constructor( + private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, +) : PortfolioSelectorController { + + private val _selectedAccount: MutableStateFlow = MutableStateFlow(null) + + override val isAccountMode: Flow by lazy { isAccountsModeEnabledUseCase() } + + override val selectedAccount: StateFlow get() = _selectedAccount + + override fun selectAccount(accountId: AccountId?) { + _selectedAccount.update { accountId } + } + + override fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow?> = + combine( + flow = _selectedAccount, + flow2 = portfolioFetcher.data, + transform = { accountId, data -> + accountId ?: return@combine null + var result: Pair? = null + + data.balances.forEach { wallet, balance -> + val accountStatuses = balance.accountsBalance.accountStatuses + .find { accountId == it.account.accountId } + if (accountStatuses != null) result = wallet to accountStatuses.account + } + + return@combine result + }, + ) +} \ No newline at end of file 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 new file mode 100644 index 0000000000..bef9142229 --- /dev/null +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/PortfolioSelectorModel.kt @@ -0,0 +1,186 @@ +package com.tangem.features.account.selector + +import com.tangem.common.ui.account.AccountPortfolioItemUMConverter +import com.tangem.common.ui.userwallet.converter.UserWalletItemUMConverter +import com.tangem.common.ui.userwallet.state.UserWalletItemUM +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.features.account.PortfolioFetcher +import com.tangem.features.account.PortfolioSelectorComponent +import com.tangem.features.account.impl.R +import com.tangem.features.account.selector.entity.PortfolioSelectorItemUM +import com.tangem.features.account.selector.entity.PortfolioSelectorUM +import com.tangem.features.wallet.utils.UserWalletImageFetcher +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 + +@ModelScoped +internal class PortfolioSelectorModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val walletImageFetcher: UserWalletImageFetcher, + private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, +) : Model() { + + private val params = paramsContainer.require() + private val balanceFetcher get() = params.portfolioFetcher + private val selectorController get() = params.controller + + internal val state: StateFlow + field = MutableStateFlow(emptyState()) + + init { + combine( + flow = isAccountsModeEnabledUseCase(), + flow2 = loadBalanceWithArtwork(), + transform = { isAccountsMode, (portfolioData, artworks) -> + val uiList = buildUiList(isAccountsMode, portfolioData, artworks) + val title = when (isAccountsMode) { + true -> resourceReference(R.string.common_choose_account) + false -> resourceReference(R.string.common_choose_wallet) + } + state.value = PortfolioSelectorUM( + title = title, + items = uiList.toImmutableList(), + ) + }, + ) + .flowOn(dispatchers.default) + .launchIn(modelScope) + } + + private fun buildUiList( + isAccountsMode: Boolean, + portfolioData: PortfolioFetcher.Data, + artworks: Map, + ): List = when (isAccountsMode) { + true -> buildAccountsList(portfolioData, artworks) + false -> buildWalletList(portfolioData, artworks) + } + + private fun buildWalletList( + portfolioData: PortfolioFetcher.Data, + artworks: Map, + ): List = buildList { + val appCurrency = portfolioData.appCurrency + val isBalanceHidden = portfolioData.isBalanceHidden + val lockedWallets = mutableListOf() + portfolioData.balances.forEach { wallet, portfolio -> + val balance = portfolio.walletBalance.getOrNull() + val walletItemUM = UserWalletItemUMConverter( + onClick = { + // todo account + // selectorController.selectAccount(portfolio.accountsBalance.mainAccount) + }, + appCurrency = appCurrency, + balance = balance, + isBalanceHidden = isBalanceHidden, + artwork = artworks[wallet.walletId], + isAuthMode = false, + ).convert(wallet) + if (walletItemUM.isEnabled) { + add(PortfolioSelectorItemUM.Portfolio(walletItemUM)) + } else { + lockedWallets.add(PortfolioSelectorItemUM.Portfolio(walletItemUM)) + } + } + if (lockedWallets.isNotEmpty()) { + val lockedWalletsTitle = PortfolioSelectorItemUM.GroupTitle( + id = "lockedWalletsTitleId", + name = resourceReference(R.string.common_locked_wallets), + ) + add(lockedWalletsTitle) + addAll(lockedWallets) + } + } + + private fun buildAccountsList( + portfolioData: PortfolioFetcher.Data, + artworks: Map, + ): List = buildList { + val appCurrency = portfolioData.appCurrency + val isBalanceHidden = portfolioData.isBalanceHidden + val lockedWallets = mutableListOf() + portfolioData.balances.forEach { wallet, portfolio -> + val balance = portfolio.walletBalance.getOrNull() + val walletItemUM = UserWalletItemUMConverter( + onClick = { + // todo account + // selectorController.selectAccount(portfolio.accountsBalance.mainAccount) + }, + appCurrency = appCurrency, + balance = balance, + isBalanceHidden = isBalanceHidden, + artwork = artworks[wallet.walletId], + isAuthMode = false, + ).convert(wallet) + if (!walletItemUM.isEnabled) { + lockedWallets.add(PortfolioSelectorItemUM.Portfolio(walletItemUM)) + return@forEach + } + + val walletTitle = PortfolioSelectorItemUM.GroupTitle( + id = "GroupTitle ${wallet.walletId.stringValue}", + name = stringReference(wallet.name), + ) + add(walletTitle) + + add(PortfolioSelectorItemUM.Portfolio(walletItemUM)) + portfolio.accountsBalance.accountStatuses.forEach { accountStatus -> + val account = accountStatus.account + val accountBalance = when (accountStatus) { + is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance + } + val accountItemUM = AccountPortfolioItemUMConverter( + onClick = { selectorController.selectAccount(account.accountId) }, + appCurrency = appCurrency, + accountBalance = accountBalance, + isBalanceHidden = isBalanceHidden, + ).convert(account) + add(PortfolioSelectorItemUM.Portfolio(accountItemUM)) + } + } + if (lockedWallets.isNotEmpty()) { + val lockedWalletsTitle = PortfolioSelectorItemUM.GroupTitle( + id = "lockedWalletsTitleId", + name = resourceReference(R.string.common_locked_wallets), + ) + add(lockedWalletsTitle) + addAll(lockedWallets) + } + } + + 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, + ) +} \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/entity/AccountSelectorUM.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/entity/AccountSelectorUM.kt deleted file mode 100644 index d8d05f0fea..0000000000 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/entity/AccountSelectorUM.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.features.account.selector.entity - -import androidx.compose.runtime.Immutable -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.core.ui.extensions.TextReference -import kotlinx.collections.immutable.ImmutableList - -data class AccountSelectorUM( - val items: ImmutableList, - val isSingleWallet: Boolean, -) - -@Immutable -sealed interface AccountSelectorItemUM { - val id: String - - data class Wallet( - override val id: String, - val name: TextReference, - ) : AccountSelectorItemUM - - data class Account( - val account: TokenItemState, - val isBalanceHidden: Boolean, - ) : AccountSelectorItemUM { - override val id: String = account.id - } -} \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/entity/PortfolioSelectorUM.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/entity/PortfolioSelectorUM.kt new file mode 100644 index 0000000000..2fb4321185 --- /dev/null +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/entity/PortfolioSelectorUM.kt @@ -0,0 +1,27 @@ +package com.tangem.features.account.selector.entity + +import androidx.compose.runtime.Immutable +import com.tangem.common.ui.userwallet.state.UserWalletItemUM +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +data class PortfolioSelectorUM( + val title: TextReference, + val items: ImmutableList, +) + +@Immutable +sealed interface PortfolioSelectorItemUM { + val id: String + + data class GroupTitle( + override val id: String, + val name: TextReference, + ) : PortfolioSelectorItemUM + + data class Portfolio( + val item: UserWalletItemUM, + ) : PortfolioSelectorItemUM { + override val id: String = item.id.stringValue + } +} \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/AccountSelectorContent.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/AccountSelectorContent.kt deleted file mode 100644 index a8f54b000d..0000000000 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/AccountSelectorContent.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.tangem.features.account.selector.ui - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.style.TextOverflow -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.core.ui.components.token.AccountItemPreviewData -import com.tangem.core.ui.components.token.TokenItem -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.account.selector.entity.AccountSelectorItemUM -import com.tangem.features.account.selector.entity.AccountSelectorUM -import com.tangem.features.account.selector.ui.AccountSelectorPreviewData.firstList -import kotlinx.collections.immutable.toImmutableList -import java.util.UUID - -@Composable -internal fun AccountSelectorContent( - state: AccountSelectorUM, - modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(), -) { - LazyColumn( - modifier = modifier, - contentPadding = contentPadding, - ) { - val items = state.items - itemsIndexed( - items = items, - key = { _, item -> item.id }, - ) { index, item -> - val previewItem = items.getOrNull(index.dec()) - val offsetModifier = when { - previewItem == null -> Modifier - state.isSingleWallet -> Modifier.padding( - top = TangemTheme.dimens.spacing16, - ) - item is AccountSelectorItemUM.Wallet -> Modifier.padding( - top = TangemTheme.dimens.spacing16, - ) - else -> Modifier.padding( - top = TangemTheme.dimens.spacing8, - ) - } - - when (item) { - is AccountSelectorItemUM.Account -> TokenItem( - state = item.account, - isBalanceHidden = item.isBalanceHidden, - modifier = offsetModifier - .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) - .background(color = TangemTheme.colors.background.primary), - ) - is AccountSelectorItemUM.Wallet -> WalletNameRow( - model = item, - modifier = offsetModifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing16), - ) - } - } - } -} - -@Composable -private fun WalletNameRow(model: AccountSelectorItemUM.Wallet, modifier: Modifier = Modifier) { - Text( - modifier = modifier, - text = model.name.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) -} - -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun AccountSelectorContentPreview( - @PreviewParameter(AccountSelectorPreviewStateProvider::class) params: AccountSelectorUM, -) { - TangemThemePreview { - AccountSelectorContent( - state = params, - modifier = Modifier.background(color = TangemTheme.colors.background.secondary), - ) - } -} - -internal object AccountSelectorPreviewData { - val firstList - get() = buildList { - AccountSelectorItemUM.Wallet( - id = UUID.randomUUID().toString(), - name = stringReference("Tangem 2.0"), - ).let(::add) - AccountItemPreviewData.accountItem - .let { AccountSelectorItemUM.Account(it, false) } - .let(::add) - AccountItemPreviewData.accountItem.copy(iconState = AccountItemPreviewData.accountLetterIcon) - .let { AccountSelectorItemUM.Account(it, false) } - .let(::add) - AccountSelectorItemUM.Wallet( - id = UUID.randomUUID().toString(), - name = stringReference("Tangem White"), - ).let(::add) - AccountItemPreviewData.accountItem.copy(iconState = AccountItemPreviewData.accountLetterIcon) - .let { AccountSelectorItemUM.Account(it, false) } - .let(::add) - } -} - -internal class AccountSelectorPreviewStateProvider : CollectionPreviewParameterProvider( - buildList { - val secondList = listOf( - AccountItemPreviewData.accountItem, - AccountItemPreviewData.accountItem.copy(iconState = AccountItemPreviewData.accountLetterIcon), - ).map { AccountSelectorItemUM.Account(it, false) } - - val first = AccountSelectorUM( - items = firstList.toImmutableList(), - isSingleWallet = false, - ) - val second = AccountSelectorUM( - items = secondList.toImmutableList(), - isSingleWallet = true, - ) - add(first) - add(second) - }, -) \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/AccountSelectorBS.kt b/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorBS.kt similarity index 80% rename from features/account/impl/src/main/java/com/tangem/features/account/selector/ui/AccountSelectorBS.kt rename to features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorBS.kt index ec1cad3c9e..9044bd9845 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/AccountSelectorBS.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorBS.kt @@ -13,14 +13,13 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle -import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.account.impl.R -import com.tangem.features.account.selector.entity.AccountSelectorUM +import com.tangem.features.account.selector.entity.PortfolioSelectorUM @Composable -internal fun AccountSelectorBS(state: AccountSelectorUM, onDismiss: () -> Unit, modifier: Modifier = Modifier) { +internal fun PortfolioSelectorBS(state: PortfolioSelectorUM, onDismiss: () -> Unit, modifier: Modifier = Modifier) { TangemModalBottomSheet( config = TangemBottomSheetConfig( isShown = true, @@ -32,13 +31,13 @@ internal fun AccountSelectorBS(state: AccountSelectorUM, onDismiss: () -> Unit, containerColor = TangemTheme.colors.background.secondary, title = { TangemModalBottomSheetTitle( - title = resourceReference(R.string.common_choose_account), + title = state.title, startIconRes = R.drawable.ic_back_24, onStartClick = onDismiss, ) }, content = { - AccountSelectorContent( + PortfolioSelectorContent( state = state, contentPadding = PaddingValues(bottom = 16.dp), modifier = modifier.padding(horizontal = 16.dp), @@ -50,9 +49,9 @@ internal fun AccountSelectorBS(state: AccountSelectorUM, onDismiss: () -> Unit, @Preview(showBackground = true, widthDp = 360) @Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview(@PreviewParameter(AccountSelectorPreviewStateProvider::class) params: AccountSelectorUM) { +private fun Preview(@PreviewParameter(PortfolioSelectorPreviewStateProvider::class) params: PortfolioSelectorUM) { TangemThemePreview { - AccountSelectorBS( + PortfolioSelectorBS( state = params, onDismiss = {}, modifier = Modifier.background(color = TangemTheme.colors.background.secondary), 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 new file mode 100644 index 0000000000..2bf56e09c3 --- /dev/null +++ b/features/account/impl/src/main/java/com/tangem/features/account/selector/ui/PortfolioSelectorContent.kt @@ -0,0 +1,219 @@ +package com.tangem.features.account.selector.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +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.state.UserWalletItemUM +import com.tangem.common.ui.userwallet.state.UserWalletItemUM.ImageState +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.features.account.impl.R +import com.tangem.features.account.selector.entity.PortfolioSelectorItemUM +import com.tangem.features.account.selector.entity.PortfolioSelectorUM +import com.tangem.features.account.selector.ui.PortfolioSelectorPreviewData.firstList +import com.tangem.features.account.selector.ui.PortfolioSelectorPreviewData.lockedWalletList +import com.tangem.features.account.selector.ui.PortfolioSelectorPreviewData.secondList +import com.tangem.features.account.selector.ui.PortfolioSelectorPreviewData.walletList +import kotlinx.collections.immutable.toImmutableList +import java.util.UUID + +private const val DISABLED_WALLET_ALPHA = 0.5f + +@Composable +internal fun PortfolioSelectorContent( + state: PortfolioSelectorUM, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(), +) { + LazyColumn( + modifier = modifier, + contentPadding = contentPadding, + ) { + val items = state.items + itemsIndexed( + items = items, + key = { _, item -> item.id }, + ) { index, item -> + val previewItem = items.getOrNull(index.dec()) + val offsetModifier = when { + previewItem == null -> Modifier + item is PortfolioSelectorItemUM.GroupTitle -> Modifier.padding( + top = TangemTheme.dimens.spacing16, + ) + else -> Modifier.padding( + top = TangemTheme.dimens.spacing8, + ) + } + + when (item) { + is PortfolioSelectorItemUM.Portfolio -> UserWalletItem( + state = item.item, + modifier = offsetModifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) + .background(color = TangemTheme.colors.background.primary) + .let { if (!item.item.isEnabled) it.alpha(DISABLED_WALLET_ALPHA) else it }, + ) + is PortfolioSelectorItemUM.GroupTitle -> WalletNameRow( + model = item, + modifier = offsetModifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + ) + } + } + } +} + +@Composable +private fun WalletNameRow(model: PortfolioSelectorItemUM.GroupTitle, modifier: Modifier = Modifier) { + Text( + modifier = modifier, + text = model.name.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) +} + +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview(@PreviewParameter(PortfolioSelectorPreviewStateProvider::class) params: PortfolioSelectorUM) { + TangemThemePreview { + PortfolioSelectorContent( + state = params, + modifier = Modifier.background(color = TangemTheme.colors.background.secondary), + ) + } +} + +internal object PortfolioSelectorPreviewData { + + val accountName get() = stringReference(value = "Portfolio") + val walletName get() = stringReference(value = "Tangem 2.0") + + private val accountItem: UserWalletItemUM + get() = UserWalletItemUM( + id = UserWalletId(UUID.randomUUID().toString().encodeToByteArray()), + name = accountName, + information = UserWalletItemUM.Information.Loaded(stringReference("12 tokens")), + balance = UserWalletItemUM.Balance.Loaded("$726.04", false), + isEnabled = true, + onClick = { }, + imageState = ImageState.Account( + name = accountName, + icon = AccountIconPreviewData.randomAccountIcon(), + ), + label = null, + ) + + private val walletItem: UserWalletItemUM + get() = UserWalletItemUM( + id = UserWalletId(UUID.randomUUID().toString().encodeToByteArray()), + name = walletName, + information = UserWalletItemUM.Information.Loaded(stringReference("12 tokens")), + balance = UserWalletItemUM.Balance.Loaded("$726.04", false), + isEnabled = true, + onClick = { }, + imageState = ImageState.MobileWallet, + label = null, + ) + + private val lockedWalletItem: UserWalletItemUM + get() = walletItem.copy(isEnabled = false) + + val firstList + get() = buildList { + PortfolioSelectorItemUM.GroupTitle( + id = UUID.randomUUID().toString(), + name = stringReference("Tangem 2.0"), + ).let(::add) + accountItem + .let { PortfolioSelectorItemUM.Portfolio(it) } + .let(::add) + accountItem + .let { PortfolioSelectorItemUM.Portfolio(it) } + .let(::add) + PortfolioSelectorItemUM.GroupTitle( + id = UUID.randomUUID().toString(), + name = stringReference("Tangem White"), + ).let(::add) + accountItem.let { PortfolioSelectorItemUM.Portfolio(it) } + .let(::add) + } + + val secondList + get() = firstList + buildList { + PortfolioSelectorItemUM.GroupTitle( + id = UUID.randomUUID().toString(), + name = resourceReference(R.string.common_locked_wallets), + ).let(::add) + add(PortfolioSelectorItemUM.Portfolio(lockedWalletItem)) + add(PortfolioSelectorItemUM.Portfolio(lockedWalletItem)) + } + + val walletList + get() = buildList { + add(PortfolioSelectorItemUM.Portfolio(walletItem)) + add(PortfolioSelectorItemUM.Portfolio(walletItem)) + } + + val lockedWalletList + get() = buildList { + add(PortfolioSelectorItemUM.Portfolio(walletItem)) + val title = PortfolioSelectorItemUM.GroupTitle( + id = UUID.randomUUID().toString(), + name = resourceReference(R.string.common_locked_wallets), + ) + add(title) + add(PortfolioSelectorItemUM.Portfolio(lockedWalletItem)) + add(PortfolioSelectorItemUM.Portfolio(lockedWalletItem)) + } +} + +internal class PortfolioSelectorPreviewStateProvider : CollectionPreviewParameterProvider( + buildList { + val first = PortfolioSelectorUM( + title = resourceReference(R.string.common_choose_account), + items = firstList.toImmutableList(), + ) + val second = PortfolioSelectorUM( + title = resourceReference(R.string.common_choose_account), + items = secondList.toImmutableList(), + ) + val walletListUM = PortfolioSelectorUM( + title = resourceReference(R.string.common_choose_wallet), + items = walletList.toImmutableList(), + ) + val lockedWalletListUM = + PortfolioSelectorUM( + title = resourceReference(R.string.common_choose_wallet), + items = lockedWalletList.toImmutableList(), + ) + add(first) + add(second) + add(walletListUM) + add(lockedWalletListUM) + }, +) \ No newline at end of file