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

@ -35,10 +35,11 @@ internal class EarnTokenWithCurrencyToListItemUMConverter(
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.YIELD -> TextReference.Res(R.string.common_yield_mode)
},
earnType = value.earnToken.type,
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.sorted
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.resourceReference
import com.tangem.core.ui.extensions.wrappedList
@ -110,7 +111,12 @@ internal class MarketsTokenItemConverter(
return MarketsListItemUM.Price(
text = priceText,
annotated = tokenQuotesShort.currentPrice.toMarketsListItemPriceAnnotated(
appCurrencyCode = appCurrency.code,
appCurrencySymbol = appCurrency.symbol,
),
changeType = changeType,
fiatPrice = tokenQuotesShort.currentPrice,
)
}

View file

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

View file

@ -1,10 +1,7 @@
package com.tangem.features.feed.model.market.details.converter
import androidx.compose.runtime.Stable
import com.tangem.core.ui.extensions.combinedReference
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.extensions.*
import com.tangem.core.ui.format.bigdecimal.compact
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
@ -188,6 +185,7 @@ internal class MetricsConverter(
formatArgs = wrappedList(formatted),
)
},
fullyDilutedValuationChange24 = fullyDilutedValuationChange24?.convertChange(),
onInfoClick = {
onInfoClick(
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")
private fun getLiquidity(volume24h: BigDecimal?, marketCap: BigDecimal?): Float? {
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
import androidx.compose.ui.text.SpanStyle
import com.tangem.common.ui.charts.state.MarketChartLook
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.getFiatPriceAmountWithScale
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.*
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.TokenQuotes
import java.math.BigDecimal
@ -73,4 +75,14 @@ internal fun PriceChangeType.toChartType(): MarketChartLook.Type {
PriceChangeType.DOWN -> MarketChartLook.Type.Falling
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,
).price()
},
priceAnnotated = newQuotes.currentPrice.toMarketsTokenDetailsPriceAnnotated(currentAppCurrency()),
priceChangePercentText = newQuotes.getFormattedPercentByInterval(
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.sorted
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.format.bigdecimal.*
import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
@ -68,7 +69,12 @@ internal class RelatedTokenConverter(private val appCurrency: AppCurrency) :
return MarketsListItemUM.Price(
text = priceText,
annotated = tokenInfo.quotes.currentPrice.toMarketsListItemPriceAnnotated(
appCurrencyCode = appCurrency.code,
appCurrencySymbol = appCurrency.symbol,
),
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.model.Model
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.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.GetTokenPriceChartUseCase
import com.tangem.domain.markets.PriceChangeInterval
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.GetSearchResultsUseCase
import com.tangem.domain.search.usecase.SaveRecentSearchTokenUseCase
import com.tangem.domain.search.usecase.SaveSearchQueryUseCase
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.SortByTypeUM
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.transformers.*
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.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -45,7 +56,9 @@ internal class SearchModel @Inject constructor(
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getSearchResultsUseCase: GetSearchResultsUseCase,
private val saveSearchQueryUseCase: SaveSearchQueryUseCase,
private val saveRecentSearchTokenUseCase: SaveRecentSearchTokenUseCase,
private val clearSearchHistoryUseCase: ClearSearchHistoryUseCase,
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
private val stateController: SearchStateController,
) : Model() {
@ -64,6 +77,18 @@ internal class SearchModel @Inject constructor(
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 {
MarketsListBatchFlowManager(
getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase,
@ -108,8 +133,15 @@ internal class SearchModel @Inject constructor(
stateController.update(UpdateSearchBarQueryTransformer(text))
}
fun onResultMarketTokenClick() {
fun onResultMarketTokenClick(item: MarketsListItemUM) {
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)
}
}
@ -263,31 +295,43 @@ internal class SearchModel @Inject constructor(
TextHintItemUM(text = hint.text)
}.toImmutableList()
val appCurrency = currentAppCurrency.value
val recentTokens = searchResult.recentTokens.map { token ->
token.toMarketsListItemUM()
recentSearchTokenToMarketsListItemConverter.convert(
RecentSearchTokenWithAppCurrency(token = token, appCurrency = appCurrency),
)
}.toImmutableList()
stateController.update(UpdateHistoryTransformer(textHints, recentTokens))
loadRecentTokenCharts(recentTokens, appCurrency)
}
}.saveIn(searchResultsJob)
}
private fun RecentSearchToken.toMarketsListItemUM(): MarketsListItemUM {
return MarketsListItemUM(
id = id,
name = name,
currencySymbol = symbol,
iconUrl = imageUrl,
ratingPosition = null,
marketCap = null,
price = MarketsListItemUM.Price(text = ""),
trendPercentText = "",
trendType = PriceChangeType.NEUTRAL,
chartData = null,
isUnder100kMarketCap = false,
stakingRate = null,
updateTimestamp = timestamp,
)
private suspend fun loadRecentTokenCharts(tokens: ImmutableList<MarketsListItemUM>, appCurrency: AppCurrency) {
coroutineScope {
tokens.map { token ->
async(dispatchers.io) {
val chart = getTokenPriceChartUseCase(
appCurrency = appCurrency,
interval = PriceChangeInterval.H24,
tokenId = token.id,
tokenSymbol = token.currencySymbol,
preview = true,
).getOrElse { return@async }
val chartData = priceAndTimePointValuesConverter.convert(
MarketChartData.Data(
y = chart.priceY.toImmutableList(),
x = chart.timeStamps.map { it.toBigDecimal() }.toImmutableList(),
).sorted(),
)
stateController.update(UpdateRecentTokenChartTransformer(token.id, chartData))
}
}.awaitAll()
}
}
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.extensions.*
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.state.*
import com.tangem.features.feed.ui.feed.state.FeedListSearchBar
@ -641,7 +642,8 @@ private fun previewEarnListItemUM(
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 6.54%"),
earnType = stringReference("Yield"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
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.stringReference
import com.tangem.core.ui.res.*
import com.tangem.domain.models.earn.EarnType
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@Composable
@ -95,7 +96,7 @@ private fun EarnListItemV1(item: EarnListItemUM, modifier: Modifier = Modifier)
)
SpacerH(2.dp)
Text(
text = item.earnType.resolveReference(),
text = item.earnTypeTitle.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
maxLines = 1,
@ -147,6 +148,7 @@ private fun EarnListItemV2(item: EarnListItemUM, modifier: Modifier = Modifier)
ModeBlock(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM),
earnType = item.earnType,
earnTypeTitle = item.earnTypeTitle,
)
},
)
@ -200,19 +202,24 @@ private fun TokenTitle(name: String, symbol: String, modifier: Modifier = Modifi
}
@Composable
private fun ModeBlock(earnType: TextReference, modifier: Modifier = Modifier) {
private fun ModeBlock(earnType: EarnType, earnTypeTitle: TextReference, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
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,
contentDescription = null,
)
Text(
text = earnType.resolveReference(),
text = earnTypeTitle.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.tertiary,
)
@ -239,7 +246,8 @@ private fun EarnListItemPreviewV1() {
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {},
),
)
@ -257,7 +265,8 @@ private fun EarnListItemPreviewV1() {
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {},
),
)
@ -286,7 +295,8 @@ private fun EarnListItemPreviewV2() {
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {},
),
)
@ -305,7 +315,8 @@ private fun EarnListItemPreviewV2() {
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 8.50%"),
earnType = stringReference("Yield"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
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.stringReference
import com.tangem.core.ui.res.*
import com.tangem.domain.models.earn.EarnType
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
@Composable
@ -161,7 +162,8 @@ private fun EarnListItemPreviewV1() {
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 6.54%"),
earnType = stringReference("Yield"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {},
),
onClick = {},
@ -187,7 +189,8 @@ private fun EarnListItemPreviewV2() {
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 6.54%"),
earnType = stringReference("Yield"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {},
),
onClick = {},

View file

@ -3,6 +3,7 @@ package com.tangem.features.feed.ui.earn.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.earn.EarnType
import kotlinx.collections.immutable.ImmutableList
@Immutable
@ -32,6 +33,7 @@ internal data class EarnListItemUM(
val tokenName: TextReference,
val currencyIconState: CurrencyIconState,
val earnValue: TextReference,
val earnType: TextReference,
val earnType: EarnType,
val earnTypeTitle: TextReference,
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.res.TangemColorPalette
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.ui.earn.state.EarnListItemUM
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.state.*
import kotlinx.collections.immutable.*
import java.math.BigDecimal
@Suppress("MagicNumber")
internal object FeedListPreviewDataProvider {
@ -246,7 +248,11 @@ internal object FeedListPreviewDataProvider {
iconUrl = null,
ratingPosition = rating,
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,
trendType = trendType,
chartData = MarketChartRawData(
@ -273,7 +279,8 @@ internal object FeedListPreviewDataProvider {
shouldShowCustomBadge = false,
),
earnValue = stringReference("APY 6.54%"),
earnType = stringReference("Yield"),
earnTypeTitle = stringReference("Yield"),
earnType = EarnType.YIELD,
onItemClick = {},
)
}.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.StateEvent
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.resourceReference
import com.tangem.core.ui.res.LocalRedesignEnabled
@ -182,6 +183,7 @@ private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier)
Column(modifier = Modifier.weight(1f)) {
TokenPriceText(
price = state.priceText,
priceAnnotated = state.priceAnnotated,
triggerPriceChange = state.triggerPriceChange,
)
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
@ -212,6 +214,28 @@ private fun Header(state: MarketsTokenDetailsUM, modifier: Modifier = Modifier)
@Composable
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,
triggerPriceChange: StateEvent<PriceChangeType>,
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
private fun IntervalSelector(
trendInterval: PriceChangeInterval,

View file

@ -156,12 +156,42 @@ internal fun FDVCard(item: InfoPointUMV2.FullyDilutedValuation) {
modifier = Modifier
.heightIn(120.dp)
.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 = {
InformationTextBlock(
text = resourceReference(R.string.markets_token_details_fully_diluted_valuation),
onInfoClick = item.onInfoClick,
)
Column {
if (item.fullyDilutedValuationChange24 != null) {
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(
item = InfoPointUMV2.FullyDilutedValuation(
value = stringReference("$ 1.5 T"),
fullyDilutedValuationChange24 = stringReference("$ 2.44 M in total"),
onInfoClick = {},
),
)

View file

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

View file

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

View file

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

View file

@ -25,6 +25,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.markets.MarketsListItem
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.components.SpacerH
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()) {
item(key = "header_portfolio") {
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(
market: MarketSearchResultUM.Content,
hasUserAssetsSection: Boolean,
onResultMarketTokenClick: () -> Unit,
onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
) {
if (hasUserAssetsSection) {
item(key = "spacer_between_sections") {
@ -185,7 +189,7 @@ private fun LazyListScope.marketSearchResultItems(
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
),
model = token,
onClick = onResultMarketTokenClick,
onClick = { onResultMarketTokenClick(token) },
)
}
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.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
/** Labeled UI state for [SearchContent] previews. */
internal data class SearchContentPreviewScenario(
@ -192,7 +193,11 @@ internal object SearchContentPreviewFixtures {
iconUrl = null,
ratingPosition = rating,
marketCap = marketCap,
price = MarketsListItemUM.Price(text = priceText),
price = MarketsListItemUM.Price(
text = priceText,
annotated = stringReference(priceText),
fiatPrice = BigDecimal(123123),
),
trendPercentText = trendText,
trendType = trend,
chartData = chart,
@ -384,7 +389,7 @@ private val SearchContentPreviewCallbacks = SearchCallbacks(
onLoadMore = {},
onClearHintsClick = {},
onTextHintClick = { _ -> },
onResultMarketTokenClick = {},
onResultMarketTokenClick = { _ -> },
)
/** 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
import com.tangem.common.ui.markets.models.MarketsListItemUM
internal data class SearchCallbacks(
val onLoadMore: () -> Unit,
val onClearHintsClick: () -> Unit,
val onTextHintClick: (hint: String) -> Unit,
val onResultMarketTokenClick: () -> Unit,
val onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
)