From 1470d4b08d30510e94f2c9900cc133f5178a0814 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Mar 2026 08:46:19 +0100 Subject: [PATCH] Updated on 2026-08-14 --- .../TangemLinearProgressIndicator.kt | 12 +- .../progress/TangemLinearProgressIndicator.kt | 159 +++++++ .../res/drawable/ic_big_laurel_left_20.xml | 9 + .../res/drawable/ic_big_laurel_right_20.xml | 9 + .../details/MarketsTokenDetailsModel.kt | 3 + .../details/converter/MetricsConverter.kt | 209 ++++++++- .../converter/TokenMarketInfoConverter.kt | 2 + .../feed/ui/components/MetricsCard.kt | 77 ++++ .../earn/components/EarnFilterBottomSheet.kt | 3 + .../EarnFilterByNetworkBottomSheet.kt | 6 + .../components/EarnFilterByTypeBottomSheet.kt | 3 + .../components/InformationTextBlock.kt | 45 +- .../detailed/components/MetricsBlock.kt | 177 +++++++- .../detailed/components/MetricsCards.kt | 417 ++++++++++++++++++ .../detailed/components/SecurityScoreBlock.kt | 66 +-- .../components/TokenMarketDetailsBody.kt | 4 +- .../preview/MarketsTokenDetailsPreview.kt | 1 + .../ui/market/detailed/state/MetricsUM.kt | 63 ++- 18 files changed, 1201 insertions(+), 64 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/progress/TangemLinearProgressIndicator.kt create mode 100644 core/ui/src/main/res/drawable/ic_big_laurel_left_20.xml create mode 100644 core/ui/src/main/res/drawable/ic_big_laurel_right_20.xml create mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/components/MetricsCard.kt create mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/MetricsCards.kt 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