From 5e083c40d92fca98d368b547ad9cd8fdad9e9e1f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Jul 2026 15:39:20 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../foryou/impl/components/DonutChart.kt | 34 ++++++++-- .../impl/components/DonutSegmentTooltip.kt | 23 ++++--- .../foryou/impl/components/MarketChart.kt | 68 +++++++++++-------- .../components/SegmentTooltipPositioning.kt | 2 +- .../impl/components/state/DonutSegmentUM.kt | 8 ++- .../components/state/MarketChartStateUM.kt | 4 +- 6 files changed, 88 insertions(+), 51 deletions(-) diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/DonutChart.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/DonutChart.kt index c7de61cd5a..962d1c96cf 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/DonutChart.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/DonutChart.kt @@ -38,11 +38,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.features.foryou.impl.R import com.tangem.features.foryou.impl.components.state.DonutSegmentUM +import java.math.BigDecimal import kotlin.math.atan2 import kotlin.math.hypot import kotlin.math.min @@ -164,7 +166,7 @@ internal fun DonutChart( } // Precompute each slice's [start, sweep] once. - val sweeps = segments.map { it.weight.coerceIn(0f, 1f) * 360f } + val sweeps = segments.map { it.weight.toFloat().coerceIn(0f, 1f) * 360f } val starts = sweeps.runningFold(startAngle) { acc, sweep -> acc + sweep } // 2. Slices — reversed so slice 0 sits on top of its neighbor. Each slice gets its own @@ -247,7 +249,7 @@ private fun segmentIndexAt( // Degrees clockwise from 3 o'clock — same convention as Canvas.drawArc. val angle = Math.toDegrees(atan2(dy, dx).toDouble()).toFloat().mod(360f) - val sweeps = segments.map { it.weight.coerceIn(0f, 1f) * 360f } + val sweeps = segments.map { it.weight.toFloat().coerceIn(0f, 1f) * 360f } val starts = sweeps.runningFold(startAngle) { acc, sweep -> acc + sweep } for (i in segments.indices) { if (sweeps[i] <= 0f) continue @@ -351,10 +353,30 @@ private fun PreviewDonutChart() { selectedIndex = selectedIndex, onSegmentClick = { index -> selectedIndex = index.takeIf { it != selectedIndex } }, segments = listOf( - DonutSegmentUM(weight = 0.55f, color = TangemTheme.colors3.border.brand), - DonutSegmentUM(weight = 0.07f, color = TangemTheme.colors3.border.accent.violet), - DonutSegmentUM(weight = 0.06f, color = TangemTheme.colors3.border.accent.red), - DonutSegmentUM(weight = 0.05f, color = TangemTheme.colors3.border.accent.green), + DonutSegmentUM( + weight = BigDecimal(0.55), + color = TangemTheme.colors3.border.brand, + title = stringReference("Ethereum"), + fiatValue = stringReference("$5,720.22"), + ), + DonutSegmentUM( + weight = BigDecimal(0.07), + color = TangemTheme.colors3.border.accent.violet, + title = stringReference("Solana"), + fiatValue = stringReference("$728.30"), + ), + DonutSegmentUM( + weight = BigDecimal(0.06), + color = TangemTheme.colors3.border.accent.red, + title = stringReference("Polkadot"), + fiatValue = stringReference("$624.26"), + ), + DonutSegmentUM( + weight = BigDecimal(0.05), + color = TangemTheme.colors3.border.accent.green, + title = stringReference("Tether"), + fiatValue = stringReference("$520.18"), + ), ), ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/DonutSegmentTooltip.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/DonutSegmentTooltip.kt index 4b404c1755..5f4ec15898 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/DonutSegmentTooltip.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/DonutSegmentTooltip.kt @@ -27,6 +27,9 @@ import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupPositionProvider import androidx.compose.ui.window.PopupProperties import com.tangem.core.ui.ds2.surface.TangemSurface +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign @@ -64,8 +67,8 @@ import com.tangem.core.ui.res.TangemThemePreviewRedesign @Composable internal fun DonutSegmentTooltip( expanded: Boolean, - title: String, - fiatValue: String, + title: TextReference, + fiatValue: TextReference, percent: String, positionProvider: PopupPositionProvider, onDismissRequest: () -> Unit, @@ -100,8 +103,8 @@ private const val DISMISSED_SCALE = 0.8f @Composable private fun TooltipPill( expandedStates: MutableTransitionState, - title: String, - fiatValue: String, + title: TextReference, + fiatValue: TextReference, percent: String, modifier: Modifier = Modifier, ) { @@ -145,7 +148,7 @@ private fun TooltipPill( verticalArrangement = Arrangement.spacedBy(2.dp), ) { Text( - text = title, + text = title.resolveReference(), color = TangemTheme.colors3.text.primary, style = TangemTheme.typography3.caption.medium, maxLines = 1, @@ -153,13 +156,13 @@ private fun TooltipPill( Row(verticalAlignment = Alignment.CenterVertically) { Text( - text = fiatValue, + text = fiatValue.resolveReference(), color = TangemTheme.colors3.text.primary, style = TangemTheme.typography3.caption.medium, maxLines = 1, ) Text( - text = " • $percent%", + text = " • $percent", color = TangemTheme.colors3.text.tertiary, style = TangemTheme.typography3.caption.medium, maxLines = 1, @@ -185,9 +188,9 @@ private fun PreviewDonutSegmentTooltip() { ) { TooltipPill( expandedStates = remember { MutableTransitionState(true) }, - title = "Ethereum", - fiatValue = "$5,720.22", - percent = "57.5", + title = stringReference("Ethereum"), + fiatValue = stringReference("$5,720.22"), + percent = "57.5%", ) } } diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/MarketChart.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/MarketChart.kt index c06b8013a9..582c492702 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/MarketChart.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/MarketChart.kt @@ -51,9 +51,14 @@ import com.tangem.core.ui.components.haze.hazeSourceTangem import com.tangem.core.ui.ds.button.SecondaryTangemButton import com.tangem.core.ui.ds.button.TangemButtonSize import com.tangem.core.ui.ds2.surface.TangemSurface +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.pluralStringResourceSafe +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.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.features.foryou.impl.R @@ -63,6 +68,7 @@ import com.tangem.features.foryou.impl.components.state.DonutSegmentUM import com.tangem.features.foryou.impl.components.state.MarketChartUM import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import java.math.BigDecimal @Composable internal fun MarketChart(marketChart: MarketChartUM, modifier: Modifier = Modifier) { @@ -200,7 +206,7 @@ private fun DonutSegmentTooltipBlock( if (selectedIndex != null) shownIndex = selectedIndex val isExpanded = selectedIndex?.let(segments::getOrNull) != null - val shownSegment = shownIndex?.let(segments::getOrNull) + val shownSegment = shownIndex?.let(segments::getOrNull) ?: return val positionProvider = remember( shownIndex, segments, @@ -225,24 +231,18 @@ private fun DonutSegmentTooltipBlock( DonutSegmentTooltip( expanded = isExpanded, positionProvider = positionProvider, - title = shownSegment?.title.orEmpty(), - fiatValue = shownSegment?.fiatValue.orEmpty(), - percent = shownSegment?.let { formatSegmentPercent(it.weight) }.orEmpty(), + title = shownSegment.title, + fiatValue = shownSegment.fiatValue, + percent = shownSegment.weight.format { percent() }, onDismissRequest = onDismissRequest, ) } -@Suppress("MagicNumber") -private fun formatSegmentPercent(weight: Float): String { - val percent = weight.coerceIn(0f, 1f) * 100 - return if (percent % 1f == 0f) "${percent.toInt()}" else "%.2f".format(percent) -} - private val DonutStrokeWidth = 28.dp private val DonutStartAngle = -90f @Composable -private fun ColumnScope.TopHoldingBlock(assetCount: Int, topHoldingPercent: Float) { +private fun ColumnScope.TopHoldingBlock(assetCount: Int, topHoldingPercent: TextReference) { Text( modifier = Modifier.padding(horizontal = 16.dp), text = pluralStringResourceSafe(R.plurals.market_chart_assets_android, assetCount, assetCount), @@ -252,7 +252,7 @@ private fun ColumnScope.TopHoldingBlock(assetCount: Int, topHoldingPercent: Floa Text( modifier = Modifier.padding(horizontal = 16.dp), - text = stringResourceSafe(R.string.market_chart_top_holding, formatSegmentPercent(topHoldingPercent)), + text = topHoldingPercent.resolveReference(), color = TangemTheme.colors3.text.primary, style = TangemTheme.typography3.heading.small, ) @@ -361,7 +361,7 @@ private fun MarketChart_Preview( @Composable private fun previewMarketChartState(scenario: MarketChartPreviewScenario): MarketChartUM = when (scenario) { MarketChartPreviewScenario.DISPLAYED -> MarketChartUM.Loaded( - topHoldingPercent = 0.41f, + topHoldingPercent = stringReference("Top holding 41%"), aiInsight = AiInsightUM.Displayed( "Your portfolio leans on a single asset – BTC is 42% of holdings. Stablecoins add 23% " + "buffer. Consider trimming concentration for a smoother ride", @@ -369,18 +369,28 @@ private fun previewMarketChartState(scenario: MarketChartPreviewScenario): Marke donutChart = previewLoadedDonut(), ) MarketChartPreviewScenario.ASK_AI -> MarketChartUM.Loaded( - topHoldingPercent = 0.41f, + topHoldingPercent = stringReference("Top holding 41%"), aiInsight = AiInsightUM.AskAiInsight(askAiInsightClick = {}), donutChart = previewLoadedDonut(), ) MarketChartPreviewScenario.NO_AI -> MarketChartUM.Loaded( - topHoldingPercent = 0.41f, + topHoldingPercent = stringReference("Top holding 41%"), aiInsight = AiInsightUM.Hide, donutChart = DonutChartUM.Loaded( totalAmount = "$10,12345678912.1333", donutSegmentList = listOf( - DonutSegmentUM(weight = 0.55f, color = TangemTheme.colors3.border.brand), - DonutSegmentUM(weight = 0.45f, color = TangemTheme.colors3.border.accent.green), + DonutSegmentUM( + weight = BigDecimal(0.55), + color = TangemTheme.colors3.border.brand, + title = stringReference("Ethereum"), + fiatValue = stringReference("$5,720.22"), + ), + DonutSegmentUM( + weight = BigDecimal(0.45), + color = TangemTheme.colors3.border.accent.green, + title = stringReference("Solana"), + fiatValue = stringReference("$728.30"), + ), ), ), ) @@ -393,28 +403,28 @@ private fun previewLoadedDonut(): DonutChartUM.Loaded = DonutChartUM.Loaded( totalAmount = "$10,123456.1333", donutSegmentList = listOf( DonutSegmentUM( - weight = 0.55f, + weight = BigDecimal(0.55), color = TangemTheme.colors3.border.brand, - title = "Ethereum", - fiatValue = "$5,720.22", + title = stringReference("Ethereum"), + fiatValue = stringReference("$5,720.22"), ), DonutSegmentUM( - weight = 0.077f, + weight = BigDecimal(0.077), color = TangemTheme.colors3.border.accent.violet, - title = "Solana", - fiatValue = "$728.30", + title = stringReference("Solana"), + fiatValue = stringReference("$728.30"), ), DonutSegmentUM( - weight = 0.0666f, + weight = BigDecimal(0.0666), color = TangemTheme.colors3.border.accent.red, - title = "Polkadot", - fiatValue = "$624.26", + title = stringReference("Polkadot"), + fiatValue = stringReference("$624.26"), ), DonutSegmentUM( - weight = 0.05f, + weight = BigDecimal(0.05), color = TangemTheme.colors3.border.accent.green, - title = "Tether", - fiatValue = "$520.18", + title = stringReference("Tether"), + fiatValue = stringReference("$520.18"), ), ), ) diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/SegmentTooltipPositioning.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/SegmentTooltipPositioning.kt index 0ea9d87f53..bf03d8e6cf 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/SegmentTooltipPositioning.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/SegmentTooltipPositioning.kt @@ -42,7 +42,7 @@ internal fun segmentTooltipPositionProvider( val centerY = chartSize.height / 2f val innerRadius = diameter / 2f - strokePx / 2 // End angle of the selected slice (before its round cap) — same layout as DonutChart's drawing pass. - val sweeps = segments.map { it.weight.coerceIn(0f, 1f) * 360f } + val sweeps = segments.map { it.weight.toFloat().coerceIn(0f, 1f) * 360f } val endAngleDeg = startAngle + sweeps.take(selectedIndex + 1).sum() val endAngleRad = Math.toRadians(endAngleDeg.toDouble()) val anchorLocal = Offset( diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegmentUM.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegmentUM.kt index cee1256f48..4fdb4e448f 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegmentUM.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegmentUM.kt @@ -1,6 +1,8 @@ package com.tangem.features.foryou.impl.components.state import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.extensions.TextReference +import java.math.BigDecimal /** * One colored slice of a [com.tangem.features.foryou.impl.components.DonutChart]. @@ -16,8 +18,8 @@ import androidx.compose.ui.graphics.Color * tooltip next to the share. Empty by default. */ internal data class DonutSegmentUM( - val weight: Float, val color: Color, - val title: String = "", - val fiatValue: String = "", + val weight: BigDecimal, + val title: TextReference, + val fiatValue: TextReference, ) \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartStateUM.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartStateUM.kt index 3498165165..ce825b8336 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartStateUM.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartStateUM.kt @@ -1,6 +1,7 @@ package com.tangem.features.foryou.impl.components.state import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference @Immutable internal sealed class MarketChartUM( @@ -10,8 +11,7 @@ internal sealed class MarketChartUM( data class Loaded( override val donutChart: DonutChartUM.Loaded, override val aiInsight: AiInsightUM = AiInsightUM.Hide, - /* from 0 to 1 */ - val topHoldingPercent: Float, + val topHoldingPercent: TextReference, ) : MarketChartUM( donutChart = donutChart, aiInsight = aiInsight,