Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-22 16:48:40 +07:00
parent 9abc118480
commit d3138c662e
18 changed files with 288 additions and 108 deletions

View file

@ -1,6 +1,8 @@
package com.tangem.common.ui.account
import com.tangem.common.ui.R
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State
@ -8,15 +10,21 @@ 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.core.ui.format.bigdecimal.percent
import com.tangem.domain.appcurrency.model.AppCurrency
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.quote.PriceChange
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.isZero
import java.math.BigDecimal
class AccountCryptoPortfolioItemStateConverter(
private val appCurrency: AppCurrency,
private val account: Account.CryptoPortfolio,
private val priceChangeLce: Lce<Unit, PriceChange>? = null,
private val onItemClick: ((Account.CryptoPortfolio) -> Unit)? = null,
private val onItemLongClick: ((Account.CryptoPortfolio) -> Unit)? = null,
) : Converter<TotalFiatBalance, TokenItemState> {
@ -32,6 +40,14 @@ class AccountCryptoPortfolioItemStateConverter(
private fun Account.CryptoPortfolio.mapToContentState(
fiatBalance: TotalFiatBalance.Loaded,
): TokenItemState.Content {
val subtitle2State = when (fiatBalance.amount.isZero()) {
true -> null
false -> priceChangeLce?.fold(
ifLoading = { priceChange -> priceChange?.toSubtitle2State() ?: Subtitle2State.Loading },
ifError = { null },
ifContent = { priceChange -> priceChange.toSubtitle2State() },
)
}
return TokenItemState.Content(
id = account.accountId.value,
iconState = AccountIconItemStateConverter.convert(this),
@ -51,7 +67,7 @@ class AccountCryptoPortfolioItemStateConverter(
.format { fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol) },
isFlickering = fiatBalance.source == StatusSource.CACHE,
),
subtitle2State = null,
subtitle2State = subtitle2State,
onItemClick = onItemClick?.let { onItemClick -> { onItemClick(account) } },
onItemLongClick = onItemLongClick?.let { onItemLongClick -> { onItemLongClick(account) } },
)
@ -102,4 +118,14 @@ class AccountCryptoPortfolioItemStateConverter(
},
)
}
private fun BigDecimal.getPriceChangeType(): PriceChangeType = PriceChangeConverter.fromBigDecimal(value = this)
private fun StatusSource.isFlickering(): Boolean = this == StatusSource.CACHE
private fun PriceChange.toSubtitle2State(): Subtitle2State = Subtitle2State.PriceChangeContent(
priceChangePercent = this.value.format { percent() },
type = this.value.getPriceChangeType(),
isFlickering = this.source.isFlickering(),
)
}

View file

@ -33,7 +33,7 @@ sealed interface AccountNameUM {
*
* @property raw the raw string value of the custom account name
*/
class Custom(internal val raw: String) : AccountNameUM {
data class Custom(internal val raw: String) : AccountNameUM {
override val value: TextReference = stringReference(value = raw)
}

View file

@ -41,6 +41,8 @@ fun PortfolioSelectRow(state: PortfolioSelectUM, modifier: Modifier = Modifier)
Text(
modifier = Modifier.weight(1f),
text = stringResourceSafe(leftText),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)

View file

@ -27,6 +27,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.token.internal.*
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.rememberHapticFeedback
import com.tangem.core.ui.extensions.stringReference
@ -691,7 +692,10 @@ object AccountItemPreviewData {
value = stringReference("24 tokens"),
isAvailable = false,
),
subtitle2State = null,
subtitle2State = Subtitle2State.PriceChangeContent(
priceChangePercent = "0,43 %",
type = PriceChangeType.UP,
),
onItemClick = {},
onItemLongClick = {},
)

View file

@ -11,7 +11,6 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.audits.AuditLabel
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
@ -31,7 +30,7 @@ internal fun TokenCryptoAmount(
isFlickering = state.isFlickering,
)
}
is TokenItemState.Subtitle2State.LabelContent -> {
is TokenCryptoAmountState.LabelContent -> {
AuditLabel(state = state.auditLabelUM, modifier = modifier)
}
is TokenCryptoAmountState.Unreachable -> {
@ -46,6 +45,15 @@ internal fun TokenCryptoAmount(
is TokenCryptoAmountState.Locked -> {
LockedRectangle(modifier = modifier.placeholderSize())
}
is TokenCryptoAmountState.PriceChangeContent -> {
PriceBlock(
modifier = modifier,
price = null,
type = state.type,
priceChangePercent = state.priceChangePercent,
isFlickering = state.isFlickering,
)
}
null -> Unit
}
}

View file

@ -64,8 +64,8 @@ internal fun TokenPrice(state: TokenPriceState?, modifier: Modifier = Modifier)
}
@Composable
private fun PriceBlock(
price: String,
internal fun PriceBlock(
price: String?,
isFlickering: Boolean,
modifier: Modifier = Modifier,
type: PriceChangeType? = null,
@ -75,13 +75,15 @@ private fun PriceBlock(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
PriceText(
modifier = Modifier.weight(weight = 1f, fill = false),
text = price,
isFlickering = isFlickering,
)
if (price != null) {
PriceText(
modifier = Modifier.weight(weight = 1f, fill = false),
text = price,
isFlickering = isFlickering,
)
SpacerW6()
SpacerW6()
}
if (type != null) {
PriceChangeIcon(

View file

@ -230,6 +230,12 @@ sealed class TokenItemState {
val isFlickering: Boolean = false,
) : Subtitle2State()
data class PriceChangeContent(
val priceChangePercent: String,
val type: PriceChangeType,
val isFlickering: Boolean = false,
) : Subtitle2State()
data class LabelContent(val auditLabelUM: AuditLabelUM) : Subtitle2State()
data object Unreachable : Subtitle2State()

View file

@ -205,8 +205,8 @@ internal class AccountCreateEditModel @Inject constructor(
is AccountCreateEditComponent.Params.Edit -> {
val oldName = params.account.accountName.toUM()
val isNewName = this.account.name != oldName
val isNewIcon = this.account.portfolioIcon != params.account.portfolioIcon
val isNewName = this.account.name.trim() != oldName
val isNewIcon = this.account.portfolioIcon != params.account.portfolioIcon.toUM()
isValidName && (isNewName || isNewIcon)
}
}
@ -276,3 +276,8 @@ internal class AccountCreateEditModel @Inject constructor(
messageSender.send(dialogMessage)
}
}
private fun AccountNameUM.trim(): AccountNameUM = when (this) {
is AccountNameUM.Custom -> this.toDomain().getOrNull()?.toUM() ?: this
AccountNameUM.DefaultMain -> this
}

View file

@ -12,7 +12,8 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.producer.SingleAccountProducer
import com.tangem.domain.account.supplier.SingleAccountSupplier
import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.Account
@ -35,7 +36,7 @@ internal class AccountDetailsModel @Inject constructor(
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
private val archiveCryptoPortfolioUseCase: ArchiveCryptoPortfolioUseCase,
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
singleAccountSupplier: SingleAccountSupplier,
private val getUserWalletUseCase: GetUserWalletUseCase,
) : Model() {
@ -46,22 +47,19 @@ internal class AccountDetailsModel @Inject constructor(
private val accountId = params.account.accountId
init {
singleAccountStatusListSupplier(accountId.userWalletId)
.map { it.accountStatuses.find { status -> status.account.accountId == accountId }?.account }
.filterNotNull()
.distinctUntilChanged()
.map { account -> buildUI(account) }
singleAccountSupplier(SingleAccountProducer.Params(accountId))
.onEach { account -> _uiState.update { buildUI(account) } }
.launchIn(modelScope)
}
private fun onEditAccountClick() {
router.push(AppRoute.EditAccount(params.account))
private fun onEditAccountClick(account: Account) {
router.push(AppRoute.EditAccount(account))
}
private fun onManageTokensClick() {
private fun onManageTokensClick(account: Account) {
val route = AppRoute.ManageTokens(
source = AppRoute.ManageTokens.Source.SETTINGS,
portfolioId = PortfolioId(params.account.accountId),
portfolioId = PortfolioId(account.accountId),
)
router.push(route)
}
@ -92,7 +90,7 @@ internal class AccountDetailsModel @Inject constructor(
private fun archiveCryptoPortfolio() = modelScope.launch {
_uiState.update { it.toggleProgress(true) }
archiveCryptoPortfolioUseCase(params.account.accountId)
archiveCryptoPortfolioUseCase(accountId)
.onLeft { error ->
failedArchiveDialog(error)
_uiState.update { it.toggleProgress(false) }
@ -138,14 +136,14 @@ internal class AccountDetailsModel @Inject constructor(
)
}
}
val isMultiCurrency = getUserWalletUseCase(params.account.accountId.userWalletId)
val isMultiCurrency = getUserWalletUseCase(account.accountId.userWalletId)
.getOrNull()?.isMultiCurrency ?: false
return AccountDetailsUM(
accountName = params.account.accountName.toUM().value,
accountIcon = params.account.portfolioIcon.toUM(),
accountName = account.accountName.toUM().value,
accountIcon = account.portfolioIcon.toUM(),
onCloseClick = { router.pop() },
onAccountEditClick = ::onEditAccountClick,
onManageTokensClick = ::onManageTokensClick,
onAccountEditClick = { onEditAccountClick(account) },
onManageTokensClick = { onManageTokensClick(account) },
archiveMode = archiveMode,
isManageTokensAvailable = isMultiCurrency,
)

View file

@ -17,6 +17,7 @@ import com.tangem.features.account.PortfolioSelectorComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.serialization.builtins.serializer
class DefaultReferralComponent @AssistedInject constructor(
private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory,
@ -27,7 +28,7 @@ class DefaultReferralComponent @AssistedInject constructor(
private val model: ReferralModel = getOrCreateModel(params)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = null,
serializer = Unit.serializer(),
handleBackButton = false,
childFactory = { configuration, context -> bottomSheetChild(context) },
)

View file

@ -85,8 +85,8 @@ internal class ReferralModel @Inject constructor(
private set
val portfolioSelectorCallback = object : PortfolioSelectorComponent.BottomSheetCallback {
override val onDismiss: () -> Unit = { bottomSheetNavigation::dismiss }
override val onBack: () -> Unit = { bottomSheetNavigation::dismiss }
override val onDismiss: () -> Unit = { bottomSheetNavigation.dismiss() }
override val onBack: () -> Unit = { bottomSheetNavigation.dismiss() }
}
init {
@ -115,7 +115,8 @@ internal class ReferralModel @Inject constructor(
}
private fun combineAccountUI(referralData: ReferralData): Flow<AccountAward?> = combine(
flow = portfolioSelectorController.selectedAccountWithData(portfolioFetcher),
flow = portfolioSelectorController.selectedAccountWithData(portfolioFetcher)
.onEach { bottomSheetNavigation.dismiss() },
flow2 = getBalanceHidingSettingsUseCase.isBalanceHidden(),
flow3 = getSelectedAppCurrencyUseCase.invokeOrDefault(),
flow4 = portfolioFetcher.data,

View file

@ -10,25 +10,33 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.draw.clip
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
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 androidx.compose.ui.unit.dp
import com.tangem.common.ui.account.AccountIcon
import com.tangem.core.res.getStringSafe
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.account.AccountIconSize
import com.tangem.core.ui.components.rows.RoundableCornersRow
import com.tangem.core.ui.extensions.pluralStringResourceSafe
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.referral.domain.models.ExpectedAward
import com.tangem.feature.referral.domain.models.ExpectedAwards
import com.tangem.feature.referral.models.ReferralStateHolder
import com.tangem.feature.referral.presentation.R
import kotlinx.coroutines.launch
@ -43,6 +51,7 @@ internal fun ParticipateBottomBlock(
onAgreementClick: () -> Unit,
onCopyClick: () -> Unit,
onShareClick: (String) -> Unit,
accountAward: ReferralStateHolder.AccountAward?,
) {
Column(
modifier = Modifier
@ -53,7 +62,7 @@ internal fun ParticipateBottomBlock(
.padding(horizontal = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
) {
PersonalCodeCard(code = code)
PersonalCodeCard(code = code, accountAward = accountAward)
AdditionalButtons(
code = code,
shareLink = shareLink,
@ -242,19 +251,13 @@ private fun ExtraItems(extraItems: List<ExpectedAward>, overallItemsLastIndex: I
}
@Composable
private fun PersonalCodeCard(code: String) {
private fun PersonalCodeCard(code: String, accountAward: ReferralStateHolder.AccountAward?) {
Column(
modifier = Modifier
.shadow(
elevation = TangemTheme.dimens.elevation2,
shape = RoundedCornerShape(TangemTheme.dimens.radius12),
)
.background(
color = TangemTheme.colors.background.secondary,
shape = RoundedCornerShape(TangemTheme.dimens.radius12),
)
.clip(RoundedCornerShape(TangemTheme.dimens.radius12))
.background(color = TangemTheme.colors.background.primary)
.fillMaxWidth()
.padding(vertical = TangemTheme.dimens.spacing12),
.padding(top = TangemTheme.dimens.spacing12),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
@ -270,6 +273,53 @@ private fun PersonalCodeCard(code: String) {
maxLines = 1,
style = TangemTheme.typography.h2,
)
if (accountAward != null) {
AwardAccount(accountAward)
} else {
SpacerH12()
}
}
}
@Composable
private fun AwardAccount(accountAward: ReferralStateHolder.AccountAward) {
Column {
HorizontalDivider(
thickness = 0.5.dp,
color = TangemTheme.colors.stroke.primary,
modifier = Modifier.padding(horizontal = 12.dp),
)
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
text = stringResourceSafe(R.string.account_for_rewards),
style = TangemTheme.typography.body1,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.secondary,
)
SpacerW12()
val icon = accountAward.accountSelectUM.icon
if (icon != null) {
AccountIcon(
name = accountAward.accountSelectUM.name,
icon = icon,
size = AccountIconSize.Small,
)
}
Text(
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 4.dp),
text = accountAward.accountSelectUM.name.resolveReference(),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
}
}
}
@ -351,10 +401,11 @@ private fun ParticipateBottomBlockPreview(
code = data.code,
shareLink = data.shareLink,
expectedAwards = data.expectedAwards,
onAgreementClick = data.onAgreementClick,
snackbarHostState = SnackbarHostState(),
onAgreementClick = data.onAgreementClick,
onCopyClick = data.onCopyClick,
onShareClick = data.onShareClick,
accountAward = data.accountAward,
)
}
}
@ -410,12 +461,18 @@ private class ParticipateBottomBlockDataProvider : CollectionPreviewParameterPro
purchasedWalletCount = 0,
expectedAwards = null,
),
ParticipateBottomBlockData(
purchasedWalletCount = 0,
expectedAwards = null,
accountAward = accountReward,
),
),
)
private data class ParticipateBottomBlockData(
val purchasedWalletCount: Int,
val expectedAwards: ExpectedAwards?,
val accountAward: ReferralStateHolder.AccountAward? = null,
val code: String = "x4JDK",
val shareLink: String = "",
val onAgreementClick: () -> Unit = {},

View file

@ -185,6 +185,7 @@ private fun ReferralInfo(
code = state.code,
shareLink = state.shareLink,
expectedAwards = state.expectedAwards,
accountAward = state.accountAward,
snackbarHostState = snackbarHostState,
onAgreementClick = onAgreementClick,
onCopyClick = stateHolder.analytics.onCopyClicked,
@ -479,6 +480,7 @@ private fun Preview_ReferralScreen_Participant_With_Referrals() {
code = "x4JdK",
shareLink = "",
url = "",
accountAward = accountReward,
expectedAwards = ExpectedAwards(
numberOfWallets = 5,
expectedAwards = listOf(
@ -535,17 +537,45 @@ private fun Preview_ReferralScreen_NonParticipant() {
}
}
internal val account = PortfolioSelectUM(
icon = AccountIconPreviewData.randomAccountIcon(),
name = AccountName.DefaultMain.toUM().value,
isAccountMode = true,
isMultiChoice = true,
onClick = {},
)
internal val accountReward
get() = AccountAward(
accountSelectUM = account,
isBalanceHidden = false,
tokenState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_tron_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
titleState = TokenItemState.TitleState.Content(
text = stringReference(value = "Tether"),
),
fiatAmountState = FiatAmountState.Content(
text = "129,65 $",
),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "129,65 USDT"),
subtitleState = TokenItemState.SubtitleState.TextContent(value = stringReference("USDT")),
onItemClick = null,
onItemLongClick = null,
),
)
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_ReferralScreen_NonParticipantAccount() {
val account = PortfolioSelectUM(
icon = AccountIconPreviewData.randomAccountIcon(),
name = AccountName.DefaultMain.toUM().value,
isAccountMode = true,
isMultiChoice = true,
onClick = {},
)
TangemThemePreview {
ReferralScreen(
stateHolder = ReferralStateHolder(
@ -557,31 +587,7 @@ private fun Preview_ReferralScreen_NonParticipantAccount() {
url = "",
onParticipateClicked = {},
participateButtonIcon = R.drawable.ic_tangem_24,
accountAward = AccountAward(
accountSelectUM = account,
isBalanceHidden = false,
tokenState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_tron_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
titleState = TokenItemState.TitleState.Content(
text = stringReference(value = "Tether"),
),
fiatAmountState = FiatAmountState.Content(
text = "129,65 $",
),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "129,65 USDT"),
subtitleState = TokenItemState.SubtitleState.TextContent(value = stringReference("USDT")),
onItemClick = null,
onItemLongClick = null,
),
),
accountAward = accountReward,
),
errorSnackbar = null,
analytics = Analytics(

View file

@ -106,6 +106,28 @@ internal object WalletScreenPreviewData {
),
)
private val emptyPortfolioContentState = WalletTokensListState.ContentState.PortfolioContent(
items = persistentListOf(
TokensListItemUM.Portfolio(
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
isExpanded = false,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem
.copy(iconState = AccountItemPreviewData.accountLetterIcon),
),
TokensListItemUM.Portfolio(
tokens = persistentListOf(),
isExpanded = true,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem,
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
isEnabled = true,
onClick = {},
),
)
private val noteLockedCard by lazy {
WalletCardState.LockedContent(
id = UserWalletId(stringValue = "1"),
@ -208,4 +230,12 @@ internal object WalletScreenPreviewData {
multiWalletState.copy(tokensListState = portfolioContentState),
),
)
internal val accountScreenWithEmptyTokensState =
walletScreenState.copy(
wallets = persistentListOf(
singleWalletLockedState,
multiWalletState.copy(tokensListState = emptyPortfolioContentState),
),
)
}

View file

@ -100,6 +100,7 @@ internal class TokenListStateConverter(
appCurrency = appCurrency,
account = account,
onItemClick = onItemClick,
priceChangeLce = this.priceChangeLce,
)
val accountItem = converter.convert(tokenList.totalFiatBalance)
val tokenConverter = tokenStatusConverter(account.accountId)

View file

@ -69,6 +69,7 @@ import com.tangem.core.ui.utils.moveTo
import com.tangem.core.ui.utils.toPx
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenState
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenWithEmptyTokensState
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder
@ -753,6 +754,7 @@ private class WalletScreenPreviewProvider : PreviewParameterProvider<WalletScree
walletScreenState,
walletScreenState.copy(selectedWalletIndex = 1),
accountScreenState.copy(selectedWalletIndex = 1),
accountScreenWithEmptyTokensState.copy(selectedWalletIndex = 1),
)
}
// endregion

View file

@ -58,6 +58,25 @@ internal fun LazyListScope.portfolioTokensList(
selectedWalletChanged = selectedWalletChanged,
)
if (!isExpanded) return
if (tokens.isEmpty()) {
item(
key = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}",
contentType = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}",
) {
NonContentItemContent(
modifier = Modifier
.animateItem()
.roundedShapeItemDecoration(
radius = TangemTheme.dimens.radius14,
currentIndex = 1,
lastIndex = 1,
backgroundColor = TangemTheme.colors.background.primary,
)
.padding(vertical = TangemTheme.dimens.spacing28),
)
}
return
}
itemsIndexed(
items = tokens,
key = { _, item -> item.id },
@ -74,6 +93,7 @@ internal fun LazyListScope.portfolioTokensList(
.testModifier(indexWithHeader)
.animateItem()
.roundedShapeItemDecoration(
radius = TangemTheme.dimens.radius14,
currentIndex = indexWithHeader,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
@ -101,6 +121,12 @@ private fun LazyListScope.portfolioItem(
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}-isExpanded$isExpanded",
contentType = "account-isExpanded$isExpanded",
@ -110,27 +136,24 @@ private fun LazyListScope.portfolioItem(
.animateItem()
.roundedShapeItemDecoration(
currentIndex = 0,
lastIndex = if (isExpanded) tokens.lastIndex.inc() else 0,
radius = TangemTheme.dimens.radius14,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
)
val isPreview = LocalInspectionMode.current
val appear = remember {
MutableTransitionState(selectedWalletChanged || isPreview).apply { targetState = true }
MutableTransitionState(selectedWalletChanged || isPreview || tokens.isEmpty())
.apply { targetState = true }
}
if (isExpanded) {
SlideInItemVisibility(
modifier = anchorModifier,
visibleState = appear,
) {
val modifier = if (portfolio.tokens.isEmpty()) {
Modifier.padding(vertical = 8.dp)
} else {
Modifier.padding(top = 8.dp)
}
PortfolioListItem(
state = portfolio,
isBalanceHidden = isBalanceHidden,
modifier = modifier,
modifier = Modifier.padding(vertical = 8.dp),
)
}
} else {

View file

@ -8,6 +8,7 @@ import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
@ -25,7 +26,7 @@ import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import kotlinx.collections.immutable.ImmutableList
private const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST"
internal const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST"
/**
* LazyList extension for [WalletTokensListState]
@ -91,27 +92,34 @@ private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) {
key = NON_CONTENT_TOKENS_LIST_KEY,
contentType = NON_CONTENT_TOKENS_LIST_KEY,
) {
Column(
NonContentItemContent(
modifier = modifier
.animateItem(fadeInSpec = null, fadeOutSpec = null)
.padding(top = TangemTheme.dimens.spacing96),
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 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,
)
}
}