Updated on 2026-08-14
This commit is contained in:
parent
b8d1c54957
commit
8bcf8b555b
6 changed files with 106 additions and 48 deletions
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.common.ui.tokens
|
||||
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||
|
|
@ -28,16 +29,20 @@ import java.math.BigDecimal
|
|||
*/
|
||||
class TokenItemStateConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
||||
CryptoCurrencyToIconStateConverter().convert(it)
|
||||
},
|
||||
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = Companion::createTitleState,
|
||||
private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = {
|
||||
createSubtitleState(it, appCurrency)
|
||||
},
|
||||
private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = {
|
||||
createFiatAmountState(it, appCurrency)
|
||||
},
|
||||
private val onItemClick: (CryptoCurrencyStatus) -> Unit,
|
||||
private val onItemLongClick: ((CryptoCurrencyStatus) -> Unit)? = null,
|
||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
||||
return when (value.value) {
|
||||
is CryptoCurrencyStatus.Loading -> value.mapToLoadingState()
|
||||
|
|
@ -56,7 +61,7 @@ class TokenItemStateConverter(
|
|||
private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading {
|
||||
return TokenItemState.Loading(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
iconState = iconStateProvider(this),
|
||||
titleState = titleStateProvider(this) as TokenItemState.TitleState.Content,
|
||||
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||
)
|
||||
|
|
@ -65,13 +70,10 @@ class TokenItemStateConverter(
|
|||
private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content {
|
||||
return TokenItemState.Content(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
iconState = iconStateProvider(this),
|
||||
titleState = titleStateProvider(this),
|
||||
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
||||
text = getFormattedFiatAmount(),
|
||||
hasStaked = !getStakedBalance().isZero(),
|
||||
),
|
||||
fiatAmountState = requireNotNull(fiatAmountStateProvider(this)),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = getFormattedAmount()),
|
||||
onItemClick = { onItemClick(this) },
|
||||
onItemLongClick = onItemLongClick?.let {
|
||||
|
|
@ -86,20 +88,10 @@ class TokenItemStateConverter(
|
|||
return amount.format { crypto(currency) }
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
|
||||
val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero()
|
||||
val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getStakedBalance() =
|
||||
(value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance().orZero()
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState(): TokenItemState.Unreachable {
|
||||
return TokenItemState.Unreachable(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
iconState = iconStateProvider(this),
|
||||
titleState = titleStateProvider(this),
|
||||
subtitleState = subtitleStateProvider(this),
|
||||
onItemClick = { onItemClick(this) },
|
||||
|
|
@ -112,7 +104,7 @@ class TokenItemStateConverter(
|
|||
private fun CryptoCurrencyStatus.mapToNoAddressTokenItemState(): TokenItemState.NoAddress {
|
||||
return TokenItemState.NoAddress(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(this),
|
||||
iconState = iconStateProvider(this),
|
||||
titleState = titleStateProvider(this),
|
||||
subtitleState = subtitleStateProvider(this),
|
||||
onItemLongClick = onItemLongClick?.let {
|
||||
|
|
@ -121,9 +113,9 @@ class TokenItemStateConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
companion object {
|
||||
|
||||
fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
|
||||
private fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
|
||||
return when (val value = currencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Loading,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
|
|
@ -145,7 +137,7 @@ class TokenItemStateConverter(
|
|||
}
|
||||
}
|
||||
|
||||
fun createSubtitleState(
|
||||
private fun createSubtitleState(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
appCurrency: AppCurrency,
|
||||
): TokenItemState.SubtitleState? {
|
||||
|
|
@ -163,6 +155,29 @@ class TokenItemStateConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createFiatAmountState(
|
||||
status: CryptoCurrencyStatus,
|
||||
appCurrency: AppCurrency,
|
||||
): TokenItemState.FiatAmountState? {
|
||||
return when (status.value) {
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> {
|
||||
TokenItemState.FiatAmountState.Content(
|
||||
text = status.getFormattedFiatAmount(appCurrency),
|
||||
hasStaked = !status.getStakedBalance().isZero(),
|
||||
)
|
||||
}
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Loading,
|
||||
-> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState {
|
||||
val fiatRate = value.fiatRate
|
||||
val priceChange = value.priceChange
|
||||
|
|
@ -189,5 +204,15 @@ class TokenItemStateConverter(
|
|||
private fun BigDecimal.getPriceChangeType(): PriceChangeType {
|
||||
return PriceChangeConverter.fromBigDecimal(value = this)
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.getFormattedFiatAmount(appCurrency: AppCurrency): String {
|
||||
val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero()
|
||||
val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getStakedBalance() =
|
||||
(value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance().orZero()
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,12 @@ import com.tangem.utils.converter.Converter
|
|||
|
||||
/**
|
||||
* Converts [CryptoCurrencyStatus] to [CurrencyIconState]
|
||||
*
|
||||
* @property isAvailable flag that indicates if the currency is available (affects on icon's grayscale)
|
||||
*/
|
||||
class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrencyStatus, CurrencyIconState> {
|
||||
class CryptoCurrencyToIconStateConverter(
|
||||
private val isAvailable: Boolean = true,
|
||||
) : Converter<CryptoCurrencyStatus, CurrencyIconState> {
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): CurrencyIconState {
|
||||
return when (val currency = value.currency) {
|
||||
|
|
@ -57,7 +61,7 @@ class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrencyStatus, Curre
|
|||
return CurrencyIconState.CoinIcon(
|
||||
url = coin.iconUrl,
|
||||
fallbackResId = coin.networkIconResId,
|
||||
isGrayscale = forceGrayscale || coin.network.isTestnet || isUnreachable,
|
||||
isGrayscale = forceGrayscale || coin.network.isTestnet || isUnreachable || !isAvailable,
|
||||
showCustomBadge = coin.isCustom && showCustomBadge,
|
||||
)
|
||||
}
|
||||
|
|
@ -68,7 +72,7 @@ class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrencyStatus, Curre
|
|||
showCustomBadge: Boolean = true,
|
||||
forceGrayscale: Boolean = false,
|
||||
): CurrencyIconState {
|
||||
val grayScale = forceGrayscale || token.network.isTestnet || isErrorStatus
|
||||
val grayScale = forceGrayscale || token.network.isTestnet || isErrorStatus || !isAvailable
|
||||
val background = token.tryGetBackgroundForTokenIcon(grayScale)
|
||||
val tint = getTintForTokenIcon(background)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.res.vectorResource
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState as TokenFiatAmountState
|
||||
|
|
@ -24,12 +25,19 @@ import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
|||
internal fun TokenFiatAmount(state: TokenFiatAmountState?, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TokenFiatAmountState.Content -> {
|
||||
FiatAmountText(
|
||||
ContentFiatAmount(
|
||||
text = state.text.orMaskWithStars(isBalanceHidden),
|
||||
hasStaked = state.hasStaked,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
is TokenItemState.FiatAmountState.TextContent -> {
|
||||
FiatAmountText(
|
||||
text = state.text.orMaskWithStars(isBalanceHidden),
|
||||
modifier = modifier,
|
||||
isAvailable = state.isAvailable,
|
||||
)
|
||||
}
|
||||
is TokenFiatAmountState.Loading -> {
|
||||
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
|
|
@ -41,7 +49,7 @@ internal fun TokenFiatAmount(state: TokenFiatAmountState?, isBalanceHidden: Bool
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun FiatAmountText(text: String, hasStaked: Boolean, modifier: Modifier = Modifier) {
|
||||
private fun ContentFiatAmount(text: String, hasStaked: Boolean, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -58,16 +66,23 @@ private fun FiatAmountText(text: String, hasStaked: Boolean, modifier: Modifier
|
|||
.size(TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
|
||||
FiatAmountText(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FiatAmountText(text: String, modifier: Modifier = Modifier, isAvailable: Boolean = true) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
color = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Modifier.placeholderSize(): Modifier = composed {
|
||||
return@composed this
|
||||
.padding(vertical = TangemTheme.dimens.spacing4)
|
||||
|
|
|
|||
|
|
@ -37,8 +37,12 @@ internal fun TokenPrice(state: TokenPriceState?, modifier: Modifier = Modifier)
|
|||
priceChangePercent = state.priceChangePercent,
|
||||
)
|
||||
}
|
||||
is TokenPriceState.TextContent -> PriceText(text = state.value, modifier = modifier)
|
||||
is TokenPriceState.Unknown -> PriceText(text = DASH_SIGN, modifier = modifier)
|
||||
is TokenPriceState.TextContent -> {
|
||||
PriceText(text = state.value, modifier = modifier, isAvailable = state.isAvailable)
|
||||
}
|
||||
is TokenPriceState.Unknown -> {
|
||||
PriceText(text = DASH_SIGN, modifier = modifier)
|
||||
}
|
||||
is TokenPriceState.Loading -> {
|
||||
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
|
|
@ -71,11 +75,11 @@ private fun PriceBlock(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceText(text: String, modifier: Modifier = Modifier) {
|
||||
private fun PriceText(text: String, modifier: Modifier = Modifier, isAvailable: Boolean = true) {
|
||||
AnimatedContent(targetState = text, label = "Update the price text", modifier = modifier) { animatedText ->
|
||||
Text(
|
||||
text = animatedText,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
color = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.caption2,
|
||||
|
|
@ -154,7 +158,7 @@ private class TokenPriceChangeStateProvider : CollectionPreviewParameterProvider
|
|||
priceChangePercent = "2.5%",
|
||||
type = PriceChangeType.NEUTRAL,
|
||||
),
|
||||
TokenPriceState.TextContent(value = "Subtitle"),
|
||||
TokenPriceState.TextContent(value = "Subtitle", isAvailable = true),
|
||||
TokenPriceState.Unknown,
|
||||
TokenPriceState.Loading,
|
||||
TokenPriceState.Locked,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as To
|
|||
internal fun TokenTitle(state: TokenTitleState?, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TokenTitleState.Content -> {
|
||||
ContentTitle(name = state.text, hasPending = state.hasPending, modifier = modifier)
|
||||
ContentTitle(state = state, modifier = modifier)
|
||||
}
|
||||
is TokenTitleState.Loading -> {
|
||||
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
|
|
@ -35,7 +35,7 @@ internal fun TokenTitle(state: TokenTitleState?, modifier: Modifier = Modifier)
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentTitle(name: String, hasPending: Boolean, modifier: Modifier = Modifier) {
|
||||
private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing6),
|
||||
|
|
@ -45,21 +45,25 @@ private fun ContentTitle(name: String, hasPending: Boolean, modifier: Modifier =
|
|||
* If currency name has a long width, then it will completely displace the image.
|
||||
* So we need to use [weight] to avoid displacement.
|
||||
*/
|
||||
CurrencyNameText(name = name, modifier = Modifier.weight(weight = 1f, fill = false))
|
||||
CurrencyNameText(
|
||||
name = state.text,
|
||||
isAvailable = state.isAvailable,
|
||||
modifier = Modifier.weight(weight = 1f, fill = false),
|
||||
)
|
||||
|
||||
PendingTransactionImage(
|
||||
hasPending = hasPending,
|
||||
hasPending = state.hasPending,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CurrencyNameText(name: String, modifier: Modifier = Modifier) {
|
||||
private fun CurrencyNameText(name: String, isAvailable: Boolean, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = name,
|
||||
modifier = modifier,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
color = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
|
|
|
|||
|
|
@ -160,7 +160,11 @@ sealed class TokenItemState {
|
|||
@Immutable
|
||||
sealed class TitleState {
|
||||
|
||||
data class Content(val text: String, val hasPending: Boolean = false) : TitleState()
|
||||
data class Content(
|
||||
val text: String,
|
||||
val hasPending: Boolean = false,
|
||||
val isAvailable: Boolean = true,
|
||||
) : TitleState()
|
||||
|
||||
data object Loading : TitleState()
|
||||
|
||||
|
|
@ -176,7 +180,7 @@ sealed class TokenItemState {
|
|||
val type: PriceChangeType,
|
||||
) : SubtitleState()
|
||||
|
||||
data class TextContent(val value: String) : SubtitleState()
|
||||
data class TextContent(val value: String, val isAvailable: Boolean = true) : SubtitleState()
|
||||
|
||||
data object Unknown : SubtitleState()
|
||||
|
||||
|
|
@ -192,6 +196,8 @@ sealed class TokenItemState {
|
|||
val hasStaked: Boolean = false,
|
||||
) : FiatAmountState()
|
||||
|
||||
data class TextContent(val text: String, val isAvailable: Boolean = true) : FiatAmountState()
|
||||
|
||||
data object Loading : FiatAmountState()
|
||||
|
||||
data object Locked : FiatAmountState()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue