Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-23 15:02:05 +08:00
parent 731fd53e3d
commit 0af5b06cbb
20 changed files with 388 additions and 177 deletions

View file

@ -167,7 +167,7 @@ private fun LoadingContent() {
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size158,
height = TangemTheme.dimens.size20,
height = TangemTheme.dimens.size18,
),
)

View file

@ -4,6 +4,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@ -158,6 +159,18 @@ private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
is TransactionState.Loading -> {
CircleShimmer(modifier = modifier.size(TangemTheme.dimens.size40))
}
is TransactionState.Locked -> {
Box(modifier = modifier.size(TangemTheme.dimens.size40)) {
Box(
modifier = Modifier
.matchParentSize()
.background(
color = TangemTheme.colors.button.secondary,
shape = CircleShape,
),
)
}
}
}
}
@ -206,6 +219,11 @@ private fun Title(state: TransactionState, modifier: Modifier = Modifier) {
modifier = modifier.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12),
)
}
is TransactionState.Locked -> {
LockedContent(
modifier = modifier.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12),
)
}
}
}
@ -247,6 +265,11 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
modifier = modifier.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12),
)
}
is TransactionState.Locked -> {
LockedContent(
modifier = modifier.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12),
)
}
}
}
@ -267,6 +290,11 @@ private fun Amount(state: TransactionState, modifier: Modifier = Modifier) {
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
)
}
is TransactionState.Locked -> {
LockedContent(
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
)
}
}
}
@ -287,9 +315,24 @@ private fun Timestamp(state: TransactionState, modifier: Modifier = Modifier) {
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
)
}
is TransactionState.Locked -> {
LockedContent(
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
)
}
}
}
@Composable
private fun LockedContent(modifier: Modifier = Modifier) {
Box(
modifier = modifier.background(
color = TangemTheme.colors.field.primary,
shape = RoundedCornerShape(TangemTheme.dimens.radius6),
),
)
}
@Preview
@Composable
private fun Preview_TransactionItem_LightTheme(

View file

@ -200,4 +200,11 @@ sealed interface TransactionState {
* @property txHash transaction hash
*/
data class Loading(override val txHash: String) : TransactionState
/**
* Locked state
*
* @property txHash transaction hash
*/
data class Locked(override val txHash: String) : TransactionState
}

View file

@ -1,7 +0,0 @@
package com.tangem.core.ui.components.wallet
/**
* Wallet locked content state.
* It allows to divide the locked content of multi-currency and single-currency wallets.
*/
interface WalletLockedContentState

View file

@ -8,6 +8,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@ -60,6 +61,7 @@ internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
is TokenItemState.Loading -> LoadingTokenItem(modifier)
is TokenItemState.Draggable -> DraggableTokenItem(state, modifier, reorderableTokenListState = null)
is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier)
is TokenItemState.Locked -> LockedTokenItem(modifier)
}
}
@ -196,6 +198,77 @@ private fun LoadingTokenItem(modifier: Modifier = Modifier) {
}
}
@Composable
private fun LockedTokenItem(modifier: Modifier = Modifier) {
BaseSurface(modifier) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing4,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
) {
Box(modifier = Modifier.size(size = TangemTheme.dimens.size42)) {
Box(
modifier = Modifier
.matchParentSize()
.background(
color = TangemTheme.colors.button.secondary,
shape = CircleShape,
),
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
LockedContent(
modifier = Modifier.size(
width = TangemTheme.dimens.size72,
height = TangemTheme.dimens.size12,
),
)
LockedContent(
modifier = Modifier.size(
width = TangemTheme.dimens.size50,
height = TangemTheme.dimens.size12,
),
)
}
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
LockedContent(
modifier = Modifier.size(
width = TangemTheme.dimens.size40,
height = TangemTheme.dimens.size12,
),
)
LockedContent(
modifier = Modifier.size(
width = TangemTheme.dimens.size40,
height = TangemTheme.dimens.size12,
),
)
}
}
}
}
}
@Composable
private fun LockedContent(modifier: Modifier = Modifier) {
Box(
modifier = modifier.background(
color = TangemTheme.colors.field.primary,
shape = RoundedCornerShape(TangemTheme.dimens.radius6),
),
)
}
/**
* Block for end part of token item
* shows status is reachable, is drag, hidden or show balance

View file

@ -14,6 +14,8 @@ internal sealed interface TokenItemState {
/** Loading token state */
data class Loading(override val id: String) : TokenItemState
data class Locked(override val id: String) : TokenItemState
/**
* Content token state
*

View file

@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.plus
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.impl.R
import java.math.BigDecimal
@ -20,50 +21,47 @@ internal object WalletAdditionalInfoFactory {
/**
* Get additional info
*
* @param cardTypesResolver card type resolver
* @param isLocked check if wallet is locked
* @param cardTypesResolver card type resolver
* @param wallet current wallet
* @param currencyAmount amount of currency
*/
fun resolve(
cardTypesResolver: CardTypesResolver,
isLocked: Boolean,
wallet: UserWallet,
currencyAmount: BigDecimal? = null,
): TextReference {
return if (cardTypesResolver.isMultiwalletAllowed()) {
resolveMultiCurrencyInfo(cardTypesResolver, isLocked)
resolveMultiCurrencyInfo(cardTypesResolver, wallet)
} else {
resolveSingleCurrencyInfo(cardTypesResolver, isLocked, currencyAmount)
resolveSingleCurrencyInfo(cardTypesResolver, wallet, currencyAmount)
}
}
private fun resolveMultiCurrencyInfo(cardTypeResolver: CardTypesResolver, isLocked: Boolean): TextReference {
val backupCardsCount = cardTypeResolver.getBackupCardsCount()
private fun resolveMultiCurrencyInfo(cardTypeResolver: CardTypesResolver, wallet: UserWallet): TextReference {
val backupCardsCount = wallet.cardsInWallet.size + 1
val backupInfoRes = TextReference.PluralRes(
id = R.plurals.card_label_card_count,
count = backupCardsCount,
formatArgs = wrappedList(backupCardsCount),
)
return when {
cardTypeResolver.isWallet2() && !isLocked -> {
return if (wallet.isLocked) {
backupInfoRes + DIVIDER_RES + TextReference.Res(R.string.common_locked)
} else {
if (cardTypeResolver.isWallet2()) {
backupInfoRes + DIVIDER_RES + TextReference.Res(id = R.string.common_seed_phrase)
}
cardTypeResolver.isTangemWallet() && !isLocked -> {
} else {
backupInfoRes
}
isLocked -> {
backupInfoRes + TextReference.Res(R.string.common_locked)
}
else -> error("It isn't exist additional info for this case") // FIXME: Crashes on dev cards
}
}
private fun resolveSingleCurrencyInfo(
cardTypeResolver: CardTypesResolver,
isLocked: Boolean,
wallet: UserWallet,
currencyAmount: BigDecimal?,
): TextReference {
return if (isLocked) {
return if (wallet.isLocked) {
TextReference.Res(R.string.common_locked)
} else {
val blockchain = cardTypeResolver.getBlockchain()

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state
import androidx.compose.runtime.Immutable
import androidx.paging.PagingData
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.feature.wallet.presentation.wallet.state.components.*
import kotlinx.collections.immutable.ImmutableList
@ -63,7 +64,20 @@ internal sealed class WalletSingleCurrencyState : WalletState.ContentState() {
)
override val txHistoryState: TxHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(PagingData.empty()),
contentItems = MutableStateFlow(
value = PagingData.from(
data = listOf(
TxHistoryState.TxHistoryItemState.Title(onExploreClick = onExploreClick),
TxHistoryState.TxHistoryItemState.Transaction(
state = TransactionState.Locked(txHash = LOCKED_TX_HASH),
),
),
),
),
)
private companion object {
const val LOCKED_TX_HASH = "LOCKED_TX_HASH"
}
}
}

View file

@ -15,6 +15,9 @@ internal sealed interface WalletCardState {
/** Title */
val title: String
/** Additional text */
val additionalInfo: TextReference?
/** Wallet image resource id */
@get:DrawableRes
val imageResId: Int?
@ -22,81 +25,92 @@ internal sealed interface WalletCardState {
/** Lambda be invoked when card is clicked */
val onClick: (() -> Unit)?
/** Additional text availability */
sealed interface AdditionalTextAvailability {
/** Additional wallet information */
val additionalInfo: TextReference
}
/**
* Wallet card content state
*
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
* @property additionalInfo wallet additional info
* @property balance wallet balance
*/
data class Content(
override val id: UserWalletId,
override val title: String,
override val imageResId: Int?,
override val onClick: (() -> Unit)? = null,
override val additionalInfo: TextReference,
val balance: String,
) : WalletCardState, AdditionalTextAvailability
/**
* Wallet card loading state
*
* @property id wallet id
* @property title wallet name
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
*/
data class Loading(
override val id: UserWalletId,
override val title: String,
override val imageResId: Int?,
override val onClick: (() -> Unit)? = null,
val balance: String,
) : WalletCardState
/**
* Wallet card hidden content state
*
* @property id wallet id
* @property title wallet name
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
*/
data class HiddenContent(
override val id: UserWalletId,
override val title: String,
override val additionalInfo: TextReference = HIDDEN_BALANCE_TEXT,
override val imageResId: Int?,
override val onClick: (() -> Unit)?,
) : WalletCardState, AdditionalTextAvailability {
) : WalletCardState
override val additionalInfo: TextReference = HIDDEN_BALANCE_TEXT
}
/**
* Wallet card locked state
*
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
*/
data class LockedContent(
override val id: UserWalletId,
override val title: String,
override val additionalInfo: TextReference? = null,
override val imageResId: Int?,
override val onClick: (() -> Unit)?,
) : WalletCardState
/**
* Wallet card error state
*
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
* @property additionalInfo wallet additional info
*/
data class Error(
override val id: UserWalletId,
override val title: String,
override val additionalInfo: TextReference = EMPTY_BALANCE_TEXT,
override val imageResId: Int?,
override val onClick: (() -> Unit)?,
override val additionalInfo: TextReference = EMPTY_BALANCE_TEXT,
) : WalletCardState, AdditionalTextAvailability
) : WalletCardState
/**
* Wallet card loading state
*
* @property id wallet id
* @property title wallet name
* @property additionalInfo wallet additional info
* @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked
*/
data class Loading(
override val id: UserWalletId,
override val title: String,
override val additionalInfo: TextReference? = null,
override val imageResId: Int?,
override val onClick: (() -> Unit)? = null,
) : WalletCardState
companion object {
val HIDDEN_BALANCE_TEXT by lazy { TextReference.Str(value = "•••") }

View file

@ -1,6 +1,5 @@
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.feature.wallet.presentation.common.state.TokenItemState
@ -52,15 +51,13 @@ internal sealed class WalletTokensListState {
) : ContentState(items, onOrganizeTokensClick)
/** Locked content state */
object Locked :
ContentState(
items = persistentListOf(
TokensListItemState.NetworkGroupTitle(value = TextReference.Res(id = R.string.main_tokens)),
TokensListItemState.Token(state = TokenItemState.Loading(id = LOCKED_TOKEN_ID)),
),
onOrganizeTokensClick = null,
object Locked : ContentState(
items = persistentListOf(
TokensListItemState.NetworkGroupTitle(value = TextReference.Res(id = R.string.main_tokens)),
TokensListItemState.Token(state = TokenItemState.Locked(id = LOCKED_TOKEN_ID)),
),
WalletLockedContentState
onOrganizeTokensClick = null,
)
/** Tokens list item state */
sealed interface TokensListItemState {

View file

@ -6,6 +6,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletLoadedTokensListConverter.LoadedTokensListModel
@ -19,7 +20,7 @@ import com.tangem.utils.converter.Converter
*
* @property currentStateProvider current ui state provider
* @param cardTypeResolverProvider card type resolver
* @param isLockedWalletProvider current wallet is locked or not provider
* @param currentWalletProvider current wallet provider
* @param clickIntents screen click intents
*
[REDACTED_AUTHOR]
@ -28,14 +29,14 @@ internal class WalletLoadedTokensListConverter(
private val currentStateProvider: Provider<WalletState>,
appCurrencyProvider: Provider<AppCurrency>,
cardTypeResolverProvider: Provider<CardTypesResolver>,
isLockedWalletProvider: Provider<Boolean>,
currentWalletProvider: Provider<UserWallet>,
clickIntents: WalletClickIntents,
) : Converter<LoadedTokensListModel, WalletMultiCurrencyState.Content> {
private val tokenListStateConverter = TokenListToWalletStateConverter(
currentStateProvider = currentStateProvider,
cardTypeResolverProvider = cardTypeResolverProvider,
isLockedWalletProvider = isLockedWalletProvider,
currentWalletProvider = currentWalletProvider,
appCurrencyProvider = appCurrencyProvider,
isWalletContentHidden = false, // TODO: [REDACTED_JIRA]
clickIntents = clickIntents,

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.factory
import com.tangem.common.Provider
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.common.CardTypesResolver
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
@ -14,12 +15,14 @@ import com.tangem.feature.wallet.presentation.wallet.state.components.WalletToke
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.update
internal class WalletRefreshStateConverter(
private val currentStateProvider: Provider<WalletState>,
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
private val clickIntents: WalletClickIntents,
) : Converter<Unit, WalletState> {
@ -41,7 +44,6 @@ internal class WalletRefreshStateConverter(
private fun WalletSingleCurrencyState.Content.getRefreshState(): WalletSingleCurrencyState.Content {
return copy(
// TODO: [REDACTED_JIRA]
walletsListConfig = getWalletsListConfig(),
pullToRefreshConfig = getPullToRefreshConfig(),
txHistoryState = getTxHistoryState(),
@ -51,18 +53,22 @@ internal class WalletRefreshStateConverter(
private fun WalletState.ContentState.getWalletsListConfig(): WalletsListConfig {
val selectedWallet = walletsListConfig.wallets[walletsListConfig.selectedWalletIndex]
val additionalInfo = if (currentCardTypeResolverProvider().isMultiwalletAllowed()) {
selectedWallet.additionalInfo
} else {
null
}
return walletsListConfig.copy(
wallets = walletsListConfig.wallets
.toPersistentList()
.set(
index = walletsListConfig.selectedWalletIndex,
element = WalletCardState.Loading(
id = selectedWallet.id,
title = selectedWallet.title,
imageResId = selectedWallet.imageResId,
),
wallets = walletsListConfig.wallets.toPersistentList().set(
index = walletsListConfig.selectedWalletIndex,
element = WalletCardState.Loading(
id = selectedWallet.id,
title = selectedWallet.title,
additionalInfo = additionalInfo,
imageResId = selectedWallet.imageResId,
),
),
)
}
@ -76,10 +82,7 @@ internal class WalletRefreshStateConverter(
WalletTokensListState.Loading(
items = tokensListState.items
.filterIsInstance<TokensListItemState.Token>()
.map {
TokensListItemState.Token(state = TokenItemState.Loading(id = it.state.id))
}
.toImmutableList(),
.mapToLoadingTokenState(),
)
}
is WalletTokensListState.Empty -> WalletTokensListState.Loading()
@ -89,18 +92,19 @@ internal class WalletRefreshStateConverter(
}
}
private fun List<TokensListItemState.Token>.mapToLoadingTokenState(): ImmutableList<TokensListItemState.Token> {
return this
.map { TokensListItemState.Token(state = TokenItemState.Loading(id = it.state.id)) }
.toImmutableList()
}
private fun WalletSingleCurrencyState.Content.getTxHistoryState(): TxHistoryState {
return when (txHistoryState) {
is TxHistoryState.Content -> {
txHistoryState.contentItems.update {
TxHistoryState.getDefaultLoadingTransactions(onExploreClick = clickIntents::onExploreClick)
}
txHistoryState
if (txHistoryState is TxHistoryState.Content) {
txHistoryState.contentItems.update {
TxHistoryState.getDefaultLoadingTransactions(onExploreClick = clickIntents::onExploreClick)
}
is TxHistoryState.Empty,
is TxHistoryState.Error,
is TxHistoryState.NotSupported,
-> txHistoryState
}
return txHistoryState
}
}

View file

@ -9,6 +9,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.error.CurrencyError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
@ -23,6 +24,7 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
private val currentStateProvider: Provider<WalletState>,
private val cardTypeResolverProvider: Provider<CardTypesResolver>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val currentWalletProvider: Provider<UserWallet>,
) : Converter<SingleCurrencyLoadedBalanceModel, WalletSingleCurrencyState.Content> {
override fun convert(value: SingleCurrencyLoadedBalanceModel): WalletSingleCurrencyState.Content {
@ -89,7 +91,7 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
title = selectedWallet.title,
additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolverProvider(),
isLocked = false,
wallet = currentWalletProvider(),
currencyAmount = status.amount,
),
imageResId = selectedWallet.imageResId,

View file

@ -5,6 +5,7 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
@ -110,6 +111,11 @@ internal class WalletSkeletonStateConverter(
return WalletCardState.Loading(
id = wallet.walletId,
title = wallet.name,
additionalInfo = if (cardTypeResolver.isMultiwalletAllowed()) {
WalletAdditionalInfoFactory.resolve(cardTypesResolver = cardTypeResolver, wallet = wallet)
} else {
null
},
imageResId = WalletImageResolver.resolve(cardTypesResolver = cardTypeResolver),
)
}

View file

@ -13,10 +13,12 @@ import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadedTxHistoryConverter
@ -24,6 +26,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.Wal
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
/**
@ -31,13 +34,13 @@ import kotlinx.coroutines.flow.Flow
*
* @property currentStateProvider current ui state provider
* @property currentCardTypeResolverProvider current card type resolver
* @property isLockedWalletProvider current wallet is locked or not
* @property currentWalletProvider current wallet
* @property clickIntents screen click intents
*/
internal class WalletStateFactory(
private val currentStateProvider: Provider<WalletState>,
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
private val isLockedWalletProvider: Provider<Boolean>,
private val currentWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val clickIntents: WalletClickIntents,
) {
@ -48,7 +51,7 @@ internal class WalletStateFactory(
WalletLoadedTokensListConverter(
currentStateProvider = currentStateProvider,
cardTypeResolverProvider = currentCardTypeResolverProvider,
isLockedWalletProvider = isLockedWalletProvider,
currentWalletProvider = currentWalletProvider,
appCurrencyProvider = appCurrencyProvider,
clickIntents = clickIntents,
)
@ -74,11 +77,16 @@ internal class WalletStateFactory(
currentStateProvider = currentStateProvider,
cardTypeResolverProvider = currentCardTypeResolverProvider,
appCurrencyProvider = appCurrencyProvider,
currentWalletProvider = currentWalletProvider,
)
}
private val refreshStateConverter by lazy {
WalletRefreshStateConverter(currentStateProvider = currentStateProvider, clickIntents = clickIntents)
WalletRefreshStateConverter(
currentStateProvider = currentStateProvider,
currentCardTypeResolverProvider = currentCardTypeResolverProvider,
clickIntents = clickIntents,
)
}
fun getInitialState(): WalletState = WalletState.Initial(onBackClick = clickIntents::onBackClick)
@ -170,7 +178,22 @@ internal class WalletStateFactory(
topBarConfig = state.topBarConfig.copy(
onMoreClick = clickIntents::onUnlockWalletNotificationClick,
),
walletsListConfig = state.walletsListConfig,
walletsListConfig = state.walletsListConfig.copy(
wallets = state.walletsListConfig.wallets
.map { walletCardState ->
WalletCardState.LockedContent(
id = walletCardState.id,
title = walletCardState.title,
imageResId = walletCardState.imageResId,
onClick = walletCardState.onClick,
additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolver,
wallet = currentWalletProvider(),
),
)
}
.toImmutableList(),
),
pullToRefreshConfig = state.pullToRefreshConfig,
onUnlockWalletsNotificationClick = clickIntents::onUnlockWalletNotificationClick,
onUnlockClick = clickIntents::onUnlockWalletClick,
@ -182,7 +205,18 @@ internal class WalletStateFactory(
topBarConfig = state.topBarConfig.copy(
onMoreClick = clickIntents::onUnlockWalletNotificationClick,
),
walletsListConfig = state.walletsListConfig,
walletsListConfig = state.walletsListConfig.copy(
wallets = state.walletsListConfig.wallets
.map { walletCardState ->
WalletCardState.LockedContent(
id = walletCardState.id,
title = walletCardState.title,
imageResId = walletCardState.imageResId,
onClick = walletCardState.onClick,
)
}
.toImmutableList(),
),
pullToRefreshConfig = state.pullToRefreshConfig,
buttons = getButtons(),
onUnlockWalletsNotificationClick = clickIntents::onUnlockWalletNotificationClick,

View file

@ -23,26 +23,32 @@ import kotlinx.coroutines.flow.update
internal class WalletLoadingTxHistoryConverter(
private val currentStateProvider: Provider<WalletState>,
private val clickIntents: WalletClickIntents,
) : Converter<Either<TxHistoryStateError, Int>, WalletSingleCurrencyState.Content> {
) : Converter<Either<TxHistoryStateError, Int>, WalletState> {
override fun convert(value: Either<TxHistoryStateError, Int>): WalletSingleCurrencyState.Content {
override fun convert(value: Either<TxHistoryStateError, Int>): WalletState {
return value.fold(ifLeft = ::convertError, ifRight = ::convert)
}
private fun convertError(error: TxHistoryStateError): WalletSingleCurrencyState.Content {
return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content).copy(
txHistoryState = when (error) {
is TxHistoryStateError.EmptyTxHistories -> {
Empty(onBuyClick = clickIntents::onBuyClick)
}
is TxHistoryStateError.DataError -> {
Error(onReloadClick = clickIntents::onReloadClick)
}
is TxHistoryStateError.TxHistoryNotImplemented -> {
NotSupported(onExploreClick = clickIntents::onExploreClick)
}
},
)
private fun convertError(error: TxHistoryStateError): WalletState {
val state = currentStateProvider()
return if (state is WalletSingleCurrencyState.Content) {
state.copy(
txHistoryState = when (error) {
is TxHistoryStateError.EmptyTxHistories -> {
Empty(onBuyClick = clickIntents::onBuyClick)
}
is TxHistoryStateError.DataError -> {
Error(onReloadClick = clickIntents::onReloadClick)
}
is TxHistoryStateError.TxHistoryNotImplemented -> {
NotSupported(onExploreClick = clickIntents::onExploreClick)
}
},
)
} else {
state
}
}
private fun convert(value: Int): WalletSingleCurrencyState.Content {

View file

@ -5,7 +5,9 @@ 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.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@ -23,7 +25,9 @@ import androidx.constraintlayout.compose.Dimension
import com.tangem.core.ui.components.FontSizeRange
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemDimens
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
@ -118,20 +122,12 @@ private fun Title(state: WalletCardState, modifier: Modifier = Modifier) {
TitleText(title = state.title)
AnimatedVisibility(visible = state is WalletCardState.HiddenContent, label = "Update the hidden icon") {
when (state) {
is WalletCardState.HiddenContent -> {
Icon(
modifier = Modifier.size(size = TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_eye_off_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
is WalletCardState.Content,
is WalletCardState.Error,
is WalletCardState.Loading,
-> Unit
}
Icon(
modifier = Modifier.size(size = TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_eye_off_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}
@ -164,60 +160,78 @@ private fun Balance(state: WalletCardState, modifier: Modifier = Modifier) {
style = TangemTheme.typography.h2,
)
}
is WalletCardState.HiddenContent -> NonContentBalanceText(text = WalletCardState.HIDDEN_BALANCE_TEXT)
is WalletCardState.Error -> NonContentBalanceText(text = WalletCardState.EMPTY_BALANCE_TEXT)
is WalletCardState.Loading -> {
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size32,
),
)
RectangleShimmer(modifier = Modifier.nonContentBalanceSize(TangemTheme.dimens))
}
is WalletCardState.HiddenContent -> {
Text(
text = WalletCardState.HIDDEN_BALANCE_TEXT.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
is WalletCardState.Error -> {
Text(
text = WalletCardState.EMPTY_BALANCE_TEXT.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
is WalletCardState.LockedContent -> {
LockedContent(modifier = Modifier.nonContentBalanceSize(TangemTheme.dimens))
}
}
}
}
@Composable
private fun NonContentBalanceText(text: TextReference) {
Text(
text = text.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
private fun Modifier.nonContentBalanceSize(dimens: TangemDimens): Modifier {
return size(width = dimens.size102, height = dimens.size32)
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun AdditionalInfo(state: WalletCardState, modifier: Modifier = Modifier) {
AnimatedContent(
targetState = state,
targetState = state.additionalInfo,
label = "Update the additional text",
modifier = modifier,
) { walletCardState ->
when (walletCardState) {
is WalletCardState.AdditionalTextAvailability -> {
Text(
text = walletCardState.additionalInfo.resolveReference(),
color = TangemTheme.colors.text.disabled,
style = TangemTheme.typography.caption,
)
}
is WalletCardState.Loading -> {
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size84,
height = TangemTheme.dimens.size16,
),
)
) { additionalInfo ->
if (additionalInfo != null) {
AdditionalInfoText(text = additionalInfo)
} else {
when (state) {
is WalletCardState.Loading -> {
RectangleShimmer(modifier = Modifier.nonContentAdditionalInfoSize(TangemTheme.dimens))
}
is WalletCardState.LockedContent -> {
LockedContent(modifier = Modifier.nonContentAdditionalInfoSize(TangemTheme.dimens))
}
else -> Unit
}
}
}
}
@Composable
private fun AdditionalInfoText(text: TextReference) {
Text(
text = text.resolveReference(),
color = TangemTheme.colors.text.disabled,
style = TangemTheme.typography.caption,
)
}
private fun Modifier.nonContentAdditionalInfoSize(dimens: TangemDimens): Modifier {
return size(width = dimens.size84, height = dimens.size16)
}
@Composable
private fun LockedContent(modifier: Modifier = Modifier) {
Box(
modifier = modifier.background(
color = TangemTheme.colors.field.primary,
shape = RoundedCornerShape(TangemTheme.dimens.radius6),
),
)
}
@Composable
private fun Image(@DrawableRes id: Int?, modifier: Modifier = Modifier) {
AnimatedVisibility(visible = id != null, modifier = modifier) {

View file

@ -5,6 +5,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.model.TokenList.FiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.utils.converter.Converter
@ -13,7 +14,7 @@ internal class FiatBalanceToWalletCardConverter(
private val currentState: WalletCardState,
private val cardTypeResolverProvider: Provider<CardTypesResolver>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val isLockedState: Boolean,
private val currentWalletProvider: Provider<UserWallet>,
private val isWalletContentHidden: Boolean,
) : Converter<FiatBalance, WalletCardState> {
@ -26,7 +27,7 @@ internal class FiatBalanceToWalletCardConverter(
}
private fun WalletCardState.toLoadingWalletCardState(): WalletCardState {
return WalletCardState.Loading(id, title, imageResId, onClick)
return WalletCardState.Loading(id, title, additionalInfo, imageResId, onClick)
}
private fun WalletCardState.toErrorWalletCardState(): WalletCardState {
@ -37,7 +38,7 @@ internal class FiatBalanceToWalletCardConverter(
onClick = onClick,
additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolverProvider(),
isLocked = isLockedState,
wallet = currentWalletProvider(),
),
)
}
@ -47,6 +48,7 @@ internal class FiatBalanceToWalletCardConverter(
WalletCardState.HiddenContent(
id = currentState.id,
title = currentState.title,
additionalInfo = currentState.additionalInfo ?: WalletCardState.HIDDEN_BALANCE_TEXT,
imageResId = currentState.imageResId,
onClick = currentState.onClick,
)
@ -58,7 +60,7 @@ internal class FiatBalanceToWalletCardConverter(
title = currentState.title,
additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolverProvider(),
isLocked = isLockedState,
wallet = currentWalletProvider(),
),
imageResId = currentState.imageResId,
onClick = currentState.onClick,

View file

@ -4,6 +4,7 @@ import com.tangem.common.Provider
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig
@ -16,7 +17,7 @@ import kotlinx.collections.immutable.toPersistentList
internal class TokenListToWalletStateConverter(
private val currentStateProvider: Provider<WalletState>,
private val cardTypeResolverProvider: Provider<CardTypesResolver>,
private val isLockedWalletProvider: Provider<Boolean>,
private val currentWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val isWalletContentHidden: Boolean,
clickIntents: WalletClickIntents,
@ -46,7 +47,7 @@ internal class TokenListToWalletStateConverter(
val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex]
val converter = FiatBalanceToWalletCardConverter(
currentState = selectedWalletCard,
isLockedState = isLockedWalletProvider(),
currentWalletProvider = currentWalletProvider,
cardTypeResolverProvider = cardTypeResolverProvider,
appCurrencyProvider = appCurrencyProvider,
isWalletContentHidden = isWalletContentHidden,

View file

@ -96,8 +96,8 @@ internal class WalletViewModel @Inject constructor(
index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex,
)
},
isLockedWalletProvider = Provider {
wallets[requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex].isLocked
currentWalletProvider = Provider {
wallets[requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex]
},
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
clickIntents = this,