Updated on 2026-08-14
This commit is contained in:
parent
acf110dbcc
commit
6910f74fd9
242 changed files with 866 additions and 732 deletions
|
|
@ -90,7 +90,7 @@ fun MarketChart(
|
|||
backgroundLineColor = state.chartColor.copy(alpha = backgroundColorAlpha),
|
||||
secondLineColor = splitChartSegmentColor,
|
||||
backgroundSecondLineColor = splitChartSegmentColor.copy(alpha = backgroundSplitChartSegmentColorAlpha),
|
||||
secondColorOnTheRightSide = state.markerHighlightRightSide.not(),
|
||||
secondColorOnTheRightSide = state.shouldMarkerHighlightRightSide.not(),
|
||||
markerFraction = state.markerFraction,
|
||||
axisValueOverrider = AxisValueOverrider.fixed(),
|
||||
canvasHeight = chartHeight,
|
||||
|
|
@ -114,10 +114,10 @@ fun MarketChart(
|
|||
|
||||
CartesianChartHost(
|
||||
modifier = modifier
|
||||
.onGloballyPositioned {
|
||||
canvasWidth = it.size.width
|
||||
chartHeight = if (it.size.height != 0) {
|
||||
it.size.height - bottomAxisHeight
|
||||
.onGloballyPositioned { layoutCoordinates ->
|
||||
canvasWidth = layoutCoordinates.size.width
|
||||
chartHeight = if (layoutCoordinates.size.height != 0) {
|
||||
layoutCoordinates.size.height - bottomAxisHeight
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ fun MarketChart(
|
|||
.drawBehind {
|
||||
state.markerFraction
|
||||
state.chartColor
|
||||
state.markerHighlightRightSide
|
||||
state.shouldMarkerHighlightRightSide
|
||||
},
|
||||
chart = chart,
|
||||
modelProducer = state.modelProducer,
|
||||
|
|
@ -269,7 +269,7 @@ private fun rememberChartAxisGuidelineComponent(color: Color): LineComponent {
|
|||
|
||||
// region Preview
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "NullableToStringCall")
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -283,7 +283,7 @@ private fun MarketChartPreview(
|
|||
MarketChartDataProducer.build {
|
||||
chartLook = MarketChartLook(
|
||||
type = MarketChartLook.Type.Growing,
|
||||
markerHighlightRightSide = true,
|
||||
shouldMarkerHighlightRightSide = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -294,8 +294,8 @@ private fun MarketChartPreview(
|
|||
x = x.toImmutableList(),
|
||||
y = y.toImmutableList(),
|
||||
)
|
||||
updateLook {
|
||||
it.copy(
|
||||
updateLook { marketChartLook ->
|
||||
marketChartLook.copy(
|
||||
xAxisFormatter = { value ->
|
||||
value.toLong().toTimeFormat(DateTimeFormatters.dateMMMdd)
|
||||
},
|
||||
|
|
@ -322,8 +322,8 @@ private fun MarketChartPreview(
|
|||
onMarkerShown = { x, y ->
|
||||
markerPoint = Pair(x, y)
|
||||
},
|
||||
colorMapper = {
|
||||
when (it) {
|
||||
colorMapper = { type ->
|
||||
when (type) {
|
||||
MarketChartLook.Type.Growing -> growingColor
|
||||
MarketChartLook.Type.Falling -> fallingColor
|
||||
MarketChartLook.Type.Neutral -> neutralColor
|
||||
|
|
@ -354,7 +354,7 @@ private fun MarketChartPreview(
|
|||
onClick = {
|
||||
dataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(markerHighlightRightSide = !it.markerHighlightRightSide)
|
||||
it.copy(shouldMarkerHighlightRightSide = !it.shouldMarkerHighlightRightSide)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -367,10 +367,10 @@ private fun MarketChartPreview(
|
|||
onClick = {
|
||||
coroutineScope.launch {
|
||||
dataProducer.runTransactionSuspend {
|
||||
updateData {
|
||||
updateData { data ->
|
||||
MarketChartData.Data(
|
||||
x = it.x,
|
||||
y = it.y.reversed().toImmutableList(),
|
||||
x = data.x,
|
||||
y = data.y.reversed().toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -383,9 +383,9 @@ private fun MarketChartPreview(
|
|||
Button(
|
||||
onClick = {
|
||||
dataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
type = when (it.type) {
|
||||
updateLook { look ->
|
||||
look.copy(
|
||||
type = when (look.type) {
|
||||
MarketChartLook.Type.Growing -> MarketChartLook.Type.Falling
|
||||
MarketChartLook.Type.Falling -> MarketChartLook.Type.Neutral
|
||||
MarketChartLook.Type.Neutral -> MarketChartLook.Type.Growing
|
||||
|
|
|
|||
|
|
@ -104,10 +104,10 @@ private fun PreviewColumn() {
|
|||
|
||||
TangemThemePreview {
|
||||
LazyColumn {
|
||||
items(100) {
|
||||
items(100) { i ->
|
||||
MarketChartMini(
|
||||
rawData = data,
|
||||
type = if (it % 3 == 0) MarketChartLook.Type.Growing else MarketChartLook.Type.Falling,
|
||||
type = if (i % 3 == 0) MarketChartLook.Type.Growing else MarketChartLook.Type.Falling,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@ object LTThreeBuckets {
|
|||
val yRes = ArrayList<Double>(points.size)
|
||||
val indexesRes = ArrayList<Int>(points.size)
|
||||
|
||||
results.fastForEach {
|
||||
xRes.add(it.x)
|
||||
yRes.add(it.y)
|
||||
indexesRes.add(it.originalIndex!!)
|
||||
results.fastForEach { result ->
|
||||
xRes.add(result.x)
|
||||
yRes.add(result.y)
|
||||
indexesRes.add(requireNotNull(result.originalIndex))
|
||||
}
|
||||
|
||||
return Result(
|
||||
|
|
|
|||
|
|
@ -62,12 +62,7 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
|||
},
|
||||
indicatorSizeDp = INDICATOR_SIZE_DP,
|
||||
guideline = guideline,
|
||||
valueFormatter = object : CartesianMarkerValueFormatter {
|
||||
override fun format(
|
||||
context: CartesianDrawContext,
|
||||
targets: List<CartesianMarker.Target>,
|
||||
): CharSequence = ""
|
||||
},
|
||||
valueFormatter = CartesianMarkerValueFormatter { _, _ -> "" },
|
||||
) {
|
||||
override fun updateInsets(
|
||||
context: CartesianMeasureContext,
|
||||
|
|
@ -76,7 +71,12 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
|||
insets: Insets,
|
||||
) {
|
||||
with(context) {
|
||||
super.updateInsets(context, horizontalDimensions, model, insets)
|
||||
super.updateInsets(
|
||||
context = context,
|
||||
horizontalDimensions = horizontalDimensions,
|
||||
model = model,
|
||||
insets = insets,
|
||||
)
|
||||
val baseShadowInsetDp =
|
||||
CLIPPING_FREE_SHADOW_RADIUS_MULTIPLIER * LABEL_BACKGROUND_SHADOW_RADIUS_DP
|
||||
val topInset = (baseShadowInsetDp - LABEL_BACKGROUND_SHADOW_DY_DP).pixels
|
||||
|
|
@ -90,11 +90,11 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
|||
cacheStore
|
||||
.getOrSet(keyNamespace, indicator, outColor) { indicator.invoke(outColor) }
|
||||
.draw(
|
||||
this,
|
||||
x - halfIndicatorSize,
|
||||
y - halfIndicatorSize,
|
||||
x + halfIndicatorSize,
|
||||
y + halfIndicatorSize,
|
||||
context = this,
|
||||
left = x - halfIndicatorSize,
|
||||
top = y - halfIndicatorSize,
|
||||
right = x + halfIndicatorSize,
|
||||
bottom = y + halfIndicatorSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ internal fun rememberMarketChartLayer(
|
|||
val backgroundColorLineGradient = persistentListOf(backgroundLineColor, Color.Transparent)
|
||||
val backgroundSecondLineColorGradient = persistentListOf(backgroundSecondLineColor, Color.Transparent)
|
||||
|
||||
val markerSet = markerFraction != null
|
||||
val isMarkerSet = markerFraction != null
|
||||
|
||||
return rememberLayer(
|
||||
fractionValue = markerFraction ?: 0f,
|
||||
|
|
@ -75,17 +75,17 @@ internal fun rememberMarketChartLayer(
|
|||
} else {
|
||||
lineColor
|
||||
},
|
||||
backLineColor = if (markerSet && !secondColorOnTheRightSide) {
|
||||
backLineColor = if (isMarkerSet && !secondColorOnTheRightSide) {
|
||||
backgroundSecondLineColorGradient
|
||||
} else {
|
||||
backgroundColorLineGradient
|
||||
},
|
||||
lineColorRight = when {
|
||||
markerSet && secondColorOnTheRightSide -> secondLineColor
|
||||
isMarkerSet && secondColorOnTheRightSide -> secondLineColor
|
||||
else -> lineColor
|
||||
},
|
||||
backLineColorRight = when {
|
||||
markerSet && secondColorOnTheRightSide -> backgroundSecondLineColorGradient
|
||||
isMarkerSet && secondColorOnTheRightSide -> backgroundSecondLineColorGradient
|
||||
else -> backgroundColorLineGradient
|
||||
},
|
||||
)
|
||||
|
|
@ -148,7 +148,7 @@ private fun LayerChartPreview(
|
|||
val y = previewData.second.map { it.toFloat() }
|
||||
val x = List(y.size) { it.toFloat() }
|
||||
val model = CartesianChartModel(LineCartesianLayerModel.build { series(x, y) })
|
||||
var lineColor by remember {
|
||||
val lineColor by remember {
|
||||
mutableStateOf(Color.Blue)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class TransactionSuspend(
|
|||
class MarketChartDataProducer private constructor(
|
||||
initialData: MarketChartData,
|
||||
initialLook: MarketChartLook,
|
||||
val pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
|
||||
val pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
|
||||
private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
) {
|
||||
internal val dataState = MutableStateFlow(initialData)
|
||||
|
|
@ -166,7 +166,7 @@ class MarketChartDataProducer private constructor(
|
|||
* @return A MarketChartDataProducer.
|
||||
*/
|
||||
suspend fun buildSuspend(
|
||||
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
|
||||
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
block: TransactionSuspend.() -> Unit,
|
||||
): MarketChartDataProducer {
|
||||
|
|
@ -191,7 +191,7 @@ class MarketChartDataProducer private constructor(
|
|||
* @return A MarketChartDataProducer.
|
||||
*/
|
||||
fun build(
|
||||
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
|
||||
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
block: Transaction.() -> Unit,
|
||||
): MarketChartDataProducer {
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import com.tangem.common.ui.charts.state.formatter.AxisLabelFormatter
|
|||
* and formatters for x and y axis.
|
||||
*
|
||||
* @property type The type of the chart, can be either Growing or Falling.
|
||||
* @property markerHighlightRightSide A boolean indicating whether the marker highlights the right side of the chart.
|
||||
* @property shouldMarkerHighlightRightSide A boolean indicating whether the marker highlights the right side of the chart.
|
||||
* @property xAxisFormatter A formatter for the x-axis labels.
|
||||
* @property yAxisFormatter A formatter for the y-axis labels.
|
||||
*/
|
||||
@Immutable
|
||||
data class MarketChartLook(
|
||||
val type: Type = Type.Growing,
|
||||
val markerHighlightRightSide: Boolean = true,
|
||||
val shouldMarkerHighlightRightSide: Boolean = true,
|
||||
val xAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
||||
val yAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import java.math.BigDecimal
|
|||
fun rememberMarketChartState(
|
||||
dataProducer: MarketChartDataProducer = remember { MarketChartDataProducer.build {} },
|
||||
colorMapper: (MarketChartLook.Type) -> Color = remember {
|
||||
{
|
||||
when (it) {
|
||||
{ type ->
|
||||
when (type) {
|
||||
MarketChartLook.Type.Growing -> Color.Green
|
||||
MarketChartLook.Type.Falling -> Color.Red
|
||||
MarketChartLook.Type.Neutral -> Color.Gray
|
||||
|
|
@ -33,7 +33,12 @@ fun rememberMarketChartState(
|
|||
val lookState = dataProducer.lookState.collectAsState()
|
||||
|
||||
val state = remember(dataProducer, lookState, colorMapper, onMarkerShown) {
|
||||
MarketChartState(dataProducer, lookState, colorMapper, onMarkerShown)
|
||||
MarketChartState(
|
||||
dataProducer = dataProducer,
|
||||
lookState = lookState,
|
||||
colorMapper = colorMapper,
|
||||
markerCallback = onMarkerShown,
|
||||
)
|
||||
}
|
||||
|
||||
return state
|
||||
|
|
@ -61,8 +66,8 @@ class MarketChartState internal constructor(
|
|||
colorMapper(lookState.value.type)
|
||||
}
|
||||
|
||||
internal val markerHighlightRightSide by derivedStateOf {
|
||||
lookState.value.markerHighlightRightSide
|
||||
internal val shouldMarkerHighlightRightSide by derivedStateOf {
|
||||
lookState.value.shouldMarkerHighlightRightSide
|
||||
}
|
||||
|
||||
internal val xValueFormatter = CartesianValueFormatter { value, _, _ ->
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import java.math.BigDecimal
|
|||
|
||||
@Suppress("MagicNumber")
|
||||
class PriceAndTimePointValuesConverter(
|
||||
private val needToFormatAxis: Boolean,
|
||||
private val shouldFormatAxis: Boolean,
|
||||
) : PointValuesConverter {
|
||||
|
||||
private data class MinMaxCache(
|
||||
|
|
@ -18,7 +18,12 @@ class PriceAndTimePointValuesConverter(
|
|||
val maxY: BigDecimal,
|
||||
)
|
||||
|
||||
private var minMaxCache = MinMaxCache(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)
|
||||
private var minMaxCache = MinMaxCache(
|
||||
minX = BigDecimal.ZERO,
|
||||
maxX = BigDecimal.ZERO,
|
||||
minY = BigDecimal.ZERO,
|
||||
maxY = BigDecimal.ZERO,
|
||||
)
|
||||
private val formatYValuesCache = mutableMapOf<Double, BigDecimal>()
|
||||
private val formatXValuesCache = mutableMapOf<Double, BigDecimal>()
|
||||
|
||||
|
|
@ -37,15 +42,14 @@ class PriceAndTimePointValuesConverter(
|
|||
val normX = data.x.normalizeTime(min = cache.minX, max = cache.maxX)
|
||||
|
||||
return if (normX.size > MAX_POINTS) {
|
||||
LTThreeBuckets
|
||||
val downsampled = LTThreeBuckets
|
||||
.downsample(normX, normY, MAX_POINTS - 2)
|
||||
.let {
|
||||
MarketChartRawData(
|
||||
originalIndexes = it.originalIndexes.toImmutableList(),
|
||||
x = it.x.toImmutableList(),
|
||||
y = it.y.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
MarketChartRawData(
|
||||
originalIndexes = downsampled.originalIndexes.toImmutableList(),
|
||||
x = downsampled.x.toImmutableList(),
|
||||
y = downsampled.y.toImmutableList(),
|
||||
)
|
||||
} else {
|
||||
MarketChartRawData(
|
||||
x = normX.toImmutableList(),
|
||||
|
|
@ -55,8 +59,8 @@ class PriceAndTimePointValuesConverter(
|
|||
}
|
||||
|
||||
override fun prepareRawXForFormat(rawX: Double, data: MarketChartData.Data): BigDecimal {
|
||||
if (!needToFormatAxis) return BigDecimal.ZERO
|
||||
if (formatXValuesCache.containsKey(rawX)) return formatXValuesCache[rawX]!!
|
||||
if (!shouldFormatAxis) return BigDecimal.ZERO
|
||||
if (formatXValuesCache.containsKey(rawX)) return requireNotNull(formatXValuesCache[rawX])
|
||||
|
||||
val result = (rawX * MINUTE).toBigDecimal()
|
||||
|
||||
|
|
@ -65,8 +69,8 @@ class PriceAndTimePointValuesConverter(
|
|||
}
|
||||
|
||||
override fun prepareRawYForFormat(rawY: Double, data: MarketChartData.Data): BigDecimal {
|
||||
if (!needToFormatAxis) return BigDecimal.ZERO
|
||||
if (formatYValuesCache.containsKey(rawY)) return formatYValuesCache[rawY]!!
|
||||
if (!shouldFormatAxis) return BigDecimal.ZERO
|
||||
if (formatYValuesCache.containsKey(rawY)) return requireNotNull(formatYValuesCache[rawY])
|
||||
|
||||
val min = minMaxCache.minY
|
||||
val max = minMaxCache.maxY
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue