Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-06 11:40:31 +02:00
parent 2e857d5e3e
commit 45fa120411
49 changed files with 669 additions and 92 deletions

View file

@ -39,6 +39,7 @@ import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowContainer import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.LocalIsInDarkTheme import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.LocalWindowSize import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
@ -47,6 +48,7 @@ import com.tangem.core.ui.test.MarketsTestTags
import com.tangem.core.ui.test.TokenElementsTestTags import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.windowsize.WindowSizeType import com.tangem.core.ui.windowsize.WindowSizeType
import com.tangem.utils.StringsSigns.MINUS import com.tangem.utils.StringsSigns.MINUS
import java.math.BigDecimal
import kotlin.random.Random import kotlin.random.Random
@Composable @Composable
@ -88,6 +90,8 @@ fun MarketListItemContentV2(model: MarketsListItemUM, modifier: Modifier = Modif
.testTag(tag = TokenElementsTestTags.TOKEN_FIAT_AMOUNT), .testTag(tag = TokenElementsTestTags.TOKEN_FIAT_AMOUNT),
price = model.price.text, price = model.price.text,
priceChangeType = model.price.changeType, priceChangeType = model.price.changeType,
priceAnnotated = model.price.annotated,
priceValue = model.price.fiatPrice,
) )
TokenSubtitle( TokenSubtitle(
@ -127,7 +131,7 @@ private fun TokenTitle(name: String, currencySymbol: String, modifier: Modifier
.alignByBaseline(), .alignByBaseline(),
text = name, text = name,
color = TangemTheme.colors2.text.neutral.primary, color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodySemibold16, style = TangemTheme.typography2.bodyMedium16,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
@ -323,12 +327,16 @@ private fun Preview(@PreviewParameter(MarketChartListItemPreviewDataProvider::cl
price = MarketsListItemUM.Price( price = MarketsListItemUM.Price(
text = "0.${prices[0].first}023 $", text = "0.${prices[0].first}023 $",
changeType = prices[0].second, changeType = prices[0].second,
fiatPrice = BigDecimal(123123),
annotated = stringReference("0.${prices[0].first}023 $"),
), ),
) )
state2 = state2.copy( state2 = state2.copy(
price = MarketsListItemUM.Price( price = MarketsListItemUM.Price(
text = "0.${prices[1].first}023 $", text = "0.${prices[1].first}023 $",
changeType = prices[1].second, changeType = prices[1].second,
fiatPrice = BigDecimal(123123),
annotated = stringReference("0.${prices[0].first}023 $"),
), ),
) )
}, },

View file

@ -0,0 +1,22 @@
package com.tangem.common.ui.markets
import androidx.compose.ui.text.SpanStyle
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.formatStyled
import com.tangem.core.ui.format.bigdecimal.price
import com.tangem.core.ui.res.TangemTheme
import java.math.BigDecimal
/**
* Fiat amount for markets list rows: integer part in primary style, fractional part (from locale separator) in secondary.
*/
fun BigDecimal.toMarketsListItemPriceAnnotated(appCurrencyCode: String, appCurrencySymbol: String): TextReference {
return formatStyled {
fiat(
fiatCurrencyCode = appCurrencyCode,
fiatCurrencySymbol = appCurrencySymbol,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
).price()
}
}

View file

@ -29,12 +29,14 @@ import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.LocalWindowSize import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketsTestTags import com.tangem.core.ui.test.MarketsTestTags
import com.tangem.core.ui.windowsize.WindowSizeType import com.tangem.core.ui.windowsize.WindowSizeType
import com.tangem.utils.StringsSigns.MINUS import com.tangem.utils.StringsSigns.MINUS
import java.math.BigDecimal
import kotlin.random.Random import kotlin.random.Random
@Composable @Composable
@ -85,6 +87,8 @@ private fun MarketsListItemContentV1(model: MarketsListItemUM, modifier: Modifie
modifier = Modifier.alignByBaseline(), modifier = Modifier.alignByBaseline(),
price = model.price.text, price = model.price.text,
priceChangeType = model.price.changeType, priceChangeType = model.price.changeType,
priceAnnotated = model.price.annotated,
priceValue = model.price.fiatPrice,
) )
} }
@ -306,12 +310,16 @@ private fun Preview(@PreviewParameter(MarketChartListItemPreviewDataProvider::cl
price = MarketsListItemUM.Price( price = MarketsListItemUM.Price(
text = "0.${prices[0].first}023 $", text = "0.${prices[0].first}023 $",
changeType = prices[0].second, changeType = prices[0].second,
fiatPrice = BigDecimal(123123),
annotated = stringReference("0.${prices[0].first}023 $"),
), ),
) )
state2 = state2.copy( state2 = state2.copy(
price = MarketsListItemUM.Price( price = MarketsListItemUM.Price(
text = "0.${prices[1].first}023 $", text = "0.${prices[1].first}023 $",
changeType = prices[1].second, changeType = prices[1].second,
fiatPrice = BigDecimal(123123),
annotated = stringReference("0.${prices[0].first}023 $"),
), ),
) )
}, },

View file

@ -6,6 +6,7 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import java.math.BigDecimal
@Immutable @Immutable
data class MarketsListItemUM( data class MarketsListItemUM(
@ -40,7 +41,9 @@ data class MarketsListItemUM(
@Immutable @Immutable
data class Price( data class Price(
val text: String, val text: String,
val annotated: TextReference,
val changeType: PriceChangeType? = null, val changeType: PriceChangeType? = null,
val fiatPrice: BigDecimal,
) )
@Suppress("NullableToStringCall") @Suppress("NullableToStringCall")

View file

@ -7,6 +7,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
@Suppress("MagicNumber") @Suppress("MagicNumber")
class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvider<MarketsListItemUM>( class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvider<MarketsListItemUM>(
@ -18,7 +19,11 @@ class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvide
iconUrl = "", iconUrl = "",
ratingPosition = "1", ratingPosition = "1",
marketCap = "$6.233 B", marketCap = "$6.233 B",
price = MarketsListItemUM.Price(text = "31 285.72$"), price = MarketsListItemUM.Price(
text = "31 285.72$",
fiatPrice = BigDecimal(123123),
annotated = stringReference("31 285.72$"),
),
trendPercentText = "12.43%", trendPercentText = "12.43%",
trendType = PriceChangeType.UP, trendType = PriceChangeType.UP,
chartData = MarketChartRawData( chartData = MarketChartRawData(
@ -35,7 +40,11 @@ class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvide
iconUrl = null, iconUrl = null,
ratingPosition = "2", ratingPosition = "2",
marketCap = "$6.233 B", marketCap = "$6.233 B",
price = MarketsListItemUM.Price(text = "31 285.72$"), price = MarketsListItemUM.Price(
text = "31 285.72$",
fiatPrice = BigDecimal(123123),
annotated = stringReference("31 285.72$"),
),
trendPercentText = "12.43%", trendPercentText = "12.43%",
trendType = PriceChangeType.NEUTRAL, trendType = PriceChangeType.NEUTRAL,
chartData = null, chartData = null,
@ -50,7 +59,11 @@ class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvide
iconUrl = null, iconUrl = null,
ratingPosition = "10", ratingPosition = "10",
marketCap = "$6.23348172384781234 B", marketCap = "$6.23348172384781234 B",
price = MarketsListItemUM.Price(text = "31 285.72$"), price = MarketsListItemUM.Price(
text = "31 285.72$",
fiatPrice = BigDecimal(123123),
annotated = stringReference("31 285.72$"),
),
trendPercentText = "12.43%", trendPercentText = "12.43%",
trendType = PriceChangeType.DOWN, trendType = PriceChangeType.DOWN,
chartData = MarketChartRawData( chartData = MarketChartRawData(
@ -67,7 +80,11 @@ class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvide
iconUrl = null, iconUrl = null,
ratingPosition = "10", ratingPosition = "10",
marketCap = null, marketCap = null,
price = MarketsListItemUM.Price(text = "31 285.72$"), price = MarketsListItemUM.Price(
text = "31 285.72$",
fiatPrice = BigDecimal(123123),
annotated = stringReference("31 285.72$"),
),
trendPercentText = "12.43%", trendPercentText = "12.43%",
trendType = PriceChangeType.UP, trendType = PriceChangeType.UP,
chartData = MarketChartRawData( chartData = MarketChartRawData(
@ -84,7 +101,11 @@ class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvide
iconUrl = null, iconUrl = null,
ratingPosition = null, ratingPosition = null,
marketCap = "$6.233 B", marketCap = "$6.233 B",
price = MarketsListItemUM.Price(text = "31 285.72$"), price = MarketsListItemUM.Price(
text = "31 285.72$",
fiatPrice = BigDecimal(123123),
annotated = stringReference("31 285.72$"),
),
trendPercentText = "12.43%", trendPercentText = "12.43%",
trendType = PriceChangeType.UP, trendType = PriceChangeType.UP,
chartData = MarketChartRawData( chartData = MarketChartRawData(
@ -101,7 +122,11 @@ class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvide
iconUrl = null, iconUrl = null,
ratingPosition = null, ratingPosition = null,
marketCap = null, marketCap = null,
price = MarketsListItemUM.Price(text = "31 285.72$"), price = MarketsListItemUM.Price(
text = "31 285.72$",
fiatPrice = BigDecimal(123123),
annotated = stringReference("31 285.72$"),
),
trendPercentText = "12.43%", trendPercentText = "12.43%",
trendType = PriceChangeType.UP, trendType = PriceChangeType.UP,
chartData = MarketChartRawData( chartData = MarketChartRawData(

View file

@ -6,25 +6,33 @@ import androidx.compose.animation.core.tween
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import java.math.BigDecimal
/** /**
* Text view for token price. * Text view for token price.
* *
* @param price Price of the token. * @param price Plain price string (used by V1 and as fallback for V2 when [priceAnnotated] is null).
* @param priceAnnotated Optional styled price (locale-aware decimal separator + secondary fractional color).
* @param priceChangeType Type of the price change. * @param priceChangeType Type of the price change.
*/ */
@Composable @Composable
fun TokenPriceText(price: String, modifier: Modifier = Modifier, priceChangeType: PriceChangeType? = null) { fun TokenPriceText(
priceValue: BigDecimal,
price: String,
priceAnnotated: TextReference,
modifier: Modifier = Modifier,
priceChangeType: PriceChangeType? = null,
) {
if (LocalRedesignEnabled.current) { if (LocalRedesignEnabled.current) {
TokenPriceTextV2( TokenPriceTextV2(
price = price, priceValue = priceValue,
priceAnnotated = priceAnnotated,
modifier = modifier, modifier = modifier,
priceChangeType = priceChangeType, priceChangeType = priceChangeType,
) )
@ -76,16 +84,20 @@ private fun TokenPriceTextV1(price: String, modifier: Modifier = Modifier, price
} }
@Composable @Composable
private fun TokenPriceTextV2(price: String, modifier: Modifier = Modifier, priceChangeType: PriceChangeType? = null) { private fun TokenPriceTextV2(
priceValue: BigDecimal,
priceAnnotated: TextReference,
modifier: Modifier = Modifier,
priceChangeType: PriceChangeType? = null,
) {
val growColor = TangemTheme.colors2.text.status.accent val growColor = TangemTheme.colors2.text.status.accent
val fallColor = TangemTheme.colors2.text.status.warning val fallColor = TangemTheme.colors2.text.status.warning
val generalColor = TangemTheme.colors2.text.neutral.primary val generalColor = TangemTheme.colors2.text.neutral.primary
val decimalColor = TangemTheme.colors2.text.neutral.secondary
val color = remember(generalColor) { Animatable(generalColor) } val color = remember(generalColor) { Animatable(generalColor) }
var isAnimationSkipped by remember { mutableStateOf(false) } var isAnimationSkipped by remember { mutableStateOf(false) }
LaunchedEffect(price) { LaunchedEffect(priceValue) {
if (!isAnimationSkipped) { if (!isAnimationSkipped) {
isAnimationSkipped = true isAnimationSkipped = true
return@LaunchedEffect return@LaunchedEffect
@ -103,27 +115,9 @@ private fun TokenPriceTextV2(price: String, modifier: Modifier = Modifier, price
} }
} }
val annotatedText = remember(price) {
buildAnnotatedString {
val dotIndex = price.indexOf(".")
if (dotIndex == -1) {
append(price)
} else {
append(price.take(dotIndex))
withStyle(
style = SpanStyle(color = decimalColor),
) {
append(price.substring(dotIndex))
}
}
}
}
Text( Text(
modifier = modifier, modifier = modifier,
text = annotatedText, text = priceAnnotated.resolveAnnotatedReference(),
color = color.value, color = color.value,
maxLines = 1, maxLines = 1,
style = TangemTheme.typography2.bodySemibold16, style = TangemTheme.typography2.bodySemibold16,

View file

@ -116,6 +116,8 @@ data class TokenMarketInfoResponse(
val maxSupply: BigDecimal?, val maxSupply: BigDecimal?,
@Json(name = "fully_diluted_valuation") @Json(name = "fully_diluted_valuation")
val fullyDilutedValuation: BigDecimal?, val fullyDilutedValuation: BigDecimal?,
@Json(name = "fully_diluted_valuation_change_24h")
val fullyDilutedValuationChange24H: BigDecimal?,
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)

View file

@ -172,6 +172,40 @@ fun BigDecimalFiatFormat.price(): BigDecimalFormat = BigDecimalFormat { value ->
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
} }
/**
* Formats fiat price with precision calculated based on the value.
* Styled version fractional part uses [spanStyleReference].
* @see getFiatPriceAmountWithScale
*/
fun BigDecimalFiatFormatStyled.price() = price(spanStyleReference)
private fun BigDecimalFiatFormatStyled.price(spanStyleReference: SpanStyleReference) = BigDecimalFormatStyled { value ->
val formatterCurrency = getJavaCurrencyByCode(fiatCurrencyCode)
val (priceAmount, finalScale) = getFiatPriceAmountWithScale(value = value)
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
currency = formatterCurrency
maximumFractionDigits = finalScale
minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS
roundingMode = RoundingMode.HALF_UP
}
val decimalSeparator = (formatter as? DecimalFormat)?.decimalFormatSymbols?.decimalSeparator
val formattedAmount = formatter.format(priceAmount)
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
val separatorIndex = decimalSeparator?.let { formattedAmount.indexOf(it) } ?: formattedAmount.length
val wholePart = formattedAmount.take(separatorIndex)
val fractionalPart = formattedAmount.drop(separatorIndex)
combinedReference(
stringReference(wholePart),
styledStringReference(fractionalPart, spanStyleReference),
)
}
/** /**
* Formats fiat amount with an exact number of fractional digits. * Formats fiat amount with an exact number of fractional digits.
*/ */

View file

@ -198,6 +198,18 @@ class TangemTypography2 internal constructor(
), ),
) )
val bodyMedium16: TextStyle = TextStyle(
fontFamily = fontFamily,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = -0.32f, type = TextUnitType.Sp),
lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp),
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
)
val bodyRegular15: TextStyle = TextStyle( val bodyRegular15: TextStyle = TextStyle(
fontFamily = fontFamily, fontFamily = fontFamily,
fontSize = 15.sp, fontSize = 15.sp,

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M4,8.667C4.368,8.667 4.667,8.965 4.667,9.333V12C4.667,12.368 4.368,12.667 4,12.667C3.632,12.667 3.333,12.368 3.333,12V9.333C3.333,8.965 3.632,8.667 4,8.667ZM8,6C8.368,6 8.667,6.298 8.667,6.667V12C8.667,12.368 8.368,12.667 8,12.667C7.632,12.667 7.333,12.368 7.333,12V6.667C7.333,6.298 7.632,6 8,6ZM12,3.333C12.368,3.333 12.667,3.632 12.667,4V12C12.667,12.368 12.368,12.667 12,12.667C11.632,12.667 11.333,12.368 11.333,12V4C11.333,3.632 11.632,3.333 12,3.333Z"
android:fillColor="#656565"/>
</vector>

View file

@ -111,6 +111,7 @@ internal class TokenMarketInfoConverter(
volume24h = volume24h, volume24h = volume24h,
maxSupply = maxSupply, maxSupply = maxSupply,
fullyDilutedValuation = fullyDilutedValuation, fullyDilutedValuation = fullyDilutedValuation,
fullyDilutedValuationChange24 = fullyDilutedValuationChange24H,
) )
} }

View file

@ -5,7 +5,9 @@ import com.tangem.data.search.model.TextHintDTO
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.search.model.RecentSearchToken import com.tangem.domain.search.model.RecentSearchToken
import com.tangem.domain.search.model.SearchTextHint import com.tangem.domain.search.model.SearchTextHint
import com.tangem.domain.search.model.TokenPriceChangeDirection
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class TextHintDTOToSearchTextHintConverter : Converter<TextHintDTO, SearchTextHint> { internal class TextHintDTOToSearchTextHintConverter : Converter<TextHintDTO, SearchTextHint> {
override fun convert(value: TextHintDTO): SearchTextHint { override fun convert(value: TextHintDTO): SearchTextHint {
@ -24,6 +26,15 @@ internal class RecentTokenDTOToRecentSearchTokenConverter : Converter<RecentToke
symbol = value.symbol, symbol = value.symbol,
imageUrl = value.imageUrl, imageUrl = value.imageUrl,
timestamp = value.timestamp, timestamp = value.timestamp,
appCurrencyCode = value.appCurrencyCode,
appCurrencySymbol = value.appCurrencySymbol,
price = value.pricePlain.toBigDecimalOrNull() ?: BigDecimal.ZERO,
priceChangePercent = value.priceChangePercent,
priceChangeDirection = value.priceChangeDirection.toTokenPriceChangeDirection(),
marketCap = value.marketCap,
ratingPosition = value.ratingPosition,
isUnder100kMarketCap = value.isUnder100kMarketCap,
stakingRateText = value.stakingRateText,
) )
} }
} }
@ -36,6 +47,20 @@ internal class RecentSearchTokenToRecentTokenDTOConverter : Converter<RecentSear
symbol = value.symbol, symbol = value.symbol,
imageUrl = value.imageUrl, imageUrl = value.imageUrl,
timestamp = value.timestamp, timestamp = value.timestamp,
pricePlain = value.price.toPlainString(),
appCurrencyCode = value.appCurrencyCode,
appCurrencySymbol = value.appCurrencySymbol,
priceChangePercent = value.priceChangePercent,
priceChangeDirection = value.priceChangeDirection.name,
marketCap = value.marketCap,
ratingPosition = value.ratingPosition,
isUnder100kMarketCap = value.isUnder100kMarketCap,
stakingRateText = value.stakingRateText,
) )
} }
} }
private fun String.toTokenPriceChangeDirection(): TokenPriceChangeDirection {
return runCatching { TokenPriceChangeDirection.valueOf(this) }
.getOrDefault(TokenPriceChangeDirection.NEUTRAL)
}

View file

@ -14,6 +14,9 @@ internal data class TextHintDTO(
val timestamp: Long, val timestamp: Long,
) )
/**
* @param appCurrencyCode currency code (e.g. "USD") the [pricePlain] was quoted in
*/
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class RecentTokenDTO( internal data class RecentTokenDTO(
val id: String, val id: String,
@ -21,4 +24,13 @@ internal data class RecentTokenDTO(
val symbol: String, val symbol: String,
val imageUrl: String?, val imageUrl: String?,
val timestamp: Long, val timestamp: Long,
val pricePlain: String,
val appCurrencyCode: String,
val appCurrencySymbol: String,
val priceChangePercent: String,
val priceChangeDirection: String,
val marketCap: String?,
val ratingPosition: String?,
val isUnder100kMarketCap: Boolean,
val stakingRateText: String?,
) )

View file

@ -55,6 +55,7 @@ data class TokenMarketInfo(
val volume24h: BigDecimal?, val volume24h: BigDecimal?,
val maxSupply: BigDecimal?, val maxSupply: BigDecimal?,
val fullyDilutedValuation: BigDecimal?, val fullyDilutedValuation: BigDecimal?,
val fullyDilutedValuationChange24: BigDecimal?,
) )
data class SecurityData( data class SecurityData(

View file

@ -1,9 +1,11 @@
package com.tangem.domain.search.model package com.tangem.domain.search.model
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import java.math.BigDecimal
/** /**
* @property timestamp epoch milliseconds * @property timestamp epoch milliseconds
* @property price fiat quote in the app currency that was active when the token was saved
*/ */
data class RecentSearchToken( data class RecentSearchToken(
val id: CryptoCurrency.RawID, val id: CryptoCurrency.RawID,
@ -11,4 +13,13 @@ data class RecentSearchToken(
val symbol: String, val symbol: String,
val imageUrl: String?, val imageUrl: String?,
val timestamp: Long, val timestamp: Long,
val appCurrencyCode: String,
val appCurrencySymbol: String,
val price: BigDecimal,
val priceChangePercent: String,
val priceChangeDirection: TokenPriceChangeDirection,
val marketCap: String?,
val ratingPosition: String?,
val isUnder100kMarketCap: Boolean,
val stakingRateText: String?,
) )

View file

@ -0,0 +1,7 @@
package com.tangem.domain.search.model
enum class TokenPriceChangeDirection {
UP,
DOWN,
NEUTRAL,
}

View file

@ -35,10 +35,11 @@ internal class EarnTokenWithCurrencyToListItemUMConverter(
formatArgs = wrappedList(convertPercent(value.earnToken.apy)), formatArgs = wrappedList(convertPercent(value.earnToken.apy)),
) )
}, },
earnType = when (value.earnToken.type) { earnTypeTitle = when (value.earnToken.type) {
EarnType.STAKING -> TextReference.Res(R.string.common_staking) EarnType.STAKING -> TextReference.Res(R.string.common_staking)
EarnType.YIELD -> TextReference.Res(R.string.common_yield_mode) EarnType.YIELD -> TextReference.Res(R.string.common_yield_mode)
}, },
earnType = value.earnToken.type,
onItemClick = { onItemClick(value) }, onItemClick = { onItemClick(value) },
) )
} }

View file

@ -5,6 +5,7 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.sorted import com.tangem.common.ui.charts.state.sorted
import com.tangem.common.ui.markets.models.MarketsListItemUM import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
@ -110,7 +111,12 @@ internal class MarketsTokenItemConverter(
return MarketsListItemUM.Price( return MarketsListItemUM.Price(
text = priceText, text = priceText,
annotated = tokenQuotesShort.currentPrice.toMarketsListItemPriceAnnotated(
appCurrencyCode = appCurrency.code,
appCurrencySymbol = appCurrency.symbol,
),
changeType = changeType, changeType = changeType,
fiatPrice = tokenQuotesShort.currentPrice,
) )
} }

View file

@ -230,6 +230,9 @@ internal class MarketsTokenDetailsModel @Inject constructor(
fiatCurrencySymbol = currentAppCurrency.value.symbol, fiatCurrencySymbol = currentAppCurrency.value.symbol,
).price() ).price()
}, },
priceAnnotated = params.token.tokenQuotes.currentPrice.toMarketsTokenDetailsPriceAnnotated(
currentAppCurrency.value,
),
dateTimeText = resourceReference(R.string.common_today), dateTimeText = resourceReference(R.string.common_today),
priceChangePercentText = params.token.tokenQuotes.h24Percent?.format { percent() }, priceChangePercentText = params.token.tokenQuotes.h24Percent?.format { percent() },
priceChangeType = params.token.tokenQuotes.h24Percent.percentChangeType(), priceChangeType = params.token.tokenQuotes.h24Percent.percentChangeType(),
@ -505,6 +508,9 @@ internal class MarketsTokenDetailsModel @Inject constructor(
fiatCurrencyCode = currentAppCurrency.value.code, fiatCurrencyCode = currentAppCurrency.value.code,
).price() ).price()
}, },
priceAnnotated = newInfo.quotes.currentPrice.toMarketsTokenDetailsPriceAnnotated(
currentAppCurrency.value,
),
priceChangePercentText = newInfo.quotes.getFormattedPercentByInterval( priceChangePercentText = newInfo.quotes.getFormattedPercentByInterval(
interval = marketsTokenDetailsUM.selectedInterval, interval = marketsTokenDetailsUM.selectedInterval,
), ),
@ -604,7 +610,8 @@ internal class MarketsTokenDetailsModel @Inject constructor(
) )
} ?: getDefaultDateTimeString(currentState.selectedInterval) } ?: getDefaultDateTimeString(currentState.selectedInterval)
val priceText = (price ?: currentQuotes.value.currentPrice).format { val amountForPrice = price ?: currentQuotes.value.currentPrice
val priceText = amountForPrice.format {
fiat( fiat(
fiatCurrencySymbol = currentAppCurrency.value.symbol, fiatCurrencySymbol = currentAppCurrency.value.symbol,
fiatCurrencyCode = currentAppCurrency.value.code, fiatCurrencyCode = currentAppCurrency.value.code,
@ -625,6 +632,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
isMarkerSet = markerTimestamp != null, isMarkerSet = markerTimestamp != null,
dateTimeText = dateTimeText, dateTimeText = dateTimeText,
priceText = priceText, priceText = priceText,
priceAnnotated = amountForPrice.toMarketsTokenDetailsPriceAnnotated(currentAppCurrency.value),
priceChangePercentText = percentText, priceChangePercentText = percentText,
priceChangeType = percent.percentChangeType(), priceChangeType = percent.percentChangeType(),
) )

View file

@ -1,10 +1,7 @@
package com.tangem.features.feed.model.market.details.converter package com.tangem.features.feed.model.market.details.converter
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.tangem.core.ui.extensions.combinedReference import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.compact import com.tangem.core.ui.format.bigdecimal.compact
import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.fiat
@ -188,6 +185,7 @@ internal class MetricsConverter(
formatArgs = wrappedList(formatted), formatArgs = wrappedList(formatted),
) )
}, },
fullyDilutedValuationChange24 = fullyDilutedValuationChange24?.convertChange(),
onInfoClick = { onInfoClick = {
onInfoClick( onInfoClick(
InfoBottomSheetContent( InfoBottomSheetContent(
@ -280,6 +278,17 @@ internal class MetricsConverter(
} }
} }
private fun BigDecimal?.convertChange(): TextReference? {
if (this == null) return null
val amount = this.abs().formatAmount()
val value = when {
this > BigDecimal.ZERO -> StringsSigns.PLUS + amount
this < BigDecimal.ZERO -> StringsSigns.MINUS + amount
else -> amount
}
return value?.let(::stringReference)
}
@Suppress("MagicNumber") @Suppress("MagicNumber")
private fun getLiquidity(volume24h: BigDecimal?, marketCap: BigDecimal?): Float? { private fun getLiquidity(volume24h: BigDecimal?, marketCap: BigDecimal?): Float? {
if (volume24h == null || marketCap == null || marketCap == BigDecimal.ZERO) return null if (volume24h == null || marketCap == null || marketCap == BigDecimal.ZERO) return null

View file

@ -1,10 +1,12 @@
package com.tangem.features.feed.model.market.details.formatter package com.tangem.features.feed.model.market.details.formatter
import androidx.compose.ui.text.SpanStyle
import com.tangem.common.ui.charts.state.MarketChartLook import com.tangem.common.ui.charts.state.MarketChartLook
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.getFiatPriceAmountWithScale import com.tangem.core.ui.format.bigdecimal.*
import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.domain.markets.TokenQuotes import com.tangem.domain.markets.TokenQuotes
import java.math.BigDecimal import java.math.BigDecimal
@ -74,3 +76,13 @@ internal fun PriceChangeType.toChartType(): MarketChartLook.Type {
PriceChangeType.NEUTRAL -> MarketChartLook.Type.Neutral PriceChangeType.NEUTRAL -> MarketChartLook.Type.Neutral
} }
} }
internal fun BigDecimal.toMarketsTokenDetailsPriceAnnotated(appCurrency: AppCurrency): TextReference {
return formatStyled {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.tertiary) },
).price()
}
}

View file

@ -66,6 +66,7 @@ internal class QuotesStateUpdater(
fiatCurrencyCode = currentAppCurrency().code, fiatCurrencyCode = currentAppCurrency().code,
).price() ).price()
}, },
priceAnnotated = newQuotes.currentPrice.toMarketsTokenDetailsPriceAnnotated(currentAppCurrency()),
priceChangePercentText = newQuotes.getFormattedPercentByInterval( priceChangePercentText = newQuotes.getFormattedPercentByInterval(
interval = stateToUpdate.selectedInterval, interval = stateToUpdate.selectedInterval,
), ),

View file

@ -6,6 +6,7 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.sorted import com.tangem.common.ui.charts.state.sorted
import com.tangem.common.ui.markets.models.MarketsListItemUM import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.format.bigdecimal.* import com.tangem.core.ui.format.bigdecimal.*
import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
@ -68,7 +69,12 @@ internal class RelatedTokenConverter(private val appCurrency: AppCurrency) :
return MarketsListItemUM.Price( return MarketsListItemUM.Price(
text = priceText, text = priceText,
annotated = tokenInfo.quotes.currentPrice.toMarketsListItemPriceAnnotated(
appCurrencyCode = appCurrency.code,
appCurrencySymbol = appCurrency.symbol,
),
changeType = null, changeType = null,
fiatPrice = tokenInfo.quotes.currentPrice,
) )
} }

View file

@ -5,19 +5,27 @@ import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.common.ui.charts.state.MarketChartData
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.sorted
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
import com.tangem.domain.markets.GetTokenPriceChartUseCase
import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.domain.models.account.AccountName import com.tangem.domain.models.account.AccountName
import com.tangem.domain.search.model.RecentSearchToken
import com.tangem.domain.search.usecase.ClearSearchHistoryUseCase import com.tangem.domain.search.usecase.ClearSearchHistoryUseCase
import com.tangem.domain.search.usecase.GetSearchResultsUseCase import com.tangem.domain.search.usecase.GetSearchResultsUseCase
import com.tangem.domain.search.usecase.SaveRecentSearchTokenUseCase
import com.tangem.domain.search.usecase.SaveSearchQueryUseCase import com.tangem.domain.search.usecase.SaveSearchQueryUseCase
import com.tangem.features.feed.components.search.DefaultSearchComponent import com.tangem.features.feed.components.search.DefaultSearchComponent
import com.tangem.features.feed.model.market.list.state.MarketsListUM import com.tangem.features.feed.model.market.list.state.MarketsListUM
import com.tangem.features.feed.model.market.list.state.SortByTypeUM import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.model.market.list.statemanager.MarketsListBatchFlowManager import com.tangem.features.feed.model.market.list.statemanager.MarketsListBatchFlowManager
import com.tangem.features.feed.model.search.converter.MarketsListItemUMToRecentSearchTokenConverter
import com.tangem.features.feed.model.search.converter.MarketsListItemUMWithAppCurrency
import com.tangem.features.feed.model.search.converter.RecentSearchTokenToMarketsListItemUMConverter
import com.tangem.features.feed.model.search.converter.RecentSearchTokenWithAppCurrency
import com.tangem.features.feed.model.search.state.SearchStateController import com.tangem.features.feed.model.search.state.SearchStateController
import com.tangem.features.feed.model.search.state.transformers.* import com.tangem.features.feed.model.search.state.transformers.*
import com.tangem.features.feed.ui.search.state.* import com.tangem.features.feed.ui.search.state.*
@ -28,6 +36,9 @@ import com.tangem.utils.coroutines.saveIn
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -45,7 +56,9 @@ internal class SearchModel @Inject constructor(
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getSearchResultsUseCase: GetSearchResultsUseCase, private val getSearchResultsUseCase: GetSearchResultsUseCase,
private val saveSearchQueryUseCase: SaveSearchQueryUseCase, private val saveSearchQueryUseCase: SaveSearchQueryUseCase,
private val saveRecentSearchTokenUseCase: SaveRecentSearchTokenUseCase,
private val clearSearchHistoryUseCase: ClearSearchHistoryUseCase, private val clearSearchHistoryUseCase: ClearSearchHistoryUseCase,
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
private val stateController: SearchStateController, private val stateController: SearchStateController,
) : Model() { ) : Model() {
@ -64,6 +77,18 @@ internal class SearchModel @Inject constructor(
initialValue = AppCurrency.Default, initialValue = AppCurrency.Default,
) )
private val marketsListItemToRecentSearchTokenConverter by lazy {
MarketsListItemUMToRecentSearchTokenConverter()
}
private val recentSearchTokenToMarketsListItemConverter by lazy {
RecentSearchTokenToMarketsListItemUMConverter()
}
private val priceAndTimePointValuesConverter by lazy {
PriceAndTimePointValuesConverter(shouldFormatAxis = false)
}
private val searchMarketsListManager by lazy { private val searchMarketsListManager by lazy {
MarketsListBatchFlowManager( MarketsListBatchFlowManager(
getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase, getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase,
@ -108,8 +133,15 @@ internal class SearchModel @Inject constructor(
stateController.update(UpdateSearchBarQueryTransformer(text)) stateController.update(UpdateSearchBarQueryTransformer(text))
} }
fun onResultMarketTokenClick() { fun onResultMarketTokenClick(item: MarketsListItemUM) {
modelScope.launch(dispatchers.default) { modelScope.launch(dispatchers.default) {
val appCurrency = currentAppCurrency.value
val input = MarketsListItemUMWithAppCurrency(
item = item,
appCurrencyCode = appCurrency.code,
appCurrencySymbol = appCurrency.symbol,
)
saveRecentSearchTokenUseCase(marketsListItemToRecentSearchTokenConverter.convert(input))
saveSearchQueryUseCase(stateController.value.searchBar.query) saveSearchQueryUseCase(stateController.value.searchBar.query)
} }
} }
@ -263,31 +295,43 @@ internal class SearchModel @Inject constructor(
TextHintItemUM(text = hint.text) TextHintItemUM(text = hint.text)
}.toImmutableList() }.toImmutableList()
val appCurrency = currentAppCurrency.value
val recentTokens = searchResult.recentTokens.map { token -> val recentTokens = searchResult.recentTokens.map { token ->
token.toMarketsListItemUM() recentSearchTokenToMarketsListItemConverter.convert(
RecentSearchTokenWithAppCurrency(token = token, appCurrency = appCurrency),
)
}.toImmutableList() }.toImmutableList()
stateController.update(UpdateHistoryTransformer(textHints, recentTokens)) stateController.update(UpdateHistoryTransformer(textHints, recentTokens))
loadRecentTokenCharts(recentTokens, appCurrency)
} }
}.saveIn(searchResultsJob) }.saveIn(searchResultsJob)
} }
private fun RecentSearchToken.toMarketsListItemUM(): MarketsListItemUM { private suspend fun loadRecentTokenCharts(tokens: ImmutableList<MarketsListItemUM>, appCurrency: AppCurrency) {
return MarketsListItemUM( coroutineScope {
id = id, tokens.map { token ->
name = name, async(dispatchers.io) {
currencySymbol = symbol, val chart = getTokenPriceChartUseCase(
iconUrl = imageUrl, appCurrency = appCurrency,
ratingPosition = null, interval = PriceChangeInterval.H24,
marketCap = null, tokenId = token.id,
price = MarketsListItemUM.Price(text = ""), tokenSymbol = token.currencySymbol,
trendPercentText = "", preview = true,
trendType = PriceChangeType.NEUTRAL, ).getOrElse { return@async }
chartData = null,
isUnder100kMarketCap = false, val chartData = priceAndTimePointValuesConverter.convert(
stakingRate = null, MarketChartData.Data(
updateTimestamp = timestamp, y = chart.priceY.toImmutableList(),
) x = chart.timeStamps.map { it.toBigDecimal() }.toImmutableList(),
).sorted(),
)
stateController.update(UpdateRecentTokenChartTransformer(token.id, chartData))
}
}.awaitAll()
}
} }
private fun AccountName.toDisplayString(): String { private fun AccountName.toDisplayString(): String {

View file

@ -0,0 +1,41 @@
package com.tangem.features.feed.model.search.converter
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.search.model.RecentSearchToken
import com.tangem.domain.search.model.TokenPriceChangeDirection
import com.tangem.utils.converter.Converter
internal class MarketsListItemUMToRecentSearchTokenConverter :
Converter<MarketsListItemUMWithAppCurrency, RecentSearchToken> {
override fun convert(value: MarketsListItemUMWithAppCurrency): RecentSearchToken {
val item = value.item
val direction = when (item.trendType) {
PriceChangeType.UP -> TokenPriceChangeDirection.UP
PriceChangeType.DOWN -> TokenPriceChangeDirection.DOWN
PriceChangeType.NEUTRAL -> TokenPriceChangeDirection.NEUTRAL
}
val stakingText = when (val rate = item.stakingRate) {
is TextReference.Str -> rate.value
is TextReference.StyledStr -> rate.value
else -> null
}
return RecentSearchToken(
id = item.id,
name = item.name,
symbol = item.currencySymbol,
imageUrl = item.iconUrl,
timestamp = item.updateTimestamp ?: System.currentTimeMillis(),
appCurrencyCode = value.appCurrencyCode,
appCurrencySymbol = value.appCurrencySymbol,
price = item.price.fiatPrice,
priceChangePercent = item.trendPercentText,
priceChangeDirection = direction,
marketCap = item.marketCap,
ratingPosition = item.ratingPosition,
isUnder100kMarketCap = item.isUnder100kMarketCap,
stakingRateText = stakingText,
)
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.features.feed.model.search.converter
import com.tangem.common.ui.markets.models.MarketsListItemUM
internal data class MarketsListItemUMWithAppCurrency(
val item: MarketsListItemUM,
val appCurrencyCode: String,
val appCurrencySymbol: String,
)

View file

@ -0,0 +1,55 @@
package com.tangem.features.feed.model.search.converter
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.price
import com.tangem.domain.search.model.TokenPriceChangeDirection
import com.tangem.utils.converter.Converter
internal class RecentSearchTokenToMarketsListItemUMConverter :
Converter<RecentSearchTokenWithAppCurrency, MarketsListItemUM> {
override fun convert(value: RecentSearchTokenWithAppCurrency): MarketsListItemUM {
val token = value.token
val currencyCode = token.appCurrencyCode.ifEmpty { value.appCurrency.code }
val currencySymbol = token.appCurrencySymbol.ifEmpty { value.appCurrency.symbol }
val trendType = when (token.priceChangeDirection) {
TokenPriceChangeDirection.UP -> PriceChangeType.UP
TokenPriceChangeDirection.DOWN -> PriceChangeType.DOWN
TokenPriceChangeDirection.NEUTRAL -> PriceChangeType.NEUTRAL
}
val priceText = token.price.format {
fiat(
fiatCurrencyCode = currencyCode,
fiatCurrencySymbol = currencySymbol,
).price()
}
return MarketsListItemUM(
id = token.id,
name = token.name,
currencySymbol = token.symbol,
iconUrl = token.imageUrl,
ratingPosition = token.ratingPosition,
marketCap = token.marketCap,
price = MarketsListItemUM.Price(
text = priceText,
annotated = token.price.toMarketsListItemPriceAnnotated(
appCurrencyCode = currencyCode,
appCurrencySymbol = currencySymbol,
),
changeType = null,
fiatPrice = token.price,
),
trendPercentText = token.priceChangePercent,
trendType = trendType,
chartData = null,
isUnder100kMarketCap = token.isUnder100kMarketCap,
stakingRate = token.stakingRateText?.let { TextReference.Str(it) },
updateTimestamp = token.timestamp,
)
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.features.feed.model.search.converter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.search.model.RecentSearchToken
internal data class RecentSearchTokenWithAppCurrency(
val token: RecentSearchToken,
val appCurrency: AppCurrency,
)

View file

@ -0,0 +1,30 @@
package com.tangem.features.feed.model.search.state.transformers
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.feed.ui.search.state.SearchContentUM
import com.tangem.features.feed.ui.search.state.SearchUM
import kotlinx.collections.immutable.toImmutableList
internal class UpdateRecentTokenChartTransformer(
private val tokenId: CryptoCurrency.RawID,
private val chartData: MarketChartRawData,
) : SearchUMTransformer {
override fun transform(prevState: SearchUM): SearchUM {
val content = prevState.content
if (content !is SearchContentUM.History) return prevState
val updatedTokens = content.recentTokens.map { token ->
if (token.id == tokenId) {
token.copy(chartData = chartData)
} else {
token
}
}.toImmutableList()
return prevState.copy(
content = content.copy(recentTokens = updatedTokens),
)
}
}

View file

@ -24,6 +24,7 @@ import com.tangem.core.ui.components.list.InfiniteListHandler
import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.* import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.* import com.tangem.core.ui.res.*
import com.tangem.domain.models.earn.EarnType
import com.tangem.features.feed.ui.earn.components.* import com.tangem.features.feed.ui.earn.components.*
import com.tangem.features.feed.ui.earn.state.* import com.tangem.features.feed.ui.earn.state.*
import com.tangem.features.feed.ui.feed.state.FeedListSearchBar import com.tangem.features.feed.ui.feed.state.FeedListSearchBar
@ -641,7 +642,8 @@ private fun previewEarnListItemUM(
shouldShowCustomBadge = false, shouldShowCustomBadge = false,
), ),
earnValue = stringReference("APY 6.54%"), earnValue = stringReference("APY 6.54%"),
earnType = stringReference("Yield"), earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {}, onItemClick = {},
) )

View file

@ -30,6 +30,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.* import com.tangem.core.ui.res.*
import com.tangem.domain.models.earn.EarnType
import com.tangem.features.feed.ui.earn.state.EarnListItemUM import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@Composable @Composable
@ -95,7 +96,7 @@ private fun EarnListItemV1(item: EarnListItemUM, modifier: Modifier = Modifier)
) )
SpacerH(2.dp) SpacerH(2.dp)
Text( Text(
text = item.earnType.resolveReference(), text = item.earnTypeTitle.resolveReference(),
color = TangemTheme.colors.text.tertiary, color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2, style = TangemTheme.typography.caption2,
maxLines = 1, maxLines = 1,
@ -147,6 +148,7 @@ private fun EarnListItemV2(item: EarnListItemUM, modifier: Modifier = Modifier)
ModeBlock( ModeBlock(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM), modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM),
earnType = item.earnType, earnType = item.earnType,
earnTypeTitle = item.earnTypeTitle,
) )
}, },
) )
@ -200,19 +202,24 @@ private fun TokenTitle(name: String, symbol: String, modifier: Modifier = Modifi
} }
@Composable @Composable
private fun ModeBlock(earnType: TextReference, modifier: Modifier = Modifier) { private fun ModeBlock(earnType: EarnType, earnTypeTitle: TextReference, modifier: Modifier = Modifier) {
Row( Row(
modifier = modifier, modifier = modifier,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp), horizontalArrangement = Arrangement.spacedBy(2.dp),
) { ) {
Icon( Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_staking_new_16), imageVector = ImageVector.vectorResource(
id = when (earnType) {
EarnType.STAKING -> R.drawable.ic_staking_new_16
EarnType.YIELD -> R.drawable.ic_yield_mode_16
},
),
tint = TangemTheme.colors2.markers.iconGray, tint = TangemTheme.colors2.markers.iconGray,
contentDescription = null, contentDescription = null,
) )
Text( Text(
text = earnType.resolveReference(), text = earnTypeTitle.resolveReference(),
style = TangemTheme.typography2.captionSemibold12, style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.tertiary, color = TangemTheme.colors2.text.neutral.tertiary,
) )
@ -239,7 +246,8 @@ private fun EarnListItemPreviewV1() {
shouldShowCustomBadge = false, shouldShowCustomBadge = false,
), ),
earnValue = stringReference("APY 8.50%"), earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"), earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {}, onItemClick = {},
), ),
) )
@ -257,7 +265,8 @@ private fun EarnListItemPreviewV1() {
shouldShowCustomBadge = false, shouldShowCustomBadge = false,
), ),
earnValue = stringReference("APY 8.50%"), earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"), earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {}, onItemClick = {},
), ),
) )
@ -286,7 +295,8 @@ private fun EarnListItemPreviewV2() {
shouldShowCustomBadge = false, shouldShowCustomBadge = false,
), ),
earnValue = stringReference("APY 8.50%"), earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"), earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {}, onItemClick = {},
), ),
) )
@ -305,7 +315,8 @@ private fun EarnListItemPreviewV2() {
shouldShowCustomBadge = false, shouldShowCustomBadge = false,
), ),
earnValue = stringReference("APY 8.50%"), earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"), earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {}, onItemClick = {},
), ),
) )

View file

@ -21,6 +21,7 @@ import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.* import com.tangem.core.ui.res.*
import com.tangem.domain.models.earn.EarnType
import com.tangem.features.feed.ui.earn.state.EarnListItemUM import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@Composable @Composable
@ -161,7 +162,8 @@ private fun EarnListItemPreviewV1() {
shouldShowCustomBadge = false, shouldShowCustomBadge = false,
), ),
earnValue = stringReference("APY 6.54%"), earnValue = stringReference("APY 6.54%"),
earnType = stringReference("Yield"), earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {}, onItemClick = {},
), ),
onClick = {}, onClick = {},
@ -187,7 +189,8 @@ private fun EarnListItemPreviewV2() {
shouldShowCustomBadge = false, shouldShowCustomBadge = false,
), ),
earnValue = stringReference("APY 6.54%"), earnValue = stringReference("APY 6.54%"),
earnType = stringReference("Yield"), earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {}, onItemClick = {},
), ),
onClick = {}, onClick = {},

View file

@ -3,6 +3,7 @@ package com.tangem.features.feed.ui.earn.state
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.earn.EarnType
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@Immutable @Immutable
@ -32,6 +33,7 @@ internal data class EarnListItemUM(
val tokenName: TextReference, val tokenName: TextReference,
val currencyIconState: CurrencyIconState, val currencyIconState: CurrencyIconState,
val earnValue: TextReference, val earnValue: TextReference,
val earnType: TextReference, val earnType: EarnType,
val earnTypeTitle: TextReference,
val onItemClick: () -> Unit, val onItemClick: () -> Unit,
) )

View file

@ -11,12 +11,14 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.earn.EarnType
import com.tangem.features.feed.model.market.list.state.SortByTypeUM import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.ui.earn.state.EarnListItemUM import com.tangem.features.feed.ui.earn.state.EarnListItemUM
import com.tangem.features.feed.ui.earn.state.EarnListUM import com.tangem.features.feed.ui.earn.state.EarnListUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.features.feed.ui.feed.state.* import com.tangem.features.feed.ui.feed.state.*
import kotlinx.collections.immutable.* import kotlinx.collections.immutable.*
import java.math.BigDecimal
@Suppress("MagicNumber") @Suppress("MagicNumber")
internal object FeedListPreviewDataProvider { internal object FeedListPreviewDataProvider {
@ -246,7 +248,11 @@ internal object FeedListPreviewDataProvider {
iconUrl = null, iconUrl = null,
ratingPosition = rating, ratingPosition = rating,
marketCap = marketCap, marketCap = marketCap,
price = MarketsListItemUM.Price(text = "31 285.72$"), price = MarketsListItemUM.Price(
text = "31 285.72$",
fiatPrice = BigDecimal(123123),
annotated = stringReference("31 285.72$"),
),
trendPercentText = percent, trendPercentText = percent,
trendType = trendType, trendType = trendType,
chartData = MarketChartRawData( chartData = MarketChartRawData(
@ -273,7 +279,8 @@ internal object FeedListPreviewDataProvider {
shouldShowCustomBadge = false, shouldShowCustomBadge = false,
), ),
earnValue = stringReference("APY 6.54%"), earnValue = stringReference("APY 6.54%"),
earnType = stringReference("Yield"), earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {}, onItemClick = {},
) )
}.toPersistentList() }.toPersistentList()

View file

@ -37,6 +37,7 @@ import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.core.ui.res.LocalRedesignEnabled
@ -182,6 +183,7 @@ private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier)
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
TokenPriceText( TokenPriceText(
price = state.priceText, price = state.priceText,
priceAnnotated = state.priceAnnotated,
triggerPriceChange = state.triggerPriceChange, triggerPriceChange = state.triggerPriceChange,
) )
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
@ -212,6 +214,28 @@ private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier)
@Composable @Composable
private fun TokenPriceText( private fun TokenPriceText(
price: String,
triggerPriceChange: StateEvent<PriceChangeType>,
priceAnnotated: TextReference,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
TokenPriceTextV2(
priceAnnotated = priceAnnotated,
triggerPriceChange = triggerPriceChange,
modifier = modifier,
)
} else {
TokenPriceTextV1(
price = price,
triggerPriceChange = triggerPriceChange,
modifier = modifier,
)
}
}
@Composable
private fun TokenPriceTextV1(
price: String, price: String,
triggerPriceChange: StateEvent<PriceChangeType>, triggerPriceChange: StateEvent<PriceChangeType>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -244,6 +268,40 @@ private fun TokenPriceText(
) )
} }
@Composable
private fun TokenPriceTextV2(
priceAnnotated: TextReference,
triggerPriceChange: StateEvent<PriceChangeType>,
modifier: Modifier = Modifier,
) {
val growColor = TangemTheme.colors2.graphic.status.accent
val fallColor = TangemTheme.colors2.graphic.status.warning
val generalColor = TangemTheme.colors2.text.neutral.primary
val color = remember(generalColor) { Animatable(generalColor) }
EventEffect(triggerPriceChange) { priceChangeType ->
val nextColor = when (priceChangeType) {
PriceChangeType.UP,
-> growColor
PriceChangeType.DOWN -> fallColor
PriceChangeType.NEUTRAL -> return@EventEffect
}
color.animateTo(nextColor, snap())
color.animateTo(generalColor, tween(durationMillis = 500))
}
Text(
text = priceAnnotated.resolveAnnotatedReference(),
modifier = modifier,
color = color.value,
autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography2.headingBold34.fontSize),
maxLines = 1,
style = TangemTheme.typography2.headingBold34,
)
}
@Composable @Composable
private fun IntervalSelector( private fun IntervalSelector(
trendInterval: PriceChangeInterval, trendInterval: PriceChangeInterval,

View file

@ -156,12 +156,42 @@ internal fun FDVCard(item: InfoPointUMV2.FullyDilutedValuation) {
modifier = Modifier modifier = Modifier
.heightIn(120.dp) .heightIn(120.dp)
.fillMaxWidth(), .fillMaxWidth(),
title = { MetricValueText(value = item.value) }, title = {
if (item.fullyDilutedValuationChange24 != null) {
Row {
MetricValueText(value = item.fullyDilutedValuationChange24)
Text(
modifier = Modifier.padding(TangemTheme.dimens2.x1),
text = stringResourceSafe(R.string.markets_token_details_trading_interval),
style = TangemTheme.typography2.captionSemibold11,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
} else {
MetricValueText(value = item.value)
}
},
content = { content = {
InformationTextBlock( Column {
text = resourceReference(R.string.markets_token_details_fully_diluted_valuation), if (item.fullyDilutedValuationChange24 != null) {
onInfoClick = item.onInfoClick, Text(
) text = item.value?.resolveReference()
?: stringResourceSafe(R.string.token_market_metrics_no_data),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SpacerH(4.dp)
}
InformationTextBlock(
text = resourceReference(R.string.markets_token_details_fully_diluted_valuation),
onInfoClick = item.onInfoClick,
)
}
}, },
) )
} }
@ -445,6 +475,7 @@ private fun MetricsCardsPreview() {
FDVCard( FDVCard(
item = InfoPointUMV2.FullyDilutedValuation( item = InfoPointUMV2.FullyDilutedValuation(
value = stringReference("$ 1.5 T"), value = stringReference("$ 1.5 T"),
fullyDilutedValuationChange24 = stringReference("$ 2.44 M in total"),
onInfoClick = {}, onInfoClick = {},
), ),
) )

View file

@ -50,6 +50,7 @@ internal object MarketsTokenDetailsPreview {
onScroll = {}, onScroll = {},
), ),
onShareClick = {}, onShareClick = {},
priceAnnotated = stringReference("$0.00000000324"),
) )
val contentState = MarketsTokenDetailsUM( val contentState = MarketsTokenDetailsUM(
@ -144,5 +145,6 @@ internal object MarketsTokenDetailsPreview {
onScroll = {}, onScroll = {},
), ),
onShareClick = {}, onShareClick = {},
priceAnnotated = stringReference("$0.00000000324"),
) )
} }

View file

@ -14,6 +14,7 @@ import java.math.BigDecimal
internal data class MarketsTokenDetailsUM( internal data class MarketsTokenDetailsUM(
val tokenName: String, val tokenName: String,
val priceText: String, val priceText: String,
val priceAnnotated: TextReference,
val iconUrl: String?, val iconUrl: String?,
val dateTimeText: TextReference, val dateTimeText: TextReference,
val priceChangePercentText: String?, val priceChangePercentText: String?,

View file

@ -38,6 +38,7 @@ internal sealed interface InfoPointUMV2 {
@Immutable @Immutable
data class FullyDilutedValuation( data class FullyDilutedValuation(
val value: TextReference?, val value: TextReference?,
val fullyDilutedValuationChange24: TextReference?,
val onInfoClick: () -> Unit, val onInfoClick: () -> Unit,
) : InfoPointUMV2 ) : InfoPointUMV2

View file

@ -25,6 +25,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.tangem.common.ui.markets.MarketsListItem import com.tangem.common.ui.markets.MarketsListItem
import com.tangem.common.ui.markets.MarketsListItemPlaceholder import com.tangem.common.ui.markets.MarketsListItemPlaceholder
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.ui.R import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW import com.tangem.core.ui.components.SpacerW
@ -133,7 +134,10 @@ private fun LazyListScope.searchHistoryItems(
} }
} }
private fun LazyListScope.searchResultsItems(results: SearchContentUM.Results, onResultMarketTokenClick: () -> Unit) { private fun LazyListScope.searchResultsItems(
results: SearchContentUM.Results,
onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
) {
if (results.userAssets.isNotEmpty()) { if (results.userAssets.isNotEmpty()) {
item(key = "header_portfolio") { item(key = "header_portfolio") {
SectionHeader(title = stringResourceSafe(R.string.markets_search_portfolio_header)) SectionHeader(title = stringResourceSafe(R.string.markets_search_portfolio_header))
@ -163,7 +167,7 @@ private fun LazyListScope.searchResultsItems(results: SearchContentUM.Results, o
private fun LazyListScope.marketSearchResultItems( private fun LazyListScope.marketSearchResultItems(
market: MarketSearchResultUM.Content, market: MarketSearchResultUM.Content,
hasUserAssetsSection: Boolean, hasUserAssetsSection: Boolean,
onResultMarketTokenClick: () -> Unit, onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
) { ) {
if (hasUserAssetsSection) { if (hasUserAssetsSection) {
item(key = "spacer_between_sections") { item(key = "spacer_between_sections") {
@ -185,7 +189,7 @@ private fun LazyListScope.marketSearchResultItems(
shape = RoundedCornerShape(TangemTheme.dimens2.x5), shape = RoundedCornerShape(TangemTheme.dimens2.x5),
), ),
model = token, model = token,
onClick = onResultMarketTokenClick, onClick = { onResultMarketTokenClick(token) },
) )
} }
if (market.shouldShowUnder100kNotification) { if (market.shouldShowUnder100kNotification) {

View file

@ -25,6 +25,7 @@ import com.tangem.features.feed.ui.search.state.*
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
/** Labeled UI state for [SearchContent] previews. */ /** Labeled UI state for [SearchContent] previews. */
internal data class SearchContentPreviewScenario( internal data class SearchContentPreviewScenario(
@ -192,7 +193,11 @@ internal object SearchContentPreviewFixtures {
iconUrl = null, iconUrl = null,
ratingPosition = rating, ratingPosition = rating,
marketCap = marketCap, marketCap = marketCap,
price = MarketsListItemUM.Price(text = priceText), price = MarketsListItemUM.Price(
text = priceText,
annotated = stringReference(priceText),
fiatPrice = BigDecimal(123123),
),
trendPercentText = trendText, trendPercentText = trendText,
trendType = trend, trendType = trend,
chartData = chart, chartData = chart,
@ -384,7 +389,7 @@ private val SearchContentPreviewCallbacks = SearchCallbacks(
onLoadMore = {}, onLoadMore = {},
onClearHintsClick = {}, onClearHintsClick = {},
onTextHintClick = { _ -> }, onTextHintClick = { _ -> },
onResultMarketTokenClick = {}, onResultMarketTokenClick = { _ -> },
) )
/** All [SearchContentPreviewScenario] values for the Preview Parameter dropdown in Android Studio. */ /** All [SearchContentPreviewScenario] values for the Preview Parameter dropdown in Android Studio. */

View file

@ -1,8 +1,10 @@
package com.tangem.features.feed.ui.search.state package com.tangem.features.feed.ui.search.state
import com.tangem.common.ui.markets.models.MarketsListItemUM
internal data class SearchCallbacks( internal data class SearchCallbacks(
val onLoadMore: () -> Unit, val onLoadMore: () -> Unit,
val onClearHintsClick: () -> Unit, val onClearHintsClick: () -> Unit,
val onTextHintClick: (hint: String) -> Unit, val onTextHintClick: (hint: String) -> Unit,
val onResultMarketTokenClick: () -> Unit, val onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
) )

View file

@ -81,6 +81,7 @@ dependencies {
/* Common */ /* Common */
implementation(projects.common.ui) implementation(projects.common.ui)
implementation(projects.common.uiMarkets)
implementation(projects.common.uiCharts) implementation(projects.common.uiCharts)
implementation(projects.common.routing) implementation(projects.common.routing)

View file

@ -6,6 +6,7 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.charts.state.MarketChartData import com.tangem.common.ui.charts.state.MarketChartData
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.sorted import com.tangem.common.ui.charts.state.sorted
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
@ -62,6 +63,8 @@ internal class TokenMarketBlockModel @Inject constructor(
TokenMarketBlockUM( TokenMarketBlockUM(
currencySymbol = params.cryptoCurrency.symbol, currencySymbol = params.cryptoCurrency.symbol,
currentPrice = null, currentPrice = null,
currentPriceValue = null,
priceAnnotated = null,
h24Percent = null, h24Percent = null,
priceChangeType = PriceChangeType.NEUTRAL, priceChangeType = PriceChangeType.NEUTRAL,
chartData = null, chartData = null,
@ -86,15 +89,21 @@ internal class TokenMarketBlockModel @Inject constructor(
h24Percent = res.priceChange, h24Percent = res.priceChange,
) )
val appCurrency = currentAppCurrency.value
state.value = state.value.copy( state.value = state.value.copy(
currentPrice = res.fiatRate.format { currentPrice = res.fiatRate.format {
fiat( fiat(
// TODO get currency from quotes use case [REDACTED_TASK_KEY] // TODO get currency from quotes use case [REDACTED_TASK_KEY]
fiatCurrencyCode = currentAppCurrency.value.code, fiatCurrencyCode = appCurrency.code,
// TODO get currency from quotes use case [REDACTED_TASK_KEY] // TODO get currency from quotes use case [REDACTED_TASK_KEY]
fiatCurrencySymbol = currentAppCurrency.value.symbol, fiatCurrencySymbol = appCurrency.symbol,
).price() ).price()
}, },
currentPriceValue = res.fiatRate,
priceAnnotated = res.fiatRate.toMarketsListItemPriceAnnotated(
appCurrencyCode = appCurrency.code,
appCurrencySymbol = appCurrency.symbol,
),
h24Percent = res.priceChange.format { percent() }, h24Percent = res.priceChange.format { percent() },
priceChangeType = PriceChangeType.fromBigDecimal(res.priceChange), priceChangeType = PriceChangeType.fromBigDecimal(res.priceChange),
) )

View file

@ -20,6 +20,8 @@ import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
@ -27,6 +29,7 @@ import com.tangem.features.markets.impl.R
import com.tangem.features.markets.token.block.impl.model.formatter.toChartType import com.tangem.features.markets.token.block.impl.model.formatter.toChartType
import com.tangem.features.markets.token.block.impl.ui.state.TokenMarketBlockUM import com.tangem.features.markets.token.block.impl.ui.state.TokenMarketBlockUM
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
import kotlin.random.Random import kotlin.random.Random
@Composable @Composable
@ -47,6 +50,8 @@ internal fun TokenMarketBlockLegacy(state: TokenMarketBlockUM, modifier: Modifie
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
symbol = state.currencySymbol, symbol = state.currencySymbol,
priceText = state.currentPrice, priceText = state.currentPrice,
priceValue = state.currentPriceValue,
priceAnnotated = state.priceAnnotated,
percentText = state.h24Percent, percentText = state.h24Percent,
type = state.priceChangeType, type = state.priceChangeType,
) )
@ -61,11 +66,14 @@ internal fun TokenMarketBlockLegacy(state: TokenMarketBlockUM, modifier: Modifie
) )
} }
@Suppress("LongParameterList")
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
private fun LeftSide( private fun LeftSide(
symbol: String, symbol: String,
priceText: String?, priceText: String?,
priceValue: BigDecimal?,
priceAnnotated: TextReference?,
percentText: String?, percentText: String?,
type: PriceChangeType, type: PriceChangeType,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -88,6 +96,8 @@ private fun LeftSide(
modifier = Modifier.alignByBaseline(), modifier = Modifier.alignByBaseline(),
price = priceText, price = priceText,
priceChangeType = type, priceChangeType = type,
priceValue = priceValue ?: BigDecimal.ZERO,
priceAnnotated = priceAnnotated ?: TextReference.EMPTY,
) )
Row( Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
@ -181,6 +191,8 @@ private fun Preview() {
priceChangeType = PriceChangeType.UP, priceChangeType = PriceChangeType.UP,
chartData = data, chartData = data,
onClick = {}, onClick = {},
currentPriceValue = BigDecimal(1234),
priceAnnotated = stringReference("0,5$"),
) )
TangemThemePreview { TangemThemePreview {

View file

@ -2,10 +2,14 @@ package com.tangem.features.markets.token.block.impl.ui.state
import com.tangem.common.ui.charts.state.MarketChartRawData import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import java.math.BigDecimal
internal data class TokenMarketBlockUM( internal data class TokenMarketBlockUM(
val currencySymbol: String, val currencySymbol: String,
val currentPrice: String?, val currentPrice: String?,
val currentPriceValue: BigDecimal?,
val priceAnnotated: TextReference?,
val h24Percent: String?, val h24Percent: String?,
val priceChangeType: PriceChangeType, val priceChangeType: PriceChangeType,
val chartData: MarketChartRawData?, val chartData: MarketChartRawData?,

View file

@ -5,6 +5,7 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.sorted import com.tangem.common.ui.charts.state.sorted
import com.tangem.common.ui.markets.models.MarketsListItemUM import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
import com.tangem.core.ui.R import com.tangem.core.ui.R
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
@ -119,7 +120,12 @@ internal class SwapMarketsTokenItemConverter(
return MarketsListItemUM.Price( return MarketsListItemUM.Price(
text = priceText, text = priceText,
annotated = tokenQuotesShort.currentPrice.toMarketsListItemPriceAnnotated(
appCurrencyCode = appCurrency.code,
appCurrencySymbol = appCurrency.symbol,
),
changeType = changeType, changeType = changeType,
fiatPrice = tokenQuotesShort.currentPrice,
) )
} }

View file

@ -5,6 +5,7 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
import com.tangem.common.ui.charts.state.sorted import com.tangem.common.ui.charts.state.sorted
import com.tangem.common.ui.markets.models.MarketsListItemUM import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
@ -119,7 +120,12 @@ internal class SwapMarketsTokenItemConverter(
return MarketsListItemUM.Price( return MarketsListItemUM.Price(
text = priceText, text = priceText,
annotated = tokenQuotesShort.currentPrice.toMarketsListItemPriceAnnotated(
appCurrencyCode = appCurrency.code,
appCurrencySymbol = appCurrency.symbol,
),
changeType = changeType, changeType = changeType,
fiatPrice = tokenQuotesShort.currentPrice,
) )
} }

View file

@ -19,6 +19,7 @@ import com.tangem.feature.swap.models.market.state.SwapMarketState
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
internal object SwapSelectTokenPreviewProvider { internal object SwapSelectTokenPreviewProvider {
@ -205,7 +206,11 @@ internal object SwapSelectTokenPreviewProvider {
iconUrl = iconUrl, iconUrl = iconUrl,
ratingPosition = ratingPosition, ratingPosition = ratingPosition,
marketCap = marketCap, marketCap = marketCap,
price = MarketsListItemUM.Price(text = "31 285.72$"), price = MarketsListItemUM.Price(
text = "31 285.72$",
annotated = stringReference("31 285.72$"),
fiatPrice = BigDecimal("123123"),
),
trendPercentText = "12.43%", trendPercentText = "12.43%",
trendType = trendType, trendType = trendType,
chartData = chartData, chartData = chartData,