diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/progressbar/TangemLinearProgressIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/components/progressbar/TangemLinearProgressIndicator.kt
index 3e27fbe865..3975af67b0 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/progressbar/TangemLinearProgressIndicator.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/progressbar/TangemLinearProgressIndicator.kt
@@ -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,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/progress/TangemLinearProgressIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/progress/TangemLinearProgressIndicator.kt
new file mode 100644
index 0000000000..5eca9a5f3e
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/ds/progress/TangemLinearProgressIndicator.kt
@@ -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
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_big_laurel_left_20.xml b/core/ui/src/main/res/drawable/ic_big_laurel_left_20.xml
new file mode 100644
index 0000000000..92f617c554
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_big_laurel_left_20.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_big_laurel_right_20.xml b/core/ui/src/main/res/drawable/ic_big_laurel_right_20.xml
new file mode 100644
index 0000000000..72a3061544
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_big_laurel_right_20.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt
index a63fc181a1..e371378488 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt
@@ -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,
// ==================
)
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/MetricsConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/MetricsConverter.kt
index e849e636cf..76cd1bed72 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/MetricsConverter.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/MetricsConverter.kt
@@ -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,
private val tokenSymbol: String,
private val onInfoClick: (InfoBottomSheetContent) -> Unit,
+ private val isRedesignEnabled: Boolean,
) : Converter {
@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): 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)
+ }
}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/TokenMarketInfoConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/TokenMarketInfoConverter.kt
index b0c2eddb35..b564fce453 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/TokenMarketInfoConverter.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/converter/TokenMarketInfoConverter.kt
@@ -15,6 +15,7 @@ import com.tangem.utils.converter.Converter
@Stable
@Suppress("LongParameterList")
internal class TokenMarketInfoConverter(
+ private val isRedesignEnabled: Boolean,
private val appCurrency: Provider,
private val needApplyFCARestrictions: Provider,
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
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/components/MetricsCard.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/components/MetricsCard.kt
new file mode 100644
index 0000000000..ed671b018a
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/components/MetricsCard.kt
@@ -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 = {},
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterBottomSheet.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterBottomSheet.kt
index 947d8e8e1b..961de3106d 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterBottomSheet.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterBottomSheet.kt
@@ -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 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),
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterByNetworkBottomSheet.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterByNetworkBottomSheet.kt
index 78dd407f9d..8a0233f7b8 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterByNetworkBottomSheet.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterByNetworkBottomSheet.kt
@@ -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(
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterByTypeBottomSheet.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterByTypeBottomSheet.kt
index 4a77e0adbd..396599caca 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterByTypeBottomSheet.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/earn/components/EarnFilterByTypeBottomSheet.kt
@@ -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(
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/InformationTextBlock.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/InformationTextBlock.kt
index c0d6ca107e..e6cfb24267 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/InformationTextBlock.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/InformationTextBlock.kt
@@ -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),
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/MetricsBlock.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/MetricsBlock.kt
index 1059eeb198..396ef07835 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/MetricsBlock.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/MetricsBlock.kt
@@ -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,
),
)
}
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/MetricsCards.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/MetricsCards.kt
new file mode 100644
index 0000000000..a5a936a6c8
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/MetricsCards.kt
@@ -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 = {},
+ ),
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/SecurityScoreBlock.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/SecurityScoreBlock.kt
index 70929c2ab5..dd7c78854c 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/SecurityScoreBlock.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/SecurityScoreBlock.kt
@@ -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,
+ )
+ }
}
}
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt
index 47d19a1751..de92450ef8 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt
@@ -307,9 +307,7 @@ private fun LazyListScope.loadingInfoBlocksV2() {
}
item("insights-loading") {
- InsightsBlockPlaceholder(
- modifier = Modifier.blockPaddings(),
- )
+ InsightsBlockPlaceholder(modifier = Modifier.blockPaddings())
}
item(key = "listedOn-loading") {
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/preview/MarketsTokenDetailsPreview.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/preview/MarketsTokenDetailsPreview.kt
index 70c4db0103..ed7261d399 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/preview/MarketsTokenDetailsPreview.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/preview/MarketsTokenDetailsPreview.kt
@@ -104,6 +104,7 @@ internal object MarketsTokenDetailsPreview {
infoPoint,
infoPoint,
),
+ metricsV2 = null,
),
pricePerformance = PricePerformanceUM(
h24 = PricePerformanceUM.Value(
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/state/MetricsUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/state/MetricsUM.kt
index 75552e0a4a..4279c29373 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/state/MetricsUM.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/state/MetricsUM.kt
@@ -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,
-)
\ No newline at end of file
+ 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,
+) {
+
+ 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
+}
\ No newline at end of file