Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-17 08:46:19 +01:00
parent ba1b0e1f84
commit 1470d4b08d
18 changed files with 1201 additions and 64 deletions

View file

@ -7,7 +7,8 @@ import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
@ -21,6 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import com.tangem.core.ui.ds.progress.TangemLinearProgressIndicatorWithDot
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlin.math.abs
@ -278,6 +280,14 @@ private fun LinearProgressIndicator_Preview() {
color = TangemTheme.colors.icon.primary1,
backgroundColor = TangemTheme.colors.background.tertiary,
)
TangemLinearProgressIndicatorWithDot(
progress = { 1f },
modifier = Modifier
.fillMaxWidth()
.height(6.dp),
dotColor = TangemTheme.colors.icon.primary1,
backgroundColor = TangemTheme.colors.background.tertiary,
)
TangemLinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
color = TangemTheme.colors.icon.primary1,

View file

@ -0,0 +1,159 @@
package com.tangem.core.ui.ds.progress
import android.content.res.Configuration
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.progressBarRangeInfo
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.progressbar.increaseSemanticsBounds
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import kotlin.math.abs
/**
* Progress indicator with a dot that reflects the current progress position.
* The dot is drawn on top of the background line. Dot diameter equals the track height.
* At progress 0 the dot's left edge aligns with the track's left edge;
* at progress 1 the dot's right edge aligns with the track's right edge.
*
* @param progress The progress of this indicator, where 0.0 represents no progress and 1.0
* represents full progress. Values outside of this range are coerced into the range.
* @param modifier the [Modifier] to be applied to this progress indicator
* @param dotColor The color of the progress dot.
* @param backgroundColor The color of the background track.
* @param strokeCap stroke cap to use for the ends of the background track
*/
@Composable
fun TangemLinearProgressIndicatorWithDot(
progress: () -> Float,
dotColor: Color,
backgroundColor: Color,
modifier: Modifier = Modifier,
strokeCap: StrokeCap = StrokeCap.Round,
) {
val coercedProgress = { progress().coerceIn(0f, 1f) }
Canvas(
modifier
.increaseSemanticsBounds()
.semantics(mergeDescendants = true) {
progressBarRangeInfo = ProgressBarRangeInfo(coercedProgress(), 0f..1f)
},
) {
val strokeWidth = size.height
drawLinearIndicatorBackground(backgroundColor, strokeWidth, strokeCap)
drawLinearProgressDot(
progress = coercedProgress(),
color = dotColor,
dotDiameter = strokeWidth,
)
}
}
private fun DrawScope.drawLinearIndicatorBackground(color: Color, strokeWidth: Float, strokeCap: StrokeCap) =
drawLinearIndicator(
startFraction = 0f,
endFraction = 1f,
color = color,
strokeWidth = strokeWidth,
strokeCap = strokeCap,
)
private fun DrawScope.drawLinearProgressDot(progress: Float, color: Color, dotDiameter: Float) {
val width = size.width
val radius = dotDiameter / 2
val yOffset = size.height / 2
val isLtr = layoutDirection == LayoutDirection.Ltr
val centerX = if (isLtr) {
radius + progress * (width - dotDiameter)
} else {
width - radius - progress * (width - dotDiameter)
}
drawCircle(
color = color,
radius = radius,
center = Offset(centerX, yOffset),
)
}
private fun DrawScope.drawLinearIndicator(
startFraction: Float,
endFraction: Float,
color: Color,
strokeWidth: Float,
strokeCap: StrokeCap,
) {
val width = size.width
val height = size.height
// Start drawing from the vertical center of the stroke
val yOffset = height / 2
val isLtr = layoutDirection == LayoutDirection.Ltr
val barStart = (if (isLtr) startFraction else 1f - endFraction) * width
val barEnd = (if (isLtr) endFraction else 1f - startFraction) * width
// if there isn't enough space to draw the stroke caps, fall back to StrokeCap.Butt
if (strokeCap == StrokeCap.Butt || height > width) {
// Progress line
drawLine(
color = color,
start = Offset(barStart, yOffset),
end = Offset(barEnd, yOffset),
strokeWidth = strokeWidth,
)
} else {
// need to adjust barStart and barEnd for the stroke caps
val strokeCapOffset = strokeWidth / 2
val coerceRange = strokeCapOffset..width - strokeCapOffset
val adjustedBarStart = barStart.coerceIn(coerceRange)
val adjustedBarEnd = barEnd.coerceIn(coerceRange)
if (abs(endFraction - startFraction) > 0) {
// Progress line
drawLine(
color = color,
start = Offset(adjustedBarStart, yOffset),
end = Offset(adjustedBarEnd, yOffset),
strokeWidth = strokeWidth,
cap = strokeCap,
)
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun LinearProgressIndicator_Preview() {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.background(TangemTheme.colors.background.secondary)
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
TangemLinearProgressIndicatorWithDot(
progress = { 1f },
modifier = Modifier
.fillMaxWidth()
.height(6.dp),
dotColor = TangemTheme.colors2.fill.status.accent,
backgroundColor = TangemTheme.colors2.graphic.neutral.primaryInvertedConstant,
)
}
}
}
// endregion

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -16,6 +16,7 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.marketprice.PriceChangeType
@ -77,6 +78,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getUserCountryUseCase: GetUserCountryUseCase,
paramsContainer: ParamsContainer,
designFeatureToggles: DesignFeatureToggles,
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
@ -147,6 +149,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
needApplyFCARestrictions = Provider {
userCountry.needApplyFCARestrictions()
},
isRedesignEnabled = designFeatureToggles.isRedesignEnabled,
// ==================
)

View file

@ -2,6 +2,8 @@ package com.tangem.features.feed.model.market.details.converter
import androidx.compose.runtime.Stable
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.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
@ -9,20 +11,23 @@ import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.state.InfoBottomSheetContent
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUM
import com.tangem.features.feed.ui.market.detailed.state.MetricsUM
import com.tangem.features.feed.ui.market.detailed.state.*
import com.tangem.utils.Provider
import com.tangem.utils.StringsSigns
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
import java.math.RoundingMode
@Suppress("LargeClass")
@Stable
internal class MetricsConverter(
private val appCurrency: Provider<AppCurrency>,
private val tokenSymbol: String,
private val onInfoClick: (InfoBottomSheetContent) -> Unit,
private val isRedesignEnabled: Boolean,
) : Converter<TokenMarketInfo.Metrics, MetricsUM> {
@Suppress("LongMethod")
@ -115,10 +120,120 @@ internal class MetricsConverter(
},
),
),
metricsV2 = if (isRedesignEnabled) {
convertToMetricsV2UM(value)
} else {
null
},
)
}
}
@Suppress("LongMethod", "NestedScopeFunctions")
private fun convertToMetricsV2UM(value: TokenMarketInfo.Metrics): MetricsV2UM {
val infoPoints = with(value) {
val liquidity = getLiquidity(value.volume24h, value.marketCap)
persistentListOf(
InfoPointUMV2.MarketCap(
capitalizationValue = stringReference(marketCap.formatAmount()),
onInfoClick = {
onInfoClick(
InfoBottomSheetContent(
title = resourceReference(
R.string.markets_token_details_market_capitalization_full,
),
body = resourceReference(
R.string.markets_token_details_market_capitalization_description,
),
),
)
},
),
InfoPointUMV2.TradingVolume(
tradingValue = stringReference(volume24h.formatAmount()),
liquidity = liquidity,
trendingVolumeLiquidityType = getTrendingVolumeLiquidityType(liquidity),
onInfoClick = {
onInfoClick(
InfoBottomSheetContent(
title = resourceReference(R.string.markets_token_details_trading_volume_full),
body = resourceReference(
R.string.markets_token_details_trading_volume_24h_description,
),
),
)
},
),
InfoPointUMV2.MarketPosition(
position = marketRating?.toString() ?: StringsSigns.DASH_SIGN,
rangeValue = getMarketRatingRangeValue(marketRating),
marketRatingType = getMarketRatingType(marketRating),
onInfoClick = {
onInfoClick(
InfoBottomSheetContent(
title = resourceReference(R.string.markets_token_details_market_rating_full),
body = resourceReference(R.string.markets_token_details_market_rating_description),
),
)
},
),
InfoPointUMV2.FullyDilutedValuation(
value = resourceReference(
R.string.markets_token_details_valuation_value_in_total,
wrappedList(fullyDilutedValuation.formatAmount()),
),
onInfoClick = {
onInfoClick(
InfoBottomSheetContent(
title = resourceReference(
R.string.markets_token_details_fully_diluted_valuation_full,
),
body = resourceReference(
R.string.markets_token_details_fully_diluted_valuation_description,
),
),
)
},
),
InfoPointUMV2.CirculatingSupply(
currentValue = stringReference(circulatingSupply.formatAmount(crypto = true)),
maxValue = maxSupply?.let { supply ->
if (supply > BigDecimal.ZERO) {
stringReference(supply.formatMaxSupply())
} else {
null
}
},
fillValue = getCirculatingSupplyFillValue(circulatingSupply, maxSupply),
onInfoClick = {
onInfoClick(
InfoBottomSheetContent(
title = resourceReference(R.string.markets_token_details_max_supply_full),
body = resourceReference(R.string.markets_token_details_total_supply_description),
),
)
},
),
)
}
return buildMetricsV2UM(infoPoints)
}
private fun buildMetricsV2UM(infoPoints: ImmutableList<InfoPointUMV2>): MetricsV2UM {
val rows = infoPoints.chunked(size = 2)
.map { chunk ->
MetricsV2UM.Row(
first = chunk.first(),
second = chunk.getOrNull(1),
)
}
return MetricsV2UM(
rows = rows.toImmutableList(),
)
}
private fun BigDecimal?.formatMaxSupply(): String {
when (this) {
null -> return StringsSigns.DASH_SIGN
@ -149,4 +264,92 @@ internal class MetricsConverter(
}
}
}
@Suppress("MagicNumber")
private fun getLiquidity(volume24h: BigDecimal?, marketCap: BigDecimal?): Float {
if (volume24h == null || marketCap == null || marketCap == BigDecimal.ZERO) return 0f
val ratio = volume24h
.divide(marketCap, 6, RoundingMode.HALF_UP)
.toFloat()
return ratio.coerceIn(0f, 1f)
}
@Suppress("MagicNumber")
private fun getTrendingVolumeLiquidityType(liquidity: Float): TrendingVolumeLiquidityType {
return when {
liquidity >= 0.5f -> TrendingVolumeLiquidityType.HIGH
liquidity in 0.2f..<0.5f -> TrendingVolumeLiquidityType.MEDIUM
else -> TrendingVolumeLiquidityType.LOW
}
}
@Suppress("MagicNumber")
private fun getMarketRatingType(marketRating: Int?): MarketRatingType {
return when (marketRating) {
1 -> MarketRatingType.GOLD
2 -> MarketRatingType.SILVER
3 -> MarketRatingType.BRONZE
else -> MarketRatingType.OTHER
}
}
@Suppress("MagicNumber")
private fun getMarketRatingRangeValue(marketRating: Int?): Float {
if (marketRating == null) return 0f
return when {
marketRating <= 20 -> {
val segmentStart = 1f
val segmentEnd = 0.75f
val rangeMin = 1
val rangeMax = 20
val progress = segmentStart +
(marketRating - rangeMin).toFloat() / (rangeMax - rangeMin) * (segmentEnd - segmentStart)
progress.coerceIn(0f, 1f)
}
marketRating <= 100 -> {
val segmentStart = 0.76f
val segmentEnd = 0.5f
val rangeMin = 21
val rangeMax = 100
val progress = segmentStart +
(marketRating - rangeMin).toFloat() / (rangeMax - rangeMin) * (segmentEnd - segmentStart)
progress.coerceIn(0f, 1f)
}
marketRating <= 1000 -> {
val segmentStart = 0.51f
val segmentEnd = 0.25f
val rangeMin = 101
val rangeMax = 1000
val progress = segmentStart +
(marketRating - rangeMin).toFloat() / (rangeMax - rangeMin) * (segmentEnd - segmentStart)
progress.coerceIn(0f, 1f)
}
marketRating <= 10000 -> {
val segmentStart = 0.26f
val segmentEnd = 0.01f
val rangeMin = 1001
val rangeMax = 10000
val progress = segmentStart +
(marketRating - rangeMin).toFloat() / (rangeMax - rangeMin) * (segmentEnd - segmentStart)
progress.coerceIn(0f, 1f)
}
else -> {
0f
}
}
}
@Suppress("MagicNumber")
private fun getCirculatingSupplyFillValue(circulatingSupply: BigDecimal?, maxSupply: BigDecimal?): Float? {
if (circulatingSupply == null || maxSupply == null || maxSupply == BigDecimal.ZERO) return null
val ratio = circulatingSupply
.divide(maxSupply, 6, RoundingMode.HALF_UP)
.toFloat()
return ratio.coerceIn(0f, 1f)
}
}

View file

@ -15,6 +15,7 @@ import com.tangem.utils.converter.Converter
@Stable
@Suppress("LongParameterList")
internal class TokenMarketInfoConverter(
private val isRedesignEnabled: Boolean,
private val appCurrency: Provider<AppCurrency>,
private val needApplyFCARestrictions: Provider<Boolean>,
private val onInfoClick: (TangemBottomSheetConfigContent) -> Unit,
@ -47,6 +48,7 @@ internal class TokenMarketInfoConverter(
tokenSymbol = value.symbol,
appCurrency = appCurrency,
onInfoClick = onInfoClick,
isRedesignEnabled = isRedesignEnabled,
)
val exchangesAmount = value.exchangesAmount

View file

@ -0,0 +1,77 @@
package com.tangem.features.feed.ui.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
internal fun MetricsCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
cardColor: Color = TangemTheme.colors2.surface.level3,
title: @Composable () -> Unit,
content: @Composable () -> Unit,
) {
Column(
modifier = modifier
.background(
color = cardColor,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
)
.conditional(
condition = onClick != null,
modifier = {
if (onClick != null) {
clickable(onClick = onClick)
} else {
this
}
},
)
.padding(TangemTheme.dimens2.x4),
verticalArrangement = Arrangement.SpaceBetween,
) {
title()
content()
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun MetricsCardPreview() {
TangemThemePreviewRedesign {
MetricsCard(
modifier = Modifier.heightIn(120.dp),
title = {
Text(
text = "$ 22.4 M",
style = TangemTheme.typography2.headingBold22,
color = TangemTheme.colors2.text.neutral.primary,
)
},
content = {
Text(
text = "Market cap",
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.tertiary,
)
},
onClick = {},
)
}
}

View file

@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
@ -40,6 +41,8 @@ internal inline fun <reified T : TangemBottomSheetConfigContent> EarnFilterBotto
style = TangemTheme.typography2.headingSemibold17,
color = TangemTheme.colors2.text.neutral.primary,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SecondaryTangemButton(
modifier = Modifier.align(Alignment.CenterEnd),

View file

@ -161,6 +161,8 @@ private fun NetworksTypesBlock(
}.resolveReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
TangemCheckbox(
@ -189,6 +191,8 @@ private fun SpecificNetworksBlock(
text = stringResourceSafe(id = R.string.earn_filter_networks),
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
specificNetworks.fastForEachIndexed { index, item ->
TangemRowContainer(
@ -215,6 +219,8 @@ private fun SpecificNetworksBlock(
text = item.text,
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
TangemCheckbox(

View file

@ -11,6 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
@ -110,6 +111,8 @@ private fun ContentV2(content: EarnFilterByTypeBottomSheetContentUM) {
text = type.text.resolveReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
TangemCheckbox(

View file

@ -4,10 +4,8 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@ -16,43 +14,37 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun InformationTextBlock(
text: TextReference,
onInfoClick: () -> Unit,
modifier: Modifier = Modifier,
textStyle: TextStyle = TangemTheme.typography2.captionSemibold12,
textColor: Color = TangemTheme.colors2.text.neutral.secondary,
onInfoClick: (() -> Unit)? = null,
textColor: Color = TangemTheme.colors2.text.neutral.tertiary,
infoIconColor: Color = TangemTheme.colors2.markers.iconGray,
informationTextBlockIconPosition: InformationTextBlockIconPosition = InformationTextBlockIconPosition.START,
) {
val interactionSource = remember { MutableInteractionSource() }
val infoIcon: @Composable () -> Unit = {
IconButton(
modifier = Modifier.requiredSize(TangemTheme.dimens2.x4),
interactionSource = interactionSource,
onClick = onInfoClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens2.x4),
imageVector = ImageVector.vectorResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors2.markers.iconGray,
contentDescription = null,
)
}
Icon(
modifier = Modifier.size(TangemTheme.dimens2.x4),
imageVector = ImageVector.vectorResource(id = R.drawable.ic_information_24),
tint = infoIconColor,
contentDescription = null,
)
}
val contentText: @Composable () -> Unit = {
Text(
text = text.resolveReference(),
style = textStyle,
style = TangemTheme.typography2.captionSemibold12,
color = textColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@ -61,10 +53,17 @@ internal fun InformationTextBlock(
Row(
modifier = modifier
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = onInfoClick,
.conditional(
condition = onInfoClick != null,
modifier = {
onInfoClick?.let { infoClick ->
clickable(
interactionSource = interactionSource,
indication = null,
onClick = infoClick,
)
} ?: this
},
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),

View file

@ -1,14 +1,14 @@
package com.tangem.features.feed.ui.market.detailed.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.TextButton
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.GridItems
@ -16,12 +16,16 @@ import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.components.MetricsCard
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUM
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUMV2
import com.tangem.features.feed.ui.market.detailed.state.MetricsUM
import com.tangem.features.feed.ui.market.detailed.state.MetricsV2UM
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -29,6 +33,17 @@ const val MAX_METRICS_COUNT = 6
@Composable
internal fun MetricsBlock(state: MetricsUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
state.metricsV2?.let {
MetricsBlockV2(it, modifier)
}
} else {
MetricsBlockV1(state, modifier)
}
}
@Composable
private fun MetricsBlockV1(state: MetricsUM, modifier: Modifier = Modifier) {
var isExpanded by remember { mutableStateOf(false) }
InformationBlock(
@ -64,6 +79,50 @@ internal fun MetricsBlock(state: MetricsUM, modifier: Modifier = Modifier) {
)
}
@Composable
private fun MetricsBlockV2(state: MetricsV2UM, modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
state.rows.forEach { row ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
Box(Modifier.weight(1f)) {
MetricRowItem(row.first)
}
row.second?.let { second ->
Box(Modifier.weight(1f)) {
MetricRowItem(second)
}
}
}
}
}
}
@Composable
private fun MetricRowItem(item: InfoPointUMV2) {
when (item) {
is InfoPointUMV2.CirculatingSupply -> CirculatingSupplyCard(item)
else -> MetricCard(item)
}
}
@Composable
private fun MetricCard(item: InfoPointUMV2) {
when (item) {
is InfoPointUMV2.MarketCap -> MarketCapCard(item)
is InfoPointUMV2.TradingVolume -> TradingVolumeCard(item)
is InfoPointUMV2.MarketPosition -> MarketPositionCard(item)
is InfoPointUMV2.FullyDilutedValuation -> FDVCard(item)
is InfoPointUMV2.CirculatingSupply -> Unit
}
}
// TODO make TextButton clickable area smaller and remove paddings for an action in InformationBlock
@Composable
private fun ShowLessMoreButton(expanded: Boolean, onClick: () -> Unit) {
@ -84,6 +143,15 @@ private fun ShowLessMoreButton(expanded: Boolean, onClick: () -> Unit) {
@Composable
internal fun MetricsBlockPlaceholder(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
MetricsBlockPlaceholderV2(modifier)
} else {
MetricsBlockPlaceholderV1(modifier)
}
}
@Composable
private fun MetricsBlockPlaceholderV1(modifier: Modifier = Modifier) {
InformationBlock(
modifier = modifier,
title = {
@ -111,6 +179,108 @@ internal fun MetricsBlockPlaceholder(modifier: Modifier = Modifier) {
)
}
@Composable
private fun MetricsBlockPlaceholderV2(modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
repeat(2) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
repeat(2) {
Box(Modifier.weight(1f)) {
MetricsCard(
modifier = Modifier
.heightIn(120.dp)
.fillMaxWidth(),
title = {
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(28.dp)
.padding(end = 10.dp),
radius = TangemTheme.dimens2.x25,
)
},
content = {
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(16.dp)
.padding(end = 74.dp),
radius = TangemTheme.dimens2.x25,
)
},
)
}
}
}
}
CirculatingSupplyCardPlaceholder()
}
}
@Composable
private fun CirculatingSupplyCardPlaceholder() {
MetricsCard(
modifier = Modifier
.heightIn(120.dp)
.fillMaxWidth(),
title = {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
RectangleShimmer(
modifier = Modifier
.width(104.dp)
.height(16.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier
.width(64.dp)
.height(16.dp),
radius = TangemTheme.dimens2.x25,
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
RectangleShimmer(
modifier = Modifier
.width(160.dp)
.height(28.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier
.width(48.dp)
.height(28.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
},
content = {
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(6.dp),
radius = TangemTheme.dimens2.x25,
)
},
)
}
@Preview
@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@ -150,6 +320,7 @@ private fun BlockPreview() {
onInfoClick = {},
),
),
metricsV2 = null,
),
)
}

View file

@ -0,0 +1,417 @@
package com.tangem.features.feed.ui.market.detailed.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.progressbar.TangemLinearProgressIndicator
import com.tangem.core.ui.ds.progress.TangemLinearProgressIndicatorWithDot
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.components.MetricsCard
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUMV2
import com.tangem.features.feed.ui.market.detailed.state.MarketRatingType
import com.tangem.features.feed.ui.market.detailed.state.TrendingVolumeLiquidityType
@Composable
internal fun MarketCapCard(item: InfoPointUMV2.MarketCap) {
MetricsCard(
modifier = Modifier
.heightIn(120.dp)
.fillMaxWidth(),
title = {
Text(
text = item.capitalizationValue.resolveReference(),
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
content = {
InformationTextBlock(
text = resourceReference(R.string.markets_token_details_market_capitalization),
onInfoClick = item.onInfoClick,
)
},
)
}
@Composable
internal fun TradingVolumeCard(item: InfoPointUMV2.TradingVolume) {
val tradingColor = when (item.trendingVolumeLiquidityType) {
TrendingVolumeLiquidityType.HIGH -> TangemTheme.colors2.markers.backgroundSolidGreen
TrendingVolumeLiquidityType.MEDIUM -> TangemTheme.colors2.graphic.status.attention
TrendingVolumeLiquidityType.LOW -> TangemTheme.colors2.graphic.status.warning
}
MetricsCard(
modifier = Modifier
.heightIn(120.dp)
.fillMaxWidth(),
title = {
Row {
Text(
text = item.tradingValue.resolveReference(),
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
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,
)
}
},
content = {
Column(modifier = Modifier.fillMaxWidth()) {
TangemLinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.height(6.dp),
progress = { item.liquidity },
color = tradingColor,
backgroundColor = TangemTheme.colors2.graphic.neutral.primaryInvertedConstant.copy(alpha = .1f),
)
SpacerH(12.dp)
InformationTextBlock(
text = resourceReference(R.string.markets_token_details_trading_volume),
textColor = tradingColor,
infoIconColor = tradingColor,
onInfoClick = item.onInfoClick,
)
}
},
cardColor = tradingColor.copy(alpha = .2f),
)
}
@Composable
internal fun MarketPositionCard(item: InfoPointUMV2.MarketPosition) {
val ratingCardColor = mapRatingToCardColor(marketRatingType = item.marketRatingType)
val ratingColor = mapRatingToColor(marketRatingType = item.marketRatingType)
MetricsCard(
modifier = Modifier
.heightIn(120.dp)
.fillMaxWidth(),
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_big_laurel_left_20),
tint = ratingColor,
contentDescription = null,
)
Text(
textAlign = TextAlign.Center,
text = item.position,
color = ratingColor,
style = TangemTheme.typography2.headingSemibold20.copy(letterSpacing = 0.sp),
maxLines = 1,
)
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_big_laurel_right_20),
tint = ratingColor,
contentDescription = null,
)
}
},
content = {
Column(modifier = Modifier.fillMaxWidth()) {
TangemLinearProgressIndicatorWithDot(
modifier = Modifier
.fillMaxWidth()
.height(6.dp),
progress = { item.rangeValue },
dotColor = TangemTheme.colors2.fill.neutral.primaryInvertedConstant,
backgroundColor = TangemTheme.colors2.graphic.neutral.primaryInvertedConstant.copy(alpha = .1f),
)
SpacerH(12.dp)
InformationTextBlock(
text = resourceReference(R.string.markets_token_details_market_rating),
textColor = ratingColor,
infoIconColor = ratingColor,
onInfoClick = item.onInfoClick,
)
}
},
cardColor = ratingCardColor,
)
}
@Composable
internal fun FDVCard(item: InfoPointUMV2.FullyDilutedValuation) {
MetricsCard(
modifier = Modifier
.heightIn(120.dp)
.fillMaxWidth(),
title = {
Text(
text = item.value.resolveReference(),
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
content = {
InformationTextBlock(
text = resourceReference(R.string.markets_token_details_fully_diluted_valuation),
onInfoClick = item.onInfoClick,
)
},
)
}
@Composable
internal fun CirculatingSupplyCard(item: InfoPointUMV2.CirculatingSupply) {
MetricsCard(
modifier = Modifier
.heightIn(min = if (item.fillValue == null) 88.dp else 114.dp)
.fillMaxWidth(),
title = {
TangemRowContainer(contentPadding = PaddingValues(0.dp)) {
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
text = stringResourceSafe(R.string.markets_token_details_circulating_supply),
style = TangemTheme.typography2.captionSemibold13,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier
.padding(top = 12.dp)
.layoutId(TangemRowLayoutId.START_BOTTOM),
text = item.currentValue.resolveReference(),
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.END_TOP),
text = stringResourceSafe(R.string.markets_token_details_max_supply),
style = TangemTheme.typography2.captionSemibold13,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (item.maxValue != null) {
Text(
modifier = Modifier
.padding(top = 12.dp)
.layoutId(TangemRowLayoutId.END_BOTTOM),
text = item.maxValue.resolveReference(),
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
},
content = {
if (item.fillValue != null) {
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.height(6.dp),
color = TangemTheme.colors2.graphic.status.accent,
trackColor = TangemTheme.colors2.graphic.neutral.primaryInvertedConstant.copy(alpha = .1f),
progress = { item.fillValue },
strokeCap = StrokeCap.Round,
drawStopIndicator = {},
gapSize = 4.dp,
)
}
},
onClick = item.onInfoClick,
)
}
@Composable
private fun MarketRatingType.baseColor(): Color {
val isDarkTheme = LocalIsInDarkTheme.current
return when (this) {
MarketRatingType.GOLD ->
if (isDarkTheme) Color(GOLD_PLACE_COLOR_NIGHT) else Color(GOLD_PLACE_COLOR_LIGHT)
MarketRatingType.SILVER ->
if (isDarkTheme) Color(SILVER_PLACE_COLOR_NIGHT) else Color(SILVER_PLACE_COLOR_LIGHT)
MarketRatingType.BRONZE ->
if (isDarkTheme) Color(BRONZE_PLACE_COLOR_NIGHT) else Color(BRONZE_PLACE_COLOR_LIGHT)
MarketRatingType.OTHER ->
TangemTheme.colors2.graphic.neutral.primary
}
}
@Composable
private fun mapRatingToColor(marketRatingType: MarketRatingType): Color = marketRatingType.baseColor()
@Composable
private fun mapRatingToCardColor(marketRatingType: MarketRatingType): Color {
return when (marketRatingType) {
MarketRatingType.OTHER -> TangemTheme.colors2.surface.level3
else -> marketRatingType.baseColor().copy(alpha = 0.3f)
}
}
private const val GOLD_PLACE_COLOR_NIGHT = 0xFFFBEE76
private const val GOLD_PLACE_COLOR_LIGHT = 0xFFD9B900
private const val SILVER_PLACE_COLOR_NIGHT = 0xFFAABEF7
private const val SILVER_PLACE_COLOR_LIGHT = 0xFF6680CC
private const val BRONZE_PLACE_COLOR_NIGHT = 0xFFFF9976
private const val BRONZE_PLACE_COLOR_LIGHT = 0xFFCC7F66
@Suppress("LongMethod")
@Preview(widthDp = 360, heightDp = 1500, showBackground = true)
@Preview(widthDp = 360, heightDp = 1500, showBackground = true, locale = "ru")
@Preview(widthDp = 360, heightDp = 1500, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun MetricsCardsPreview() {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level2)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
MarketCapCard(
item = InfoPointUMV2.MarketCap(
capitalizationValue = stringReference("$ 1.2 T"),
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 45.2 M"),
liquidity = 0.75f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.HIGH,
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 12.1 M"),
liquidity = 0.45f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.MEDIUM,
onInfoClick = {},
),
)
TradingVolumeCard(
item = InfoPointUMV2.TradingVolume(
tradingValue = stringReference("$ 2.3 M"),
liquidity = 0.15f,
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.LOW,
onInfoClick = {},
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = "1",
rangeValue = 0.02f,
marketRatingType = MarketRatingType.GOLD,
onInfoClick = {},
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = "2",
rangeValue = 0.05f,
marketRatingType = MarketRatingType.SILVER,
onInfoClick = {},
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = "3",
rangeValue = 0.08f,
marketRatingType = MarketRatingType.BRONZE,
onInfoClick = {},
),
)
MarketPositionCard(
item = InfoPointUMV2.MarketPosition(
position = "42",
rangeValue = 0.42f,
marketRatingType = MarketRatingType.OTHER,
onInfoClick = {},
),
)
FDVCard(
item = InfoPointUMV2.FullyDilutedValuation(
value = stringReference("$ 1.5 T"),
onInfoClick = {},
),
)
CirculatingSupplyCard(
item = InfoPointUMV2.CirculatingSupply(
currentValue = stringReference("12.5 B POL"),
maxValue = stringReference("21 B POL"),
fillValue = 0.6f,
onInfoClick = {},
),
)
CirculatingSupplyCard(
item = InfoPointUMV2.CirculatingSupply(
currentValue = stringReference("18.9 M ETH"),
maxValue = null,
fillValue = null,
onInfoClick = {},
),
)
}
}
}
}

View file

@ -27,6 +27,7 @@ import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.components.ContainerWithDivider
import com.tangem.features.feed.ui.market.detailed.state.SecurityScoreUM
@Composable
@ -79,39 +80,44 @@ private fun SecurityScoreBlockV1(state: SecurityScoreUM, modifier: Modifier = Mo
@Composable
private fun SecurityScoreBlockV2(state: SecurityScoreUM, modifier: Modifier = Modifier) {
TangemRowContainer(modifier = modifier) {
Text(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_TOP),
text = "${state.score}",
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.headingBold28,
)
ContainerWithDivider(
modifier = modifier,
showDivider = true,
) {
TangemRowContainer(modifier = Modifier.padding(top = 20.dp, bottom = 24.dp)) {
Text(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_TOP),
text = "${state.score}",
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.headingBold28,
)
InformationTextBlock(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_BOTTOM),
text = resourceReference(R.string.markets_token_details_security_score),
onInfoClick = state.onInfoClick,
textColor = TangemTheme.colors2.text.neutral.primary,
informationTextBlockIconPosition = InformationTextBlockIconPosition.END,
)
InformationTextBlock(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_BOTTOM),
text = resourceReference(R.string.markets_token_details_security_score),
onInfoClick = state.onInfoClick,
textColor = TangemTheme.colors2.text.neutral.primary,
informationTextBlockIconPosition = InformationTextBlockIconPosition.END,
)
Text(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM),
text = state.description.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM),
text = state.description.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
ScoreStarsBlock(
modifier = Modifier
.padding(bottom = 16.dp)
.layoutId(layoutId = TangemRowLayoutId.END_TOP),
score = state.score,
scoreTextStyle = TangemTheme.typography.body1,
horizontalSpacing = TangemTheme.dimens.spacing8,
)
ScoreStarsBlock(
modifier = Modifier
.padding(bottom = 16.dp)
.layoutId(layoutId = TangemRowLayoutId.END_TOP),
score = state.score,
scoreTextStyle = TangemTheme.typography.body1,
horizontalSpacing = TangemTheme.dimens.spacing8,
)
}
}
}

View file

@ -307,9 +307,7 @@ private fun LazyListScope.loadingInfoBlocksV2() {
}
item("insights-loading") {
InsightsBlockPlaceholder(
modifier = Modifier.blockPaddings(),
)
InsightsBlockPlaceholder(modifier = Modifier.blockPaddings())
}
item(key = "listedOn-loading") {

View file

@ -104,6 +104,7 @@ internal object MarketsTokenDetailsPreview {
infoPoint,
infoPoint,
),
metricsV2 = null,
),
pricePerformance = PricePerformanceUM(
h24 = PricePerformanceUM.Value(

View file

@ -1,7 +1,68 @@
package com.tangem.features.feed.ui.market.detailed.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
internal data class MetricsUM(
val metrics: ImmutableList<InfoPointUM>,
)
val metricsV2: MetricsV2UM?,
)
@Immutable
internal sealed interface InfoPointUMV2 {
@Immutable
data class MarketCap(
val capitalizationValue: TextReference,
val onInfoClick: () -> Unit,
) : InfoPointUMV2
@Immutable
data class TradingVolume(
val tradingValue: TextReference,
val liquidity: Float,
val trendingVolumeLiquidityType: TrendingVolumeLiquidityType,
val onInfoClick: () -> Unit,
) : InfoPointUMV2
@Immutable
data class MarketPosition(
val position: String,
val rangeValue: Float,
val marketRatingType: MarketRatingType,
val onInfoClick: () -> Unit,
) : InfoPointUMV2
@Immutable
data class FullyDilutedValuation(
val value: TextReference,
val onInfoClick: () -> Unit,
) : InfoPointUMV2
@Immutable
data class CirculatingSupply(
val currentValue: TextReference,
val maxValue: TextReference?,
val fillValue: Float?,
val onInfoClick: () -> Unit,
) : InfoPointUMV2
}
internal data class MetricsV2UM(
val rows: ImmutableList<Row>,
) {
internal data class Row(
val first: InfoPointUMV2,
val second: InfoPointUMV2?,
)
}
internal enum class TrendingVolumeLiquidityType {
HIGH, MEDIUM, LOW,
}
internal enum class MarketRatingType {
GOLD, SILVER, BRONZE, OTHER
}