Updated on 2026-08-14
This commit is contained in:
parent
31a8ee5dd4
commit
7c3b952ee8
7 changed files with 170 additions and 76 deletions
|
|
@ -8,18 +8,9 @@ import androidx.compose.animation.core.animateFloatAsState
|
|||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
|
|
@ -43,7 +34,10 @@ 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.DonutSegmentColor
|
||||
import com.tangem.features.foryou.impl.components.state.DonutSegmentUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.hypot
|
||||
|
|
@ -88,7 +82,7 @@ import kotlin.math.min
|
|||
@Suppress("MagicNumber", "LongParameterList", "LongMethod", "NamedArguments")
|
||||
@Composable
|
||||
internal fun DonutChart(
|
||||
segments: List<DonutSegmentUM>,
|
||||
segments: ImmutableList<DonutSegmentUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
selectedIndex: Int? = null,
|
||||
onSegmentClick: ((index: Int?) -> Unit)? = null,
|
||||
|
|
@ -100,6 +94,11 @@ internal fun DonutChart(
|
|||
val strokePx = with(LocalDensity.current) { strokeWidth.toPx() }
|
||||
val dimOverlayColor = TangemTheme.colors3.border.inverse.tertiary
|
||||
|
||||
// Resolve each slice's themed colour once, here in composition (the palette reads TangemTheme, which
|
||||
// isn't available inside the drawBehind DrawScope). Order is preserved 1:1 with [segments] so slice i
|
||||
// keeps the colour its producer assigned by rank — the draw pass below indexes by i, not by paint order.
|
||||
val segmentColors = segments.map { it.color.getColor() }
|
||||
|
||||
// Fade the dim in/out in step with the segment tooltip's pop-in (same spring as DonutSegmentTooltip).
|
||||
val dimProgress by animateFloatAsState(
|
||||
targetValue = if (selectedIndex != null) 1f else 0f,
|
||||
|
|
@ -175,7 +174,7 @@ internal fun DonutChart(
|
|||
for (i in segments.indices.reversed()) {
|
||||
if (sweeps[i] <= 0f) continue
|
||||
drawArc(
|
||||
color = segments[i].color,
|
||||
color = segmentColors[i],
|
||||
startAngle = starts[i],
|
||||
sweepAngle = sweeps[i],
|
||||
useCenter = false,
|
||||
|
|
@ -352,28 +351,28 @@ private fun PreviewDonutChart() {
|
|||
modifier = Modifier.size(260.dp),
|
||||
selectedIndex = selectedIndex,
|
||||
onSegmentClick = { index -> selectedIndex = index.takeIf { it != selectedIndex } },
|
||||
segments = listOf(
|
||||
segments = persistentListOf(
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.55),
|
||||
color = TangemTheme.colors3.border.brand,
|
||||
color = DonutSegmentColor.Brand,
|
||||
title = stringReference("Ethereum"),
|
||||
fiatValue = stringReference("$5,720.22"),
|
||||
),
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.07),
|
||||
color = TangemTheme.colors3.border.accent.violet,
|
||||
color = DonutSegmentColor.Violet,
|
||||
title = stringReference("Solana"),
|
||||
fiatValue = stringReference("$728.30"),
|
||||
),
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.06),
|
||||
color = TangemTheme.colors3.border.accent.red,
|
||||
color = DonutSegmentColor.Red,
|
||||
title = stringReference("Polkadot"),
|
||||
fiatValue = stringReference("$624.26"),
|
||||
),
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.05),
|
||||
color = TangemTheme.colors3.border.accent.green,
|
||||
color = DonutSegmentColor.Green,
|
||||
title = stringReference("Tether"),
|
||||
fiatValue = stringReference("$520.18"),
|
||||
),
|
||||
|
|
@ -409,7 +408,7 @@ private fun PreviewDonutChartEmpty() {
|
|||
) {
|
||||
DonutChart(
|
||||
modifier = Modifier.size(260.dp),
|
||||
segments = emptyList(),
|
||||
segments = persistentListOf(),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.market_chart_bubble_no_data),
|
||||
|
|
|
|||
|
|
@ -8,27 +8,10 @@ import androidx.compose.animation.togetherWith
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.withFrameNanos
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
|
|
@ -51,21 +34,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.extensions.*
|
||||
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
|
||||
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 com.tangem.features.foryou.impl.components.state.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -80,7 +56,7 @@ internal fun MarketChart(marketChart: MarketChartUM, modifier: Modifier = Modifi
|
|||
.onGloballyPositioned { cardBoundsInWindow = it.boundsInWindow() },
|
||||
color = TangemTheme.colors3.bg.secondary,
|
||||
) {
|
||||
Column {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
DonutChartBlock(marketChart.donutChart, cardBoundsInWindow)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
if (marketChart is MarketChartUM.Loaded) {
|
||||
|
|
@ -353,10 +329,7 @@ private fun MarketChart_Preview(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a [scenario] to the state shown. Built inside a `@Composable` (not the [PreviewParameterProvider])
|
||||
* because the segment colors come from [TangemTheme.colors3], which can only be read in composition.
|
||||
*/
|
||||
/** Maps a [scenario] to the state shown in the preview. */
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun previewMarketChartState(scenario: MarketChartPreviewScenario): MarketChartUM = when (scenario) {
|
||||
|
|
@ -378,16 +351,16 @@ private fun previewMarketChartState(scenario: MarketChartPreviewScenario): Marke
|
|||
aiInsight = AiInsightUM.Hide,
|
||||
donutChart = DonutChartUM.Loaded(
|
||||
totalAmount = "$10,12345678912.1333",
|
||||
donutSegmentList = listOf(
|
||||
donutSegmentList = persistentListOf(
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.55),
|
||||
color = TangemTheme.colors3.border.brand,
|
||||
color = DonutSegmentColor.Brand,
|
||||
title = stringReference("Ethereum"),
|
||||
fiatValue = stringReference("$5,720.22"),
|
||||
),
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.45),
|
||||
color = TangemTheme.colors3.border.accent.green,
|
||||
color = DonutSegmentColor.Green,
|
||||
title = stringReference("Solana"),
|
||||
fiatValue = stringReference("$728.30"),
|
||||
),
|
||||
|
|
@ -401,28 +374,28 @@ private fun previewMarketChartState(scenario: MarketChartPreviewScenario): Marke
|
|||
@Composable
|
||||
private fun previewLoadedDonut(): DonutChartUM.Loaded = DonutChartUM.Loaded(
|
||||
totalAmount = "$10,123456.1333",
|
||||
donutSegmentList = listOf(
|
||||
donutSegmentList = persistentListOf(
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.55),
|
||||
color = TangemTheme.colors3.border.brand,
|
||||
color = DonutSegmentColor.Brand,
|
||||
title = stringReference("Ethereum"),
|
||||
fiatValue = stringReference("$5,720.22"),
|
||||
),
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.077),
|
||||
color = TangemTheme.colors3.border.accent.violet,
|
||||
color = DonutSegmentColor.Violet,
|
||||
title = stringReference("Solana"),
|
||||
fiatValue = stringReference("$728.30"),
|
||||
),
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.0666),
|
||||
color = TangemTheme.colors3.border.accent.red,
|
||||
color = DonutSegmentColor.Red,
|
||||
title = stringReference("Polkadot"),
|
||||
fiatValue = stringReference("$624.26"),
|
||||
),
|
||||
DonutSegmentUM(
|
||||
weight = BigDecimal(0.05),
|
||||
color = TangemTheme.colors3.border.accent.green,
|
||||
color = DonutSegmentColor.Green,
|
||||
title = stringReference("Tether"),
|
||||
fiatValue = stringReference("$520.18"),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.features.foryou.impl.components.state
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
@ -12,7 +15,8 @@ import java.math.BigDecimal
|
|||
* contiguously; whatever is left after `sum(weight)` shows through as the track.
|
||||
* For a portfolio where slices sum to 1f the ring fills completely and no track is visible.
|
||||
* Also doubles as the slice's share for the selection tooltip (rendered as `weight * 100%`).
|
||||
* @param color Solid fill of the slice.
|
||||
* @param color Palette entry for the slice's solid fill. Assigned by the producer in segment order, so the
|
||||
* slice's colour follows its rank; [DonutChart] resolves it to a themed [Color] in composition.
|
||||
* @param title Human-readable name of the asset this slice represents (e.g. `"Ethereum"`). Shown in the
|
||||
* selection tooltip. Empty by default for slices that don't need a label.
|
||||
* @param fiatValue Pre-formatted fiat value of the slice (e.g. `"$5,720.22"`). Shown in the selection
|
||||
|
|
@ -21,8 +25,27 @@ import java.math.BigDecimal
|
|||
|
||||
@Immutable
|
||||
internal data class DonutSegmentUM(
|
||||
val color: Color,
|
||||
val color: DonutSegmentColor,
|
||||
val weight: BigDecimal,
|
||||
val title: TextReference,
|
||||
val fiatValue: TextReference,
|
||||
)
|
||||
)
|
||||
|
||||
internal enum class DonutSegmentColor {
|
||||
Brand,
|
||||
Violet,
|
||||
Red,
|
||||
Green,
|
||||
;
|
||||
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
fun getColor(): Color {
|
||||
return when (this) {
|
||||
Brand -> TangemTheme.colors3.border.brand
|
||||
Violet -> TangemTheme.colors3.border.accent.violet
|
||||
Red -> TangemTheme.colors3.border.accent.red
|
||||
Green -> TangemTheme.colors3.border.accent.green
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.features.foryou.impl.components.state
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Immutable
|
||||
internal sealed class MarketChartUM(
|
||||
|
|
@ -27,14 +29,14 @@ internal sealed class MarketChartUM(
|
|||
|
||||
@Immutable
|
||||
internal sealed class DonutChartUM(
|
||||
open val donutSegmentList: List<DonutSegmentUM>,
|
||||
open val donutSegmentList: ImmutableList<DonutSegmentUM>,
|
||||
) {
|
||||
data class Loaded(
|
||||
val totalAmount: String,
|
||||
override val donutSegmentList: List<DonutSegmentUM>,
|
||||
override val donutSegmentList: ImmutableList<DonutSegmentUM>,
|
||||
) : DonutChartUM(donutSegmentList = donutSegmentList)
|
||||
|
||||
data object NoData : DonutChartUM(donutSegmentList = emptyList())
|
||||
data object NoData : DonutChartUM(donutSegmentList = persistentListOf())
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.foryou.impl.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds2.messagebanner.TangemMessageBanner
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_cloud_exclamation_20
|
||||
import com.tangem.features.foryou.impl.R
|
||||
|
||||
@Immutable
|
||||
internal sealed class ForYouNotification(val state: TangemMessageBanner.State) {
|
||||
|
||||
data object UsedOutdatedData : ForYouNotification(
|
||||
state = TangemMessageBanner.State(
|
||||
title = resourceReference(R.string.warning_some_token_balances_not_updated),
|
||||
iconEnd = TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_cloud_exclamation_20,
|
||||
tintReference = { TangemTheme.colors3.icon.primary },
|
||||
),
|
||||
variant = TangemMessageBanner.Variant.Warning,
|
||||
isShowGlowRing = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue