diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt index c778c6c0a4..6b20a931f8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt @@ -31,7 +31,11 @@ fun HorizontalActionChips( verticalAlignment = Alignment.CenterVertically, contentPadding = contentPadding, ) { - items(items = buttons, itemContent = { ActionButton(config = it) }) + items( + items = buttons, + key = { config -> "${config.text.hashCode()} ${config.iconResId}" }, + itemContent = { ActionButton(config = it) }, + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index cfb5eeb72e..f7e10f8451 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -5,8 +5,8 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.marketprice.PriceChangeConfig import com.tangem.core.ui.components.transactions.state.TransactionState -import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.components.transactions.state.TxHistoryState +import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState @@ -142,7 +142,7 @@ internal object WalletPreviewData { ) } - val loadingTokenItemState by lazy { TokenItemState.Loading } + val loadingTokenItemState by lazy { TokenItemState.Loading(id = "Loading#1") } private const val networksSize = 10 private const val tokensSize = 3 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt index 5c4cb19fb7..8effab7dad 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -1,6 +1,9 @@ package com.tangem.feature.wallet.presentation.common.component import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -16,11 +19,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstrainedLayoutReference import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.ConstraintLayoutScope @@ -124,7 +127,7 @@ internal fun UnreachableTokenItem(state: TokenItemState.Unreachable, modifier: M options = { ref -> Text( modifier = Modifier.constrainAsOptionsItem(scope = this, ref), - text = "Unreachable", // TODO (conform this text) + text = stringResource(id = R.string.common_unreachable), style = TangemTypography.body2, color = TangemTheme.colors.text.tertiary, ) @@ -143,7 +146,7 @@ private fun LoadingTokenItem(modifier: Modifier = Modifier) { vertical = TangemTheme.dimens.spacing4, ), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), ) { CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42)) Row( @@ -188,22 +191,17 @@ private fun LoadingTokenItem(modifier: Modifier = Modifier) { * Block for end part of token item * shows status is reachable, is drag, hidden or show balance */ +@OptIn(ExperimentalAnimationApi::class) @Composable private fun TokenOptionsBlock(state: TokenOptionsState, modifier: Modifier = Modifier) { - when (state) { - is TokenOptionsState.Visible -> { - TokenFiatPercentageBlock( - modifier = modifier, - fiatAmount = state.fiatAmount, - priceChange = state.priceChange, - ) - } - is TokenOptionsState.Hidden -> { - TokenFiatPercentageBlock( - modifier = modifier, - fiatAmount = DOTS, - priceChange = state.priceChange, - ) + AnimatedContent(targetState = state, label = "Update the options", modifier = modifier) { options -> + when (options) { + is TokenOptionsState.Visible -> { + TokenFiatPercentageBlock(fiatAmount = options.fiatAmount, priceChange = options.priceChange) + } + is TokenOptionsState.Hidden -> { + TokenFiatPercentageBlock(fiatAmount = DOTS, priceChange = options.priceChange) + } } } } @@ -243,7 +241,7 @@ private fun InternalTokenItem( TokenTitleAmountBlock( modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing12) + .padding(horizontal = TangemTheme.dimens.spacing8) .constrainAs(tokenNameItem) { centerVerticallyTo(parent) start.linkTo(iconItem.end) @@ -298,17 +296,18 @@ private fun TokenTitleAmountBlock(title: String, amount: String?, hasPending: Bo style = TangemTypography.subtitle2, color = TangemTheme.colors.text.primary1, ) - if (hasPending) { + + AnimatedVisibility(visible = hasPending, modifier = Modifier.align(Alignment.CenterVertically)) { Image( - modifier = Modifier.align(Alignment.CenterVertically), painter = painterResource(id = R.drawable.img_loader_15), contentDescription = null, ) } } - if (!amount.isNullOrBlank()) { + + AnimatedVisibility(visible = !amount.isNullOrBlank()) { Text( - text = amount, + text = requireNotNull(amount), style = TangemTypography.body2, color = TangemTheme.colors.text.tertiary, ) @@ -316,6 +315,7 @@ private fun TokenTitleAmountBlock(title: String, amount: String?, hasPending: Bo } } +@OptIn(ExperimentalAnimationApi::class) @Composable private fun TokenFiatPercentageBlock( fiatAmount: String, @@ -334,30 +334,39 @@ private fun TokenFiatPercentageBlock( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End, ) { - val iconChangeArrow: Int - val changeTextColor: Color - when (priceChange.type) { - PriceChangeConfig.Type.UP -> { - iconChangeArrow = R.drawable.img_arrow_up_8 - changeTextColor = TangemTheme.colors.text.accent - } - PriceChangeConfig.Type.DOWN -> { - iconChangeArrow = R.drawable.img_arrow_down_8 - changeTextColor = TangemTheme.colors.text.warning - } + AnimatedContent( + targetState = priceChange.type, + label = "Update the price change's arrow", + modifier = Modifier.align(Alignment.CenterVertically), + ) { + Image( + painter = painterResource( + id = when (priceChange.type) { + PriceChangeConfig.Type.UP -> R.drawable.img_arrow_up_8 + PriceChangeConfig.Type.DOWN -> R.drawable.img_arrow_down_8 + }, + ), + contentDescription = null, + ) } - Image( - modifier = Modifier.align(Alignment.CenterVertically), - painter = painterResource(id = iconChangeArrow), - contentDescription = null, - ) + SpacerW4() - Text( + + AnimatedContent( + targetState = priceChange.type, + label = "Update the price change's arrow", modifier = Modifier.align(Alignment.CenterVertically), - text = priceChange.valueInPercent, - style = TangemTypography.body2, - color = changeTextColor, - ) + ) { + Text( + modifier = Modifier.align(Alignment.CenterVertically), + text = priceChange.valueInPercent, + style = TangemTypography.body2, + color = when (priceChange.type) { + PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent + PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning + }, + ) + } } } } @@ -389,20 +398,20 @@ private fun TokenIcon( contentDescription = null, ) - if (networkIconRes != null) { - Box( + AnimatedVisibility( + visible = networkIconRes != null, + modifier = Modifier + .align(Alignment.TopEnd) + .size(TangemTheme.dimens.size18) + .background(color = Color.White, shape = CircleShape), + ) { + Image( modifier = Modifier - .align(Alignment.TopEnd) - .size(TangemTheme.dimens.size18) - .background(color = Color.White, shape = CircleShape), - contentAlignment = Alignment.Center, - ) { - Image( - modifier = Modifier.padding(all = 0.5.dp), - painter = painterResource(id = networkIconRes), - contentDescription = null, - ) - } + .padding(all = TangemTheme.dimens.spacing0_5) + .align(Alignment.Center), + painter = painterResource(id = requireNotNull(networkIconRes)), + contentDescription = null, + ) } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt index b1c8417e9e..b53c7458b8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt @@ -8,12 +8,16 @@ import com.tangem.core.ui.components.marketprice.PriceChangeConfig @Immutable internal sealed interface TokenItemState { + /** Unique id */ + val id: String + /** Loading token state */ - object Loading : TokenItemState + data class Loading(override val id: String) : TokenItemState /** * Content token state * + * @property id unique id * @property tokenIconUrl token icon url * @property tokenIconResId token icon resource id * @property networkIconResId network icon resource id, may be null if it is a coin @@ -23,7 +27,7 @@ internal sealed interface TokenItemState { * @property tokenOptions state for token options */ data class Content( - val id: String, + override val id: String, val tokenIconUrl: String?, @DrawableRes val tokenIconResId: Int, @DrawableRes val networkIconResId: Int?, @@ -36,13 +40,15 @@ internal sealed interface TokenItemState { /** * Draggable token state * + * @property id unique id * @property tokenIconUrl token icon url * @property tokenIconResId token icon resource id * @property networkIconResId network icon resource id, may be null if it is a coin * @property name token name + * @property fiatAmount fiat amount of token */ data class Draggable( - val id: String, + override val id: String, val tokenIconUrl: String?, @DrawableRes val tokenIconResId: Int, @DrawableRes val networkIconResId: Int?, @@ -60,7 +66,7 @@ internal sealed interface TokenItemState { * @property name token name */ data class Unreachable( - val id: String, + override val id: String, val tokenIconUrl: String?, @DrawableRes val tokenIconResId: Int, @DrawableRes val networkIconResId: Int?, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt index 98430d72a6..cc607f9f17 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt @@ -1,8 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.components +import com.tangem.core.ui.components.wallet.WalletLockedContentState import com.tangem.core.ui.extensions.TextReference import com.tangem.feature.wallet.impl.R -import com.tangem.core.ui.components.wallet.WalletLockedContentState import com.tangem.feature.wallet.presentation.common.state.TokenItemState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -23,9 +23,8 @@ internal sealed class WalletTokensListState( /** Loading content state */ object Loading : WalletTokensListState( items = persistentListOf( - TokensListItemState.NetworkGroupTitle(value = TextReference.Res(id = R.string.main_tokens)), - TokensListItemState.Token(state = TokenItemState.Loading), - TokensListItemState.Token(state = TokenItemState.Loading), + TokensListItemState.Token(state = TokenItemState.Loading(id = FIRST_LOADING_TOKEN_ID)), + TokensListItemState.Token(state = TokenItemState.Loading(id = SECOND_LOADING_TOKEN_ID)), ), onOrganizeTokensClick = null, ) @@ -46,7 +45,7 @@ internal sealed class WalletTokensListState( WalletTokensListState( items = persistentListOf( TokensListItemState.NetworkGroupTitle(value = TextReference.Res(id = R.string.main_tokens)), - TokensListItemState.Token(state = TokenItemState.Loading), + TokensListItemState.Token(state = TokenItemState.Loading(id = LOCKED_TOKEN_ID)), ), onOrganizeTokensClick = null, ), @@ -69,4 +68,10 @@ internal sealed class WalletTokensListState( */ data class Token(val state: TokenItemState) : TokensListItemState } + + private companion object { + const val FIRST_LOADING_TOKEN_ID = "Loading#1" + const val SECOND_LOADING_TOKEN_ID = "Loading#2" + const val LOCKED_TOKEN_ID = "Locked#1" + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt index dcae40e426..09acf941f4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt @@ -45,7 +45,11 @@ internal fun WalletsList(config: WalletsListConfig, lazyListState: LazyListState horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState), ) { - items(items = config.wallets, key = { it.id.stringValue }) { state -> + items( + items = config.wallets, + key = { it.id.stringValue }, + contentType = { it::class.java }, + ) { state -> WalletCard( state = state, modifier = Modifier diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt index baa0144e1b..c6ef7f89ef 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt @@ -21,6 +21,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList index }, + key = { _, item -> + when (item) { + is WalletTokensListState.TokensListItemState.NetworkGroupTitle -> item.value.hashCode() + is WalletTokensListState.TokensListItemState.Token -> item.state.id + } + }, + contentType = { _, item -> item::class.java }, itemContent = { index, item -> MultiCurrencyContentItem( state = item, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt index a995ca2715..210bfd423c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt @@ -4,6 +4,8 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Modifier +private const val ORGANIZE_BUTTON_CONTENT_TYPE = "OrganizeTokensButton" + /** * Organize tokens button * @@ -14,5 +16,7 @@ import androidx.compose.ui.Modifier */ @OptIn(ExperimentalFoundationApi::class) internal fun LazyListScope.organizeButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) { - item { OrganizeTokensButton(onClick = onClick, modifier = modifier.animateItemPlacement()) } + item(key = ORGANIZE_BUTTON_CONTENT_TYPE, contentType = ORGANIZE_BUTTON_CONTENT_TYPE) { + OrganizeTokensButton(onClick = onClick, modifier = modifier.animateItemPlacement()) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt index f88ea6d07f..4843bd9dc4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt @@ -10,6 +10,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.components.WalletMana import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +private const val CONTROL_BUTTONS_CONTENT_TYPE = "ControlButtons" + /** * Single currency control buttons. Like, "Buy", "Sell", etc * @@ -20,7 +22,7 @@ import kotlinx.collections.immutable.toImmutableList */ @OptIn(ExperimentalFoundationApi::class) internal fun LazyListScope.controlButtons(configs: ImmutableList, modifier: Modifier = Modifier) { - item { + item(key = CONTROL_BUTTONS_CONTENT_TYPE, contentType = CONTROL_BUTTONS_CONTENT_TYPE) { HorizontalActionChips( buttons = configs.map(WalletManageButton::config).toImmutableList(), modifier = modifier.animateItemPlacement(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyMarketPriceBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyMarketPriceBlock.kt index b839d42baf..12d9b505b7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyMarketPriceBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyMarketPriceBlock.kt @@ -16,5 +16,10 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState */ @OptIn(ExperimentalFoundationApi::class) internal fun LazyListScope.marketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier) { - item { MarketPriceBlock(state = state, modifier = modifier.animateItemPlacement()) } + item( + key = MarketPriceBlockState::class.java, + contentType = MarketPriceBlockState::class.java, + ) { + MarketPriceBlock(state = state, modifier = modifier.animateItemPlacement()) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt index 76a4c01582..2779e6473a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt @@ -30,7 +30,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter( override fun convert(value: CryptoCurrencyStatus): TokenItemState { return when (value.value) { - is CryptoCurrencyStatus.Loading -> TokenItemState.Loading + is CryptoCurrencyStatus.Loading -> TokenItemState.Loading(id = value.currency.id.value) is CryptoCurrencyStatus.Loaded, is CryptoCurrencyStatus.Custom, is CryptoCurrencyStatus.NoQuote, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt index 174c0b9f50..c7e545dec3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt @@ -8,8 +8,14 @@ import kotlinx.collections.immutable.toImmutableList internal object LoadingItemsProvider { fun getLoadingMultiCurrencyTokens(): ImmutableList { - return buildList(capacity = 5) { - add(WalletTokensListState.TokensListItemState.Token(state = TokenItemState.Loading)) - }.toImmutableList() + val items = mutableListOf() + repeat(times = 5) { + items.add( + WalletTokensListState.TokensListItemState.Token( + state = TokenItemState.Loading(id = "Loading#$it"), + ), + ) + } + return items.toImmutableList() } } \ No newline at end of file