Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-20 18:48:25 +03:00
parent 3c59214c76
commit 4c903fc654
73 changed files with 886 additions and 190 deletions

View file

@ -63,6 +63,8 @@ internal object WalletPreviewData {
imageResId = R.drawable.ill_businessman_3d,
onRenameClick = { _, _ -> },
onDeleteClick = {},
balance = "8923,05 $",
additionalInfo = TextReference.Str("3 cards • Seed phrase"),
)
}
@ -125,12 +127,13 @@ internal object WalletPreviewData {
name = "Polygon",
amount = "5,412 MATIC",
hasPending = true,
tokenOptions = TokenOptionsState.Visible(
tokenOptions = TokenOptionsState(
fiatAmount = "321 $",
config = PriceChangeConfig(
valueInPercent = "2%",
type = PriceChangeConfig.Type.UP,
),
isBalanceHidden = false,
),
onItemClick = {},
onItemLongClick = {},
@ -151,11 +154,14 @@ internal object WalletPreviewData {
name = "Polygon",
amount = "5,412 MATIC",
hasPending = true,
tokenOptions = TokenOptionsState.Hidden(
tokenOptions = TokenOptionsState(
config = PriceChangeConfig(
valueInPercent = "2%",
type = PriceChangeConfig.Type.UP,
),
fiatAmount = "321 $",
isBalanceHidden = false,
),
onItemClick = {},
onItemLongClick = {},

View file

@ -9,14 +9,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.painterResource
import com.tangem.common.Strings.STARS
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemTypography
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.Companion.DOTS
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
@Composable
internal fun TokenCryptoInfoBlock(state: TokenItemState, modifier: Modifier = Modifier) {
@ -40,7 +39,7 @@ private fun ContentBlock(state: TokenItemState.ContentState, modifier: Modifier
AmountText(
amount = when (state) {
is TokenItemState.Content -> if (state.tokenOptions is TokenOptionsState.Hidden) DOTS else state.amount
is TokenItemState.Content -> if (state.tokenOptions.isBalanceHidden) STARS else state.amount
is TokenItemState.Draggable -> state.info.resolveReference()
is TokenItemState.Unreachable -> null
},

View file

@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.tangem.common.Strings.STARS
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW4
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
@ -18,7 +19,6 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemTypography
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.Companion.DOTS
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.detectReorder
@ -56,9 +56,10 @@ private fun ContentBlock(state: TokenOptionsState, modifier: Modifier = Modifier
modifier = Modifier.align(Alignment.End),
) {
Text(
text = when (it) {
is TokenOptionsState.Visible -> it.fiatAmount
is TokenOptionsState.Hidden -> DOTS
text = if (it.isBalanceHidden) {
STARS
} else {
it.fiatAmount
},
style = TangemTypography.body2,
color = TangemTheme.colors.text.primary1,

View file

@ -146,30 +146,9 @@ internal sealed interface TokenItemState {
/** Token options state */
@Immutable
sealed interface TokenOptionsState {
val config: PriceChangeConfig
/**
* Visible token options state
*
* @property fiatAmount fiat amount of token
* @property config value of price changing
*/
data class Visible(
override val config: PriceChangeConfig,
val fiatAmount: String,
) : TokenOptionsState
/**
* Hidden token options state
*
* @property config value of price changing
*/
data class Hidden(override val config: PriceChangeConfig) : TokenOptionsState
}
companion object {
const val DOTS = "•••"
}
data class TokenOptionsState(
val config: PriceChangeConfig,
val fiatAmount: String,
val isBalanceHidden: Boolean,
)
}

View file

@ -0,0 +1,21 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
internal class TokenItemHiddenStateConverter {
fun updateHiddenState(
optionsState: TokenItemState.TokenOptionsState,
isBalanceHidden: Boolean,
): TokenItemState.TokenOptionsState {
return when {
!optionsState.isBalanceHidden && isBalanceHidden -> {
optionsState.copy(isBalanceHidden = true)
}
optionsState.isBalanceHidden && !isBalanceHidden -> {
optionsState.copy(isBalanceHidden = false)
}
else -> optionsState
}
}
}

View file

@ -49,11 +49,13 @@ internal sealed interface WalletCardState {
/**
* Wallet card hidden content state
*
* @property id wallet id
* @property title wallet name
* @property imageResId wallet image resource id
* @property onRenameClick lambda be invoked when Rename button is clicked
* @property onDeleteClick lambda be invoked when Delete button is clicked
* @property id wallet id
* @property title wallet name
* @property imageResId wallet image resource id
* @property onRenameClick lambda be invoked when Rename button is clicked
* @property onDeleteClick lambda be invoked when Delete button is clicked
* @property additionalInfo wallet additional info
* @property balance wallet balance
*/
data class HiddenContent(
override val id: UserWalletId,
@ -61,6 +63,8 @@ internal sealed interface WalletCardState {
override val imageResId: Int?,
override val onRenameClick: (UserWalletId, String) -> Unit,
override val onDeleteClick: (UserWalletId) -> Unit,
val additionalInfo: TextReference,
val balance: String,
) : WalletCardState
/**

View file

@ -24,11 +24,13 @@ import com.tangem.utils.converter.Converter
*
[REDACTED_AUTHOR]
*/
@Suppress("LongParameterList")
internal class WalletLoadedTokensListConverter(
private val currentStateProvider: Provider<WalletState>,
private val tokenListErrorConverter: TokenListErrorConverter,
appCurrencyProvider: Provider<AppCurrency>,
currentWalletProvider: Provider<UserWallet>,
isBalanceHiddenProvider: Provider<Boolean>,
clickIntents: WalletClickIntents,
) : Converter<Either<TokenListError, TokenList>, WalletState> {
@ -36,7 +38,7 @@ internal class WalletLoadedTokensListConverter(
currentStateProvider = currentStateProvider,
currentWalletProvider = currentWalletProvider,
appCurrencyProvider = appCurrencyProvider,
isWalletContentHidden = false, // TODO: [REDACTED_JIRA]
isBalanceHiddenProvider = isBalanceHiddenProvider,
clickIntents = clickIntents,
)

View file

@ -18,11 +18,16 @@ 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.state.ActionsBottomSheetConfig
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.*
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadedTxHistoryConverter
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadingTxHistoryConverter
import com.tangem.feature.wallet.presentation.wallet.utils.CurrencyStatusErrorConverter
import com.tangem.feature.wallet.presentation.wallet.utils.HiddenStateConverter
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorConverter
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletsUpdateActionResolver
@ -44,6 +49,7 @@ internal class WalletStateFactory(
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
private val currentWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val isBalanceHiddenProvider: Provider<Boolean>,
private val clickIntents: WalletClickIntents,
) {
@ -57,6 +63,8 @@ internal class WalletStateFactory(
private val walletDeleteStateConverter by lazy { WalletDeleteStateConverter(currentStateProvider) }
private val hiddenStateConverter by lazy { HiddenStateConverter(currentStateProvider) }
private val tokenListErrorConverter by lazy {
TokenListErrorConverter(currentStateProvider)
}
@ -69,6 +77,7 @@ internal class WalletStateFactory(
tokenListErrorConverter = tokenListErrorConverter,
appCurrencyProvider = appCurrencyProvider,
currentWalletProvider = currentWalletProvider,
isBalanceHiddenProvider = isBalanceHiddenProvider,
clickIntents = clickIntents,
)
}
@ -242,6 +251,10 @@ internal class WalletStateFactory(
return currencyStatusErrorConverter.convert(error)
}
fun getHiddenBalanceState(isBalanceHidden: Boolean): WalletState {
return hiddenStateConverter.convert(isBalanceHidden)
}
fun getStateAndTriggerEvent(
state: WalletState,
event: WalletEvent,

View file

@ -241,15 +241,6 @@ private fun Title(state: WalletCardState, modifier: Modifier = Modifier) {
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
) {
TitleText(title = state.title)
AnimatedVisibility(visible = state is WalletCardState.HiddenContent, label = "Update the hidden icon") {
Icon(
modifier = Modifier.size(size = TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_eye_off_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}

View file

@ -13,7 +13,7 @@ import java.math.BigDecimal
internal class CryptoCurrencyStatusToTokenItemConverter(
private val appCurrencyProvider: Provider<AppCurrency>,
private val isWalletContentHidden: Boolean,
private val isBalanceHiddenProvider: Provider<Boolean>,
private val clickIntents: WalletClickIntents,
) : Converter<CryptoCurrencyStatus, TokenItemState> {
@ -42,14 +42,11 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
icon = iconStateConverter.convert(value = this),
amount = getFormattedAmount(),
hasPending = value.hasCurrentNetworkTransactions,
tokenOptions = if (isWalletContentHidden) {
TokenItemState.TokenOptionsState.Hidden(getPriceChangeConfig())
} else {
TokenItemState.TokenOptionsState.Visible(
fiatAmount = getFormattedFiatAmount(),
config = getPriceChangeConfig(),
)
},
tokenOptions = TokenItemState.TokenOptionsState(
fiatAmount = getFormattedFiatAmount(),
config = getPriceChangeConfig(),
isBalanceHidden = isBalanceHiddenProvider(),
),
onItemClick = { clickIntents.onTokenItemClick(currency) },
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
)

View file

@ -13,7 +13,7 @@ internal class FiatBalanceToWalletCardConverter(
private val currentState: WalletCardState,
private val appCurrencyProvider: Provider<AppCurrency>,
private val currentWalletProvider: Provider<UserWallet>,
private val isWalletContentHidden: Boolean,
private val isBalanceHiddenProvider: Provider<Boolean>,
) : Converter<FiatBalance, WalletCardState> {
override fun convert(value: FiatBalance): WalletCardState {
@ -39,17 +39,23 @@ internal class FiatBalanceToWalletCardConverter(
}
private fun FiatBalance.Loaded.convertToWalletCardState(): WalletCardState {
return if (isWalletContentHidden) {
val appCurrency = appCurrencyProvider()
return if (isBalanceHiddenProvider()) {
WalletCardState.HiddenContent(
id = currentState.id,
title = currentState.title,
imageResId = currentState.imageResId,
onRenameClick = currentState.onRenameClick,
onDeleteClick = currentState.onDeleteClick,
balance = formatFiatAmount(
fiatAmount = this.amount,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
),
additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = currentWalletProvider()),
)
} else {
val appCurrency = appCurrencyProvider()
WalletCardState.Content(
id = currentState.id,
title = currentState.title,

View file

@ -0,0 +1,70 @@
package com.tangem.feature.wallet.presentation.wallet.utils
import com.tangem.common.Converter
import com.tangem.common.Provider
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.TokenItemHiddenStateConverter
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.WalletTokensListState
import kotlinx.collections.immutable.toImmutableList
internal class HiddenStateConverter(
private val currentStateProvider: Provider<WalletState>,
) : Converter<Boolean, WalletState> {
private val walletHiddenBalanceStateConverter by lazy { WalletHiddenBalanceStateConverter() }
private val tokenItemHiddenStateConverter by lazy { TokenItemHiddenStateConverter() }
override fun convert(value: Boolean): WalletState {
return when (val state = currentStateProvider() as? WalletState.ContentState) {
is WalletMultiCurrencyState.Content -> {
val updatedTokensList = (state.tokensListState as? WalletTokensListState.Content)?.let { content ->
content.copy(
items = content.items.map { tokenListItemState ->
if (tokenListItemState is WalletTokensListState.TokensListItemState.Token) {
if (tokenListItemState.state is TokenItemState.Content) {
tokenListItemState.copy(
state = tokenListItemState.state.copy(
tokenOptions = tokenItemHiddenStateConverter.updateHiddenState(
optionsState = tokenListItemState.state.tokenOptions,
isBalanceHidden = value,
),
),
)
} else {
tokenListItemState
}
} else {
tokenListItemState
}
}.toImmutableList(),
)
} ?: state.tokensListState
state.copy(
walletsListConfig = state.walletsListConfig.copy(
wallets = state.walletsListConfig.wallets.map {
walletHiddenBalanceStateConverter.updateHiddenState(it, value)
}.toImmutableList(),
),
tokensListState = updatedTokensList,
)
}
is WalletSingleCurrencyState.Content -> {
state.copy(
walletsListConfig = state.walletsListConfig.copy(
wallets = state.walletsListConfig.wallets.map {
walletHiddenBalanceStateConverter.updateHiddenState(it, value)
}.toImmutableList(),
),
)
}
else -> currentStateProvider()
}
}
}

View file

@ -17,12 +17,12 @@ import kotlinx.collections.immutable.persistentListOf
internal class TokenListToContentItemsConverter(
appCurrencyProvider: Provider<AppCurrency>,
isWalletContentHidden: Boolean,
isBalanceHiddenProvider: Provider<Boolean>,
private val clickIntents: WalletClickIntents,
) : Converter<TokenList, WalletTokensListState> {
private val tokenStatusConverter = CryptoCurrencyStatusToTokenItemConverter(
isWalletContentHidden = isWalletContentHidden,
isBalanceHiddenProvider = isBalanceHiddenProvider,
appCurrencyProvider = appCurrencyProvider,
clickIntents = clickIntents,
)

View file

@ -17,12 +17,12 @@ internal class TokenListToWalletStateConverter(
private val currentStateProvider: Provider<WalletState>,
private val currentWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val isWalletContentHidden: Boolean,
private val isBalanceHiddenProvider: Provider<Boolean>,
clickIntents: WalletClickIntents,
) : Converter<TokenList, WalletState> {
private val tokenListToContentConverter = TokenListToContentItemsConverter(
isWalletContentHidden = isWalletContentHidden,
isBalanceHiddenProvider = isBalanceHiddenProvider,
appCurrencyProvider = appCurrencyProvider,
clickIntents = clickIntents,
)
@ -48,9 +48,9 @@ internal class TokenListToWalletStateConverter(
val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex]
val converter = FiatBalanceToWalletCardConverter(
currentState = selectedWalletCard,
appCurrencyProvider = appCurrencyProvider,
currentWalletProvider = currentWalletProvider,
isWalletContentHidden = isWalletContentHidden,
appCurrencyProvider = appCurrencyProvider,
isBalanceHiddenProvider = isBalanceHiddenProvider,
)
return walletsListConfig.copy(

View file

@ -0,0 +1,42 @@
package com.tangem.feature.wallet.presentation.wallet.utils
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
internal class WalletHiddenBalanceStateConverter {
fun updateHiddenState(walletCardState: WalletCardState, hiddenBalance: Boolean): WalletCardState {
return when {
walletCardState is WalletCardState.Content && hiddenBalance -> {
contentToHidden(walletCardState)
}
walletCardState is WalletCardState.HiddenContent && !hiddenBalance -> {
hiddenToContent(walletCardState)
}
else -> walletCardState
}
}
private fun contentToHidden(content: WalletCardState.Content): WalletCardState.HiddenContent {
return WalletCardState.HiddenContent(
id = content.id,
title = content.title,
additionalInfo = content.additionalInfo,
imageResId = content.imageResId,
onRenameClick = content.onRenameClick,
onDeleteClick = content.onDeleteClick,
balance = content.balance,
)
}
private fun hiddenToContent(hiddenContent: WalletCardState.HiddenContent): WalletCardState.Content {
return WalletCardState.Content(
id = hiddenContent.id,
title = hiddenContent.title,
additionalInfo = hiddenContent.additionalInfo,
imageResId = hiddenContent.imageResId,
onRenameClick = hiddenContent.onRenameClick,
onDeleteClick = hiddenContent.onDeleteClick,
balance = hiddenContent.balance,
)
}
}

View file

@ -14,14 +14,14 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.card.*
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.CanUseBiometryUseCase
import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase
import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -93,6 +93,8 @@ internal class WalletViewModel @Inject constructor(
private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase,
private val canUseBiometryUseCase: CanUseBiometryUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase,
private val listenToFlipsUseCase: ListenToFlipsUseCase,
private val walletManagersFacade: WalletManagersFacade,
private val reduxStateHolder: ReduxStateHolder,
private val dispatchers: CoroutineDispatcherProvider,
@ -104,6 +106,7 @@ internal class WalletViewModel @Inject constructor(
var router: InnerWalletRouter by Delegates.notNull()
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private var isBalanceHidden = true
private val notificationsListFactory = WalletNotificationsListFactory(
wasCardScannedCallback = getCardWasScannedUseCase::invoke,
@ -123,6 +126,7 @@ internal class WalletViewModel @Inject constructor(
wallets[requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex]
},
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
isBalanceHiddenProvider = Provider { isBalanceHidden },
clickIntents = this,
)
@ -161,6 +165,20 @@ internal class WalletViewModel @Inject constructor(
.onEach(::updateWallets)
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
isBalanceHiddenUseCase()
.flowWithLifecycle(owner.lifecycle)
.onEach { hidden ->
isBalanceHidden = hidden
uiState = stateFactory.getHiddenBalanceState(isBalanceHidden = hidden)
}
.launchIn(viewModelScope)
viewModelScope.launch {
listenToFlipsUseCase()
.flowWithLifecycle(owner.lifecycle)
.collect()
}
}
private fun updateWallets(sourceList: List<UserWallet>) {