Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-13 14:15:45 +07:00
parent 7a7204c6c4
commit 0a1a59d421
6 changed files with 105 additions and 38 deletions

View file

@ -16,6 +16,7 @@ import com.tangem.domain.core.lce.Lce
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.quote.PriceChange
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.isZero
@ -49,7 +50,7 @@ class AccountCryptoPortfolioItemStateConverter(
)
}
return TokenItemState.Content(
id = account.accountId.value,
id = account.accountId.toItemId(),
iconState = AccountIconItemStateConverter.convert(this),
titleState = TokenItemState.TitleState.Content(
text = accountName.toUM().value,
@ -75,7 +76,7 @@ class AccountCryptoPortfolioItemStateConverter(
private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Content {
return TokenItemState.Content(
id = account.accountId.value,
id = account.accountId.toItemId(),
iconState = AccountIconItemStateConverter.convert(account),
titleState = TokenItemState.TitleState.Content(
text = accountName.toUM().value,
@ -97,7 +98,7 @@ class AccountCryptoPortfolioItemStateConverter(
private fun Account.CryptoPortfolio.mapToUnreachableState(): TokenItemState.Unreachable {
return TokenItemState.Unreachable(
id = account.accountId.value,
id = account.accountId.toItemId(),
iconState = AccountIconItemStateConverter.convert(account),
titleState = TokenItemState.TitleState.Content(
text = accountName.toUM().value,
@ -119,6 +120,8 @@ class AccountCryptoPortfolioItemStateConverter(
)
}
private fun AccountId.toItemId() = this.userWalletId.stringValue + this.value
private fun BigDecimal.getPriceChangeType(): PriceChangeType = PriceChangeConverter.fromBigDecimal(value = this)
private fun StatusSource.isFlickering(): Boolean = this == StatusSource.CACHE

View file

@ -4,6 +4,7 @@ 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.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
@ -13,13 +14,20 @@ import javax.inject.Inject
@ModelScoped
internal class ExpandedAccountsHolder @Inject constructor(
private val singleAccountListSupplier: SingleAccountListSupplier,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
) {
private val expandedAccounts = MutableStateFlow<Map<UserWalletId, Set<AccountId>>>(mapOf())
fun expandedAccounts(userWallet: UserWallet): Flow<Set<AccountId>> = channelFlow {
walletAccounts(userWallet)
.onEach { accountList ->
combine(
flow = walletAccounts(userWallet),
flow2 = isAccountsModeEnabledUseCase.invoke(),
transform = { accountList, isAccountsMode ->
if (!isAccountsMode) {
expandedAccounts.update { emptyMap() }
return@combine
}
val isSingleAccount = accountList.accounts.size == 1
val defaultExpanded = when {
isSingleAccount -> setOf(accountList.mainAccount.accountId)
@ -31,7 +39,8 @@ internal class ExpandedAccountsHolder @Inject constructor(
if (isSingleAccount) expandedSet = defaultExpanded
map.plus(userWallet.walletId to expandedSet)
}
}.launchIn(this)
},
).launchIn(this)
expandedAccounts
.mapNotNull { map -> map[userWallet.walletId] }

View file

@ -18,6 +18,7 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
@ -55,6 +56,7 @@ import com.tangem.core.ui.components.rememberIsKeyboardVisible
import com.tangem.core.ui.components.sheetscaffold.*
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.stringResourceSafe
@ -76,6 +78,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistor
import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.nftCollections
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
@ -143,7 +146,7 @@ private fun WalletContent(
*/
val selectedWalletIndex by remember(state.selectedWalletIndex) { mutableIntStateOf(state.selectedWalletIndex) }
val selectedWallet = state.wallets.getOrElse(selectedWalletIndex) { state.wallets[state.selectedWalletIndex] }
val selectedWalletChanged = rememberChangedOnce(selectedWalletIndex)
val (expandedState, collapsedState) = getExpandPortfolioStates(selectedWallet)
val listState = rememberLazyListState()
@ -241,10 +244,16 @@ private fun WalletContent(
contentItems(
state = selectedWallet,
selectedWalletChanged = selectedWalletChanged,
txHistoryItems = txHistoryItems,
isBalanceHidden = state.isHidingMode,
modifier = movableItemModifier,
portfolioVisibleState = {
findPortfolioVisibleState(
portfolio = it,
expandedState = expandedState,
collapsedState = collapsedState,
)
},
)
nftCollections(state = selectedWallet, itemModifier = itemModifier)
@ -292,12 +301,59 @@ private fun WalletContent(
)
}
private fun findPortfolioVisibleState(
portfolio: TokensListItemUM.Portfolio,
expandedState: SnapshotStateMap<String, MutableTransitionState<Boolean>>,
collapsedState: SnapshotStateMap<String, MutableTransitionState<Boolean>>,
): MutableTransitionState<Boolean> {
val portfolioKey = portfolio.id
fun forceVisible() = MutableTransitionState(true).apply { targetState = true }
val shouldExpand = portfolio.isExpanded
return if (shouldExpand) {
collapsedState[portfolioKey] ?: forceVisible()
} else {
expandedState[portfolioKey] ?: forceVisible()
}
}
@Composable
private fun rememberChangedOnce(selectedWalletIndex: Int): Boolean {
var prev by remember { mutableIntStateOf(selectedWalletIndex) }
val changed = prev != selectedWalletIndex
SideEffect { prev = selectedWalletIndex }
return changed
private fun getExpandPortfolioStates(
state: WalletState,
): Pair<
SnapshotStateMap<String, MutableTransitionState<Boolean>>,
SnapshotStateMap<String, MutableTransitionState<Boolean>>,
> {
val expandedTransitionState =
remember { mutableStateMapOf<String, MutableTransitionState<Boolean>>() }
val collapsedTransitionState =
remember { mutableStateMapOf<String, MutableTransitionState<Boolean>>() }
val portfolioContent = state is WalletState.MultiCurrency.Content &&
state.tokensListState is WalletTokensListState.ContentState.PortfolioContent
if (!portfolioContent) {
return expandedTransitionState to collapsedTransitionState
}
state.tokensListState.items.fastForEach { portfolio ->
val portfolioKey = portfolio.id
val shouldExpand = portfolio.isExpanded
fun toggleVisible() = MutableTransitionState(false).apply { targetState = true }
fun forceVisible() = MutableTransitionState(true).apply { targetState = true }
val isFirstCall = collapsedTransitionState[portfolioKey] == null ||
expandedTransitionState[portfolioKey] == null
when {
isFirstCall -> {
collapsedTransitionState[portfolioKey] = forceVisible()
expandedTransitionState[portfolioKey] = forceVisible()
}
shouldExpand -> expandedTransitionState[portfolioKey] = toggleVisible()
else -> collapsedTransitionState[portfolioKey] = toggleVisible()
}
}
return expandedTransitionState to collapsedTransitionState
}
@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod")

View file

@ -1,8 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import androidx.paging.compose.LazyPagingItems
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.txHistoryItems
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
@ -22,11 +24,11 @@ internal fun LazyListScope.contentItems(
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>?,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
selectedWalletChanged: Boolean,
portfolioVisibleState: (portfolio: TokensListItemUM.Portfolio) -> MutableTransitionState<Boolean>,
) {
when (state) {
is WalletState.MultiCurrency -> {
tokensListItems(state.tokensListState, modifier, isBalanceHidden, selectedWalletChanged)
tokensListItems(state.tokensListState, modifier, isBalanceHidden, portfolioVisibleState)
}
is WalletState.SingleCurrency -> {
txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier)

View file

@ -8,9 +8,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
@ -27,7 +25,7 @@ internal fun LazyListScope.portfolioContentItems(
items: ImmutableList<TokensListItemUM.Portfolio>,
modifier: Modifier = Modifier,
isBalanceHidden: Boolean,
selectedWalletChanged: Boolean,
portfolioVisibleState: (portfolio: TokensListItemUM.Portfolio) -> MutableTransitionState<Boolean>,
) {
items.forEachIndexed { index, item ->
portfolioTokensList(
@ -35,7 +33,7 @@ internal fun LazyListScope.portfolioContentItems(
modifier = modifier,
portfolioIndex = index,
isBalanceHidden = isBalanceHidden,
selectedWalletChanged = selectedWalletChanged,
portfolioVisibleState = portfolioVisibleState,
)
}
}
@ -45,7 +43,7 @@ internal fun LazyListScope.portfolioTokensList(
modifier: Modifier,
portfolioIndex: Int,
isBalanceHidden: Boolean,
selectedWalletChanged: Boolean,
portfolioVisibleState: (portfolio: TokensListItemUM.Portfolio) -> MutableTransitionState<Boolean>,
) {
val tokens = portfolio.tokens
val isExpanded = portfolio.isExpanded
@ -55,7 +53,7 @@ internal fun LazyListScope.portfolioTokensList(
modifier = modifier,
portfolioIndex = portfolioIndex,
isBalanceHidden = isBalanceHidden,
selectedWalletChanged = selectedWalletChanged,
portfolioVisibleState = portfolioVisibleState,
)
if (!isExpanded) return
if (tokens.isEmpty()) {
@ -63,17 +61,22 @@ internal fun LazyListScope.portfolioTokensList(
key = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}",
contentType = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}",
) {
NonContentItemContent(
modifier = Modifier
val appear = portfolioVisibleState(portfolio)
SlideInItemVisibility(
modifier = modifier
.animateItem()
.roundedShapeItemDecoration(
radius = TangemTheme.dimens.radius14,
currentIndex = 1,
lastIndex = 1,
backgroundColor = TangemTheme.colors.background.primary,
)
.padding(vertical = TangemTheme.dimens.spacing28),
)
),
visibleState = appear,
) {
NonContentItemContent(
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing28),
)
}
}
return
}
@ -84,10 +87,7 @@ internal fun LazyListScope.portfolioTokensList(
itemContent = { tokenIndex, token ->
val indexWithHeader = tokenIndex.inc()
val lastIndex = tokens.lastIndex.inc()
val isPreview = LocalInspectionMode.current
val appear = remember {
MutableTransitionState(selectedWalletChanged || isPreview).apply { targetState = true }
}
val appear = portfolioVisibleState(portfolio)
SlideInItemVisibility(
modifier = modifier
.testModifier(indexWithHeader)
@ -116,7 +116,7 @@ private fun LazyListScope.portfolioItem(
modifier: Modifier,
portfolioIndex: Int,
isBalanceHidden: Boolean,
selectedWalletChanged: Boolean,
portfolioVisibleState: (portfolio: TokensListItemUM.Portfolio) -> MutableTransitionState<Boolean>,
) {
val tokens = portfolio.tokens
val isExpanded = portfolio.isExpanded
@ -140,11 +140,7 @@ private fun LazyListScope.portfolioItem(
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
)
val isPreview = LocalInspectionMode.current
val appear = remember {
MutableTransitionState(selectedWalletChanged || isPreview || tokens.isEmpty())
.apply { targetState = true }
}
val appear = portfolioVisibleState(portfolio)
if (isExpanded) {
SlideInItemVisibility(
modifier = anchorModifier,

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@ -40,14 +41,14 @@ internal fun LazyListScope.tokensListItems(
state: WalletTokensListState,
modifier: Modifier = Modifier,
isBalanceHidden: Boolean,
selectedWalletChanged: Boolean,
portfolioVisibleState: (portfolio: TokensListItemUM.Portfolio) -> MutableTransitionState<Boolean>,
) {
when (state) {
is WalletTokensListState.ContentState.PortfolioContent -> portfolioContentItems(
items = state.items,
isBalanceHidden = isBalanceHidden,
modifier = modifier,
selectedWalletChanged = selectedWalletChanged,
portfolioVisibleState = portfolioVisibleState,
)
is WalletTokensListState.ContentState.Content,
is WalletTokensListState.ContentState.Loading,