From 54d1ea497364fc54822296212137a03b8c69f09e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Jul 2026 14:48:07 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../foryou/impl/components/DonutChart.kt | 16 ++-- .../foryou/impl/components/MarketChart.kt | 82 +++++++++---------- .../components/SegmentTooltipPositioning.kt | 4 +- .../{DonutSegment.kt => DonutSegmentUM.kt} | 2 +- .../impl/components/state/MarketChartState.kt | 40 --------- .../components/state/MarketChartStateUM.kt | 45 ++++++++++ 6 files changed, 96 insertions(+), 93 deletions(-) rename features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/{DonutSegment.kt => DonutSegmentUM.kt} (96%) delete mode 100644 features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartState.kt create mode 100644 features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartStateUM.kt 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 72e4d0d909..c7de61cd5a 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 @@ -42,7 +42,7 @@ 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.DonutSegment +import com.tangem.features.foryou.impl.components.state.DonutSegmentUM import kotlin.math.atan2 import kotlin.math.hypot import kotlin.math.min @@ -71,7 +71,7 @@ import kotlin.math.min * yet). Taps are hit-tested against the ring band only and reported via [onSegmentClick]; the chart is * interactive only when [onSegmentClick] is set **and** [segments] is non-empty. * - * @param segments Slices, in priority order (index 0 is painted on top). See [DonutSegment.weight]. + * @param segments Slices, in priority order (index 0 is painted on top). See [DonutSegmentUM.weight]. * @param modifier Modifier; should carry the overall size (e.g. `Modifier.size(240.dp)`). * @param selectedIndex Index of the currently selected slice, or `null` for no selection (nothing dimmed). * @param onSegmentClick Invoked on every tap inside the chart: with the tapped slice index, or with `null` @@ -86,7 +86,7 @@ import kotlin.math.min @Suppress("MagicNumber", "LongParameterList", "LongMethod", "NamedArguments") @Composable internal fun DonutChart( - segments: List, + segments: List, modifier: Modifier = Modifier, selectedIndex: Int? = null, onSegmentClick: ((index: Int?) -> Unit)? = null, @@ -230,7 +230,7 @@ private fun segmentIndexAt( tap: Offset, size: Size, strokePx: Float, - segments: List, + segments: List, startAngle: Float, ): Int? { val cx = size.width / 2f @@ -351,10 +351,10 @@ private fun PreviewDonutChart() { selectedIndex = selectedIndex, onSegmentClick = { index -> selectedIndex = index.takeIf { it != selectedIndex } }, segments = listOf( - DonutSegment(weight = 0.55f, color = TangemTheme.colors3.border.brand), - DonutSegment(weight = 0.07f, color = TangemTheme.colors3.border.accent.violet), - DonutSegment(weight = 0.06f, color = TangemTheme.colors3.border.accent.red), - DonutSegment(weight = 0.05f, color = TangemTheme.colors3.border.accent.green), + 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), ), ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { 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 54bccd77a2..63a272194e 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 @@ -54,51 +54,49 @@ import com.tangem.core.ui.ds2.surface.TangemSurface import com.tangem.core.ui.extensions.pluralStringResourceSafe import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.res.LocalHazeState 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.AiInsightState -import com.tangem.features.foryou.impl.components.state.DonutChartState -import com.tangem.features.foryou.impl.components.state.DonutSegment -import com.tangem.features.foryou.impl.components.state.MarketChartState +import com.tangem.features.foryou.impl.components.state.AiInsightUM +import com.tangem.features.foryou.impl.components.state.DonutChartUM +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 @Composable -internal fun MarketChart(marketChartState: MarketChartState, modifier: Modifier = Modifier) { - val hazeState = LocalHazeState.current +internal fun MarketChart(modifier: Modifier = Modifier, marketChart: MarketChartUM) { var cardBoundsInWindow by remember { mutableStateOf(Rect.Zero) } TangemSurface( modifier = modifier - .hazeSourceTangem(hazeState) + .hazeSourceTangem() .onGloballyPositioned { cardBoundsInWindow = it.boundsInWindow() }, color = TangemTheme.colors3.bg.secondary, ) { Column { - DonutChartBlock(marketChartState.donutChartState, cardBoundsInWindow) + DonutChartBlock(marketChart.donutChart, cardBoundsInWindow) Spacer(modifier = Modifier.height(16.dp)) - if (marketChartState is MarketChartState.Loaded) { + if (marketChart is MarketChartUM.Loaded) { TopHoldingBlock( - assetCount = marketChartState.assetCount, - topHoldingPercent = marketChartState.topHoldingPercent, + assetCount = marketChart.assetCount, + topHoldingPercent = marketChart.topHoldingPercent, ) } else { CantLoadDataBlock() } Spacer(modifier = Modifier.height(16.dp)) - AiInsightContent(marketChartState.aiInsightState) + AiInsightContent(marketChart.aiInsight) } } } @Suppress("LongMethod") @Composable -private fun ColumnScope.DonutChartBlock(donutChartState: DonutChartState, cardBoundsInWindow: Rect) { +private fun ColumnScope.DonutChartBlock(donutChartUM: DonutChartUM, cardBoundsInWindow: Rect) { var selectedIndex by remember { mutableStateOf(null) } - val segments = donutChartState.donutSegmentList + val segments = donutChartUM.donutSegmentList val scope = rememberCoroutineScope() var dismissJob by remember { mutableStateOf(null) } var chartSize by remember { mutableStateOf(IntSize.Zero) } @@ -135,9 +133,9 @@ private fun ColumnScope.DonutChartBlock(donutChartState: DonutChartState, cardBo }, segments = segments, ) { - if (donutChartState is DonutChartState.Loaded) { + if (donutChartUM is DonutChartUM.Loaded) { Text( - text = donutChartState.totalAmount, + text = donutChartUM.totalAmount, color = TangemTheme.colors3.text.primary, style = TangemTheme.typography3.body.medium, maxLines = 1, @@ -189,7 +187,7 @@ private fun ColumnScope.DonutChartBlock(donutChartState: DonutChartState, cardBo @Composable private fun DonutSegmentTooltipBlock( selectedIndex: Int?, - segments: List, + segments: List, chartSize: IntSize, chartWindowOffset: Offset, cardBoundsInWindow: Rect, @@ -273,13 +271,13 @@ private fun ColumnScope.CantLoadDataBlock() { // IntrinsicSize.Min lets the gradient divider match the AI text height — heightIn wouldn't achieve that. @Suppress("ModifierHeightWithText") @Composable -private fun AiInsightContent(aiInsightState: AiInsightState) { +private fun AiInsightContent(aiInsightUM: AiInsightUM) { AnimatedContent( - targetState = aiInsightState, + targetState = aiInsightUM, transitionSpec = { fadeIn().togetherWith(fadeOut()) }, ) { currentState -> when (currentState) { - is AiInsightState.AskAiInsight -> { + is AiInsightUM.AskAiInsight -> { SecondaryTangemButton( modifier = Modifier .fillMaxWidth() @@ -289,7 +287,7 @@ private fun AiInsightContent(aiInsightState: AiInsightState) { text = resourceReference(R.string.market_chart_ask_for_ai_summary_button), ) } - is AiInsightState.Displayed -> { + is AiInsightUM.Displayed -> { Row( modifier = Modifier .height(IntrinsicSize.Min) @@ -324,7 +322,7 @@ private fun AiInsightContent(aiInsightState: AiInsightState) { ) } } - AiInsightState.Hide -> {} + AiInsightUM.Hide -> {} } } } @@ -350,7 +348,7 @@ private fun MarketChart_Preview( .background(TangemTheme.colors3.bg.primary) .padding(16.dp), ) { - MarketChart(marketChartState = previewMarketChartState(scenario)) + MarketChart(marketChart = previewMarketChartState(scenario)) } } } @@ -361,58 +359,58 @@ private fun MarketChart_Preview( */ @Suppress("MagicNumber") @Composable -private fun previewMarketChartState(scenario: MarketChartPreviewScenario): MarketChartState = when (scenario) { - MarketChartPreviewScenario.DISPLAYED -> MarketChartState.Loaded( +private fun previewMarketChartState(scenario: MarketChartPreviewScenario): MarketChartUM = when (scenario) { + MarketChartPreviewScenario.DISPLAYED -> MarketChartUM.Loaded( topHoldingPercent = 0.41f, - aiInsightState = AiInsightState.Displayed( + 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", ), - donutChartState = previewLoadedDonut(), + donutChart = previewLoadedDonut(), ) - MarketChartPreviewScenario.ASK_AI -> MarketChartState.Loaded( + MarketChartPreviewScenario.ASK_AI -> MarketChartUM.Loaded( topHoldingPercent = 0.41f, - aiInsightState = AiInsightState.AskAiInsight(askAiInsightClick = {}), - donutChartState = previewLoadedDonut(), + aiInsight = AiInsightUM.AskAiInsight(askAiInsightClick = {}), + donutChart = previewLoadedDonut(), ) - MarketChartPreviewScenario.NO_AI -> MarketChartState.Loaded( + MarketChartPreviewScenario.NO_AI -> MarketChartUM.Loaded( topHoldingPercent = 0.41f, - aiInsightState = AiInsightState.Hide, - donutChartState = DonutChartState.Loaded( + aiInsight = AiInsightUM.Hide, + donutChart = DonutChartUM.Loaded( totalAmount = "$10,12345678912.1333", donutSegmentList = listOf( - DonutSegment(weight = 0.55f, color = TangemTheme.colors3.border.brand), - DonutSegment(weight = 0.45f, color = TangemTheme.colors3.border.accent.green), + DonutSegmentUM(weight = 0.55f, color = TangemTheme.colors3.border.brand), + DonutSegmentUM(weight = 0.45f, color = TangemTheme.colors3.border.accent.green), ), ), ) - MarketChartPreviewScenario.NO_DATA -> MarketChartState.NoData + MarketChartPreviewScenario.NO_DATA -> MarketChartUM.NoData } @Suppress("MagicNumber") @Composable -private fun previewLoadedDonut(): DonutChartState.Loaded = DonutChartState.Loaded( +private fun previewLoadedDonut(): DonutChartUM.Loaded = DonutChartUM.Loaded( totalAmount = "$10,123456.1333", donutSegmentList = listOf( - DonutSegment( + DonutSegmentUM( weight = 0.55f, color = TangemTheme.colors3.border.brand, title = "Ethereum", fiatValue = "$5,720.22", ), - DonutSegment( + DonutSegmentUM( weight = 0.077f, color = TangemTheme.colors3.border.accent.violet, title = "Solana", fiatValue = "$728.30", ), - DonutSegment( + DonutSegmentUM( weight = 0.0666f, color = TangemTheme.colors3.border.accent.red, title = "Polkadot", fiatValue = "$624.26", ), - DonutSegment( + DonutSegmentUM( weight = 0.05f, color = TangemTheme.colors3.border.accent.green, title = "Tether", 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 2049ea8f9e..0ea9d87f53 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 @@ -7,7 +7,7 @@ import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.window.PopupPositionProvider -import com.tangem.features.foryou.impl.components.state.DonutSegment +import com.tangem.features.foryou.impl.components.state.DonutSegmentUM import kotlin.math.cos import kotlin.math.min import kotlin.math.roundToInt @@ -23,7 +23,7 @@ import kotlin.math.sin @Suppress("MagicNumber", "LongParameterList", "ComplexCondition") internal fun segmentTooltipPositionProvider( selectedIndex: Int?, - segments: List, + segments: List, chartSize: IntSize, chartWindowOffset: Offset, strokePx: Float, diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegment.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegmentUM.kt similarity index 96% rename from features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegment.kt rename to features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegmentUM.kt index e37e432d5c..cee1256f48 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegment.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/DonutSegmentUM.kt @@ -15,7 +15,7 @@ import androidx.compose.ui.graphics.Color * @param fiatValue Pre-formatted fiat value of the slice (e.g. `"$5,720.22"`). Shown in the selection * tooltip next to the share. Empty by default. */ -internal data class DonutSegment( +internal data class DonutSegmentUM( val weight: Float, val color: Color, val title: String = "", diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartState.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartState.kt deleted file mode 100644 index 1312449349..0000000000 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartState.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.features.foryou.impl.components.state - -internal sealed class MarketChartState( - open val donutChartState: DonutChartState, - open val aiInsightState: AiInsightState, -) { - data class Loaded( - override val donutChartState: DonutChartState.Loaded, - override val aiInsightState: AiInsightState = AiInsightState.Hide, - /* from 0 to 1 */ - val topHoldingPercent: Float, - ) : MarketChartState( - donutChartState = donutChartState, - aiInsightState = aiInsightState, - ) { - val assetCount: Int = donutChartState.donutSegmentList.size - } - - data object NoData : MarketChartState( - donutChartState = DonutChartState.NoData, - aiInsightState = AiInsightState.Hide, - ) -} - -internal sealed class DonutChartState( - open val donutSegmentList: List, -) { - data class Loaded( - val totalAmount: String, - override val donutSegmentList: List, - ) : DonutChartState(donutSegmentList = donutSegmentList) - - data object NoData : DonutChartState(donutSegmentList = emptyList()) -} - -internal sealed class AiInsightState { - data object Hide : AiInsightState() - data class AskAiInsight(val askAiInsightClick: () -> Unit) : AiInsightState() - data class Displayed(val text: String) : AiInsightState() -} \ 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 new file mode 100644 index 0000000000..3498165165 --- /dev/null +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/components/state/MarketChartStateUM.kt @@ -0,0 +1,45 @@ +package com.tangem.features.foryou.impl.components.state + +import androidx.compose.runtime.Immutable + +@Immutable +internal sealed class MarketChartUM( + open val donutChart: DonutChartUM, + open val aiInsight: AiInsightUM, +) { + data class Loaded( + override val donutChart: DonutChartUM.Loaded, + override val aiInsight: AiInsightUM = AiInsightUM.Hide, + /* from 0 to 1 */ + val topHoldingPercent: Float, + ) : MarketChartUM( + donutChart = donutChart, + aiInsight = aiInsight, + ) { + val assetCount: Int = donutChart.donutSegmentList.size + } + + data object NoData : MarketChartUM( + donutChart = DonutChartUM.NoData, + aiInsight = AiInsightUM.Hide, + ) +} + +@Immutable +internal sealed class DonutChartUM( + open val donutSegmentList: List, +) { + data class Loaded( + val totalAmount: String, + override val donutSegmentList: List, + ) : DonutChartUM(donutSegmentList = donutSegmentList) + + data object NoData : DonutChartUM(donutSegmentList = emptyList()) +} + +@Immutable +internal sealed class AiInsightUM { + data object Hide : AiInsightUM() + data class AskAiInsight(val askAiInsightClick: () -> Unit) : AiInsightUM() + data class Displayed(val text: String) : AiInsightUM() +} \ No newline at end of file