Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-02 15:30:56 +04:00
parent bfeea010d0
commit 35f073e493
6 changed files with 616 additions and 226 deletions

View file

@ -1,33 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import androidx.compose.animation.*
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.tokenlist.PortfolioListItem
import com.tangem.core.ui.components.tokenlist.PortfolioTokensListItem
import com.tangem.core.ui.components.tokenlist.state.PortfolioItemContentUM
import com.tangem.common.ui.tokens.portfolioTokensList
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.core.ui.utils.lazyListItemPosition
import kotlinx.collections.immutable.ImmutableList
internal fun LazyListScope.portfolioContentItems(
@ -41,178 +18,7 @@ internal fun LazyListScope.portfolioContentItems(
modifier = modifier,
portfolioIndex = index,
isBalanceHidden = isBalanceHidden,
testTag = MainScreenTestTags.TOKEN_LIST_ITEM,
)
}
}
internal fun LazyListScope.portfolioTokensList(
portfolio: TokensListItemUM.Portfolio,
modifier: Modifier,
portfolioIndex: Int,
isBalanceHidden: Boolean,
) {
val tokens = portfolio.tokens
val isExpanded = portfolio.isExpanded
val lastIndex = tokens.lastIndex.inc()
portfolioItem(
portfolio = portfolio,
modifier = modifier,
portfolioIndex = portfolioIndex,
isBalanceHidden = isBalanceHidden,
)
val portfolioContent = portfolio.content
if (portfolioContent is PortfolioItemContentUM.Empty) {
item(
key = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}",
contentType = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}",
) {
SlideInItemVisibility(
currentIndex = 1,
lastIndex = lastIndex,
modifier = modifier
.animateItem(fadeInSpec = null, placementSpec = null, fadeOutSpec = null)
.roundedShapeItemDecoration(
radius = TangemTheme.dimens.radius14,
currentIndex = 1,
lastIndex = 1,
backgroundColor = TangemTheme.colors.background.primary,
),
visible = isExpanded,
) {
EmptyAccountContent(portfolioContent)
}
}
return
}
itemsIndexed(
items = tokens,
key = { _, item -> item.id.toString() + "-portfolio-${portfolio.id}" },
contentType = { _, item -> item::class.java },
itemContent = { tokenIndex, token ->
val indexWithHeader = tokenIndex.inc()
SlideInItemVisibility(
currentIndex = tokenIndex,
lastIndex = lastIndex,
modifier = modifier
.testModifier(indexWithHeader)
.animateItem(fadeInSpec = null, placementSpec = null, fadeOutSpec = null)
.roundedShapeItemDecoration(
radius = TangemTheme.dimens.radius14,
currentIndex = indexWithHeader,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
),
visible = isExpanded,
) {
val modifier = if (indexWithHeader == lastIndex) Modifier.padding(bottom = 8.dp) else Modifier
PortfolioTokensListItem(
state = token,
isBalanceHidden = isBalanceHidden,
modifier = modifier,
)
}
},
)
}
@Composable
private fun EmptyAccountContent(emptyConent: PortfolioItemContentUM.Empty, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerH16()
NonContentItemContent()
val emptyAction = emptyConent.action
if (emptyAction != null) {
SpacerH16()
SecondarySmallButton(
config = SmallButtonConfig(
text = emptyAction.text,
onClick = { emptyAction.onClick() },
),
)
}
SpacerH24()
}
}
@Suppress("MagicNumber")
private fun LazyListScope.portfolioItem(
portfolio: TokensListItemUM.Portfolio,
modifier: Modifier,
portfolioIndex: Int,
isBalanceHidden: Boolean,
) {
val tokens = portfolio.tokens
val isExpanded = portfolio.isExpanded
val lastIndex = when {
isExpanded && tokens.isEmpty() -> 1
isExpanded -> tokens.lastIndex.inc()
else -> 0
}
item(
key = "account-${portfolio.id}",
contentType = "account-content",
) {
// Snap immediately on expand; on collapse, hold until all child items finish
// their shrink animation, then snap to fully-rounded shape.
val effectiveLastIndex by animateIntAsState(
targetValue = lastIndex,
animationSpec = if (lastIndex != 0) {
snap()
} else {
snap(delayMillis = minOf(50 * tokens.lastIndex, 250) + 150)
},
label = "lastIndex",
)
PortfolioListItem(
state = portfolio,
isBalanceHidden = isBalanceHidden,
modifier = modifier
.testModifier(portfolioIndex)
.roundedShapeItemDecoration(
currentIndex = 0,
radius = TangemTheme.dimens.radius14,
lastIndex = effectiveLastIndex,
backgroundColor = TangemTheme.colors.background.primary,
),
)
}
}
@Suppress("MagicNumber")
@Composable
internal fun SlideInItemVisibility(
visible: Boolean,
currentIndex: Int,
lastIndex: Int,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
val maxDelay = 250
val delayEnter = minOf(50 * currentIndex, maxDelay)
val delayExit = minOf(50 * (lastIndex - currentIndex - 1), maxDelay)
AnimatedVisibility(
modifier = modifier,
visible = visible,
enter = expandVertically(
tween(200, delayMillis = delayEnter, easing = LinearOutSlowInEasing),
expandFrom = Alignment.Top,
) + fadeIn(tween(200, delayMillis = delayEnter, easing = LinearOutSlowInEasing)),
exit = shrinkVertically(
tween(150, delayMillis = delayExit, easing = FastOutLinearInEasing),
shrinkTowards = Alignment.Top,
) + fadeOut(tween(150, delayMillis = delayExit, easing = FastOutLinearInEasing)),
) {
content()
}
}
private fun Modifier.testModifier(index: Int): Modifier = this
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
.semantics { lazyListItemPosition = index }
}

View file

@ -7,7 +7,6 @@ import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -31,9 +30,12 @@ import androidx.compose.ui.text.lerp
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.common.ui.tokens.NonContentItemContent
import com.tangem.common.ui.tokens.SlideInItemVisibility
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.account.AccountIconSize
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.tokenlist.NON_CONTENT_TOKENS_LIST_KEY
import com.tangem.core.ui.components.tokenlist.TokenListItem
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
@ -59,8 +61,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensLis
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM
import kotlinx.collections.immutable.ImmutableList
internal const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST"
/**
* LazyList extension for [WalletTokensListState]
*
@ -522,30 +522,6 @@ private fun LazyListScope.nonContentAccountItem(
}
}
@Composable
internal fun NonContentItemContent(modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing16),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
painter = painterResource(id = R.drawable.ic_empty_64),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size64),
tint = TangemTheme.colors.icon.inactive,
)
Text(
text = stringResourceSafe(id = R.string.main_empty_tokens_list_message),
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing48),
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.caption2,
)
}
}
@Composable
internal fun NonContentItemContentV2(textColor: Color, modifier: Modifier = Modifier, onClick: () -> Unit) {
Column(