Updated on 2026-08-14
This commit is contained in:
parent
5f68c41da2
commit
60bdbc33ab
14 changed files with 1124 additions and 144 deletions
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
implementation(deps.tangem.vico.compose)
|
||||
implementation(deps.tangem.vico.compose.m3)
|
||||
|
||||
implementation(deps.lifecycle.compose)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,402 @@
|
|||
package com.tangem.common.ui.charts
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalFontFamilyResolver
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontSynthesis
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.font.resolveAsTypeface
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.patrykandpatrick.vico.compose.cartesian.*
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisGuidelineComponent
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLabelComponent
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottomAxis
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberCustomStartAxis
|
||||
import com.patrykandpatrick.vico.compose.common.of
|
||||
import com.patrykandpatrick.vico.core.cartesian.HorizontalLayout
|
||||
import com.patrykandpatrick.vico.core.cartesian.Zoom
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.*
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.AxisValueOverrider
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
|
||||
import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerVisibilityListener
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.LineCartesianLayerMarkerTarget
|
||||
import com.patrykandpatrick.vico.core.common.Dimensions
|
||||
import com.patrykandpatrick.vico.core.common.component.LineComponent
|
||||
import com.patrykandpatrick.vico.core.common.shape.Shape
|
||||
import com.tangem.common.ui.charts.layer.rememberMarketChartLayer
|
||||
import com.tangem.common.ui.charts.marker.rememberTangemChartMarker
|
||||
import com.tangem.common.ui.charts.preview.MarketChartPreviewDataProvider
|
||||
import com.tangem.common.ui.charts.state.*
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
private const val GUIDELINES_COUNT = 3
|
||||
|
||||
/**
|
||||
* MarketChart ui component for representing coin prices.
|
||||
*
|
||||
* @param modifier The modifier to be applied to the chart.
|
||||
* @param state The state of the Market Chart, which includes data and look of the chart.
|
||||
* @param splitChartSegmentColor The color of the grayed by marker chart segment.
|
||||
* @param backgroundSplitChartSegmentColorAlpha The alpha of the background the [splitChartSegmentColor]
|
||||
* @param backgroundColorAlpha The alpha of the background color of the chart.
|
||||
* @param noChartContent A composable function that defines the content to be displayed when there is no data to display.
|
||||
*/
|
||||
@Composable
|
||||
fun MarketChart(
|
||||
modifier: Modifier = Modifier,
|
||||
state: MarketChartState = rememberMarketChartState(),
|
||||
splitChartSegmentColor: Color,
|
||||
@FloatRange(from = 0.0, to = 1.0) backgroundSplitChartSegmentColorAlpha: Float,
|
||||
@FloatRange(from = 0.0, to = 1.0) backgroundColorAlpha: Float,
|
||||
noChartContent: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
var canvasWidth by remember { mutableIntStateOf(0) }
|
||||
var canvasHeight by remember { mutableIntStateOf(0) }
|
||||
|
||||
val layer = rememberLayerFromState(
|
||||
state = state,
|
||||
splitChartSegmentColor = splitChartSegmentColor,
|
||||
backgroundColorAlpha = backgroundColorAlpha,
|
||||
backgroundSplitChartSegmentColorAlpha = backgroundSplitChartSegmentColorAlpha,
|
||||
canvasHeight = canvasHeight,
|
||||
)
|
||||
val chart = rememberCartesianChart(
|
||||
layer,
|
||||
startAxis = rememberMarketChartStartAxis(
|
||||
yValueFormatter = state.yValueFormatter,
|
||||
),
|
||||
bottomAxis = rememberMarketChartBottomAxis(
|
||||
xValueFormatter = state.xValueFormatter,
|
||||
),
|
||||
)
|
||||
val marker = rememberTangemChartMarker(
|
||||
color = state.chartColor,
|
||||
innerCircleColor = Color.White,
|
||||
)
|
||||
val density = LocalDensity.current
|
||||
|
||||
CartesianChartHost(
|
||||
modifier = modifier.onGloballyPositioned {
|
||||
with(density) {
|
||||
canvasWidth = it.size.width
|
||||
canvasHeight = if (it.size.height != 0) {
|
||||
// FIXME get height bounded to min max chart points
|
||||
it.size.height - 20.dp.toPx().toInt() - 27.dp.toPx().toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
},
|
||||
chart = chart,
|
||||
modelProducer = state.modelProducer,
|
||||
scrollState = rememberVicoScrollState(scrollEnabled = false),
|
||||
zoomState = rememberVicoZoomState(initialZoom = Zoom.Content, zoomEnabled = false),
|
||||
horizontalLayout = HorizontalLayout.fullWidth(),
|
||||
markerVisibilityListener = state.rememberMarketVisibilityListener(canvasWidth = canvasWidth),
|
||||
diffAnimationSpec = null,
|
||||
marker = marker,
|
||||
placeholder = noChartContent,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MarketChart() {
|
||||
TODO()
|
||||
}
|
||||
private fun MarketChartState.rememberMarketVisibilityListener(canvasWidth: Int): CartesianMarkerVisibilityListener {
|
||||
val state = this
|
||||
return remember(state.markerVisibilityListener, canvasWidth) {
|
||||
val maxCanvasXFloat = canvasWidth.toFloat().takeIf { it != 0f }
|
||||
|
||||
object : CartesianMarkerVisibilityListener {
|
||||
override fun onShown(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
|
||||
state.stopDrawingAnimation()
|
||||
val xCanvas = (targets[0] as LineCartesianLayerMarkerTarget).canvasX
|
||||
|
||||
state.markerFraction = maxCanvasXFloat?.let { xCanvas / it }
|
||||
state.markerVisibilityListener.onShown(marker, targets)
|
||||
}
|
||||
|
||||
override fun onHidden(marker: CartesianMarker) {
|
||||
state.markerFraction = null
|
||||
state.markerVisibilityListener.onHidden(marker)
|
||||
}
|
||||
|
||||
override fun onUpdated(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
|
||||
val xCanvas = (targets[0] as LineCartesianLayerMarkerTarget).canvasX
|
||||
|
||||
state.markerFraction = maxCanvasXFloat?.let { xCanvas / it }
|
||||
state.markerVisibilityListener.onUpdated(marker, targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberLayerFromState(
|
||||
state: MarketChartState,
|
||||
splitChartSegmentColor: Color,
|
||||
@FloatRange(from = 0.0, to = 1.0) backgroundColorAlpha: Float,
|
||||
@FloatRange(from = 0.0, to = 1.0) backgroundSplitChartSegmentColorAlpha: Float,
|
||||
canvasHeight: Int,
|
||||
): LineCartesianLayer {
|
||||
return rememberMarketChartLayer(
|
||||
lineColor = state.chartColor,
|
||||
backgroundLineColor = state.chartColor.copy(alpha = backgroundColorAlpha),
|
||||
secondLineColor = splitChartSegmentColor,
|
||||
backgroundSecondLineColor = splitChartSegmentColor.copy(alpha = backgroundSplitChartSegmentColorAlpha),
|
||||
secondColorOnTheRightSide = state.markerHighlightRightSide.not(),
|
||||
startDrawingAnimation = state.startDrawingAnimationState,
|
||||
markerFraction = state.markerFraction,
|
||||
axisValueOverrider = AxisValueOverrider.adaptiveYValues(yFraction = 1.2f, round = true), // FIXME ?
|
||||
canvasHeight = canvasHeight,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberMarketChartStartAxis(
|
||||
yValueFormatter: CartesianValueFormatter,
|
||||
): VerticalAxis<AxisPosition.Vertical.Start> {
|
||||
return rememberCustomStartAxis(
|
||||
axis = null,
|
||||
tick = null,
|
||||
guideline = null,
|
||||
labelGuideline = rememberChartAxisGuidelineComponent(
|
||||
color = TangemTheme.colors.icon.inactive.copy(alpha = 0.12f),
|
||||
),
|
||||
label = rememberAxisLabelComponent(
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
background = null,
|
||||
padding = Dimensions.of(
|
||||
start = TangemTheme.dimens.spacing4,
|
||||
end = TangemTheme.dimens.spacing4,
|
||||
),
|
||||
textSize = TangemTheme.typography.caption2.fontSize,
|
||||
typeface = TangemTheme.typography.caption2.toGraphicsTypeFace(),
|
||||
),
|
||||
horizontalLabelPosition = VerticalAxis.HorizontalLabelPosition.Inside,
|
||||
verticalLabelPosition = VerticalAxis.VerticalLabelPosition.Center,
|
||||
itemPlacer = AxisItemPlacer.Vertical.count({ GUIDELINES_COUNT }, false),
|
||||
valueFormatter = yValueFormatter,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberMarketChartBottomAxis(
|
||||
xValueFormatter: CartesianValueFormatter,
|
||||
): HorizontalAxis<AxisPosition.Horizontal.Bottom> {
|
||||
return rememberBottomAxis(
|
||||
label = rememberAxisLabelComponent(
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textSize = TangemTheme.typography.caption2.fontSize,
|
||||
padding = Dimensions.of(top = TangemTheme.dimens.spacing20),
|
||||
typeface = TangemTheme.typography.caption2.toGraphicsTypeFace(),
|
||||
),
|
||||
tick = null,
|
||||
axis = null,
|
||||
guideline = null,
|
||||
sizeConstraint = BaseAxis.SizeConstraint.Exact(sizeDp = 37f), // FIXME ?
|
||||
itemPlacer = remember {
|
||||
AxisItemPlacer.Horizontal.default(
|
||||
spacing = 25, // FIXME ?
|
||||
offset = 60, // FIXME ?
|
||||
shiftExtremeTicks = false,
|
||||
addExtremeLabelPadding = false,
|
||||
)
|
||||
},
|
||||
valueFormatter = xValueFormatter,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberChartAxisGuidelineComponent(color: Color): LineComponent {
|
||||
return rememberAxisGuidelineComponent(
|
||||
color = color,
|
||||
shape = Shape.Rectangle,
|
||||
margins = Dimensions(
|
||||
startDp = TangemTheme.dimens.spacing4.value,
|
||||
endDp = TangemTheme.dimens.spacing4.value,
|
||||
topDp = 0f,
|
||||
bottomDp = 0f,
|
||||
),
|
||||
thickness = TangemTheme.dimens.size2,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TextStyle.toGraphicsTypeFace(): android.graphics.Typeface {
|
||||
val resolver = LocalFontFamilyResolver.current
|
||||
return remember(resolver, this) {
|
||||
resolver.resolveAsTypeface(
|
||||
fontFamily = this.fontFamily,
|
||||
fontWeight = this.fontWeight ?: FontWeight.Normal,
|
||||
fontStyle = this.fontStyle ?: FontStyle.Normal,
|
||||
fontSynthesis = this.fontSynthesis ?: FontSynthesis.All,
|
||||
)
|
||||
}.value
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun MarketChartPreview(
|
||||
@PreviewParameter(MarketChartPreviewDataProvider::class) previewData: Pair<List<BigDecimal>, List<BigDecimal>>,
|
||||
) {
|
||||
val y = previewData.second
|
||||
val x = previewData.first
|
||||
|
||||
val dataProducer = remember {
|
||||
MarketChartDataProducer.build {
|
||||
chartLook = MarketChartLook(
|
||||
type = MarketChartLook.Type.Growing,
|
||||
markerHighlightRightSide = true,
|
||||
animationOnDataChange = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
dataProducer.runTransactionSuspend {
|
||||
chartData = MarketChartData.Data(
|
||||
x = x,
|
||||
y = y,
|
||||
)
|
||||
updateLook {
|
||||
it.copy(
|
||||
xAxisFormatter = { value ->
|
||||
value.toLong().toTimeFormat(DateTimeFormatters.dateMMMMd)
|
||||
},
|
||||
yAxisFormatter = { value ->
|
||||
value.setScale(3, RoundingMode.HALF_UP).toPlainString()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
var markerPoint by remember {
|
||||
mutableStateOf(Pair<BigDecimal?, BigDecimal?>(null, null))
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val look by dataProducer.lookState.collectAsState()
|
||||
|
||||
TangemThemePreview {
|
||||
val growingColor = TangemTheme.colors.icon.accent
|
||||
val fallingColor = TangemTheme.colors.icon.warning
|
||||
|
||||
val chartState = rememberMarketChartState(
|
||||
dataProducer = dataProducer,
|
||||
onMarkerShown = { x, y ->
|
||||
markerPoint = Pair(x, y)
|
||||
},
|
||||
colorMapper = {
|
||||
when (it) {
|
||||
MarketChartLook.Type.Growing -> growingColor
|
||||
MarketChartLook.Type.Falling -> fallingColor
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Text(text = "Point: ${markerPoint.first}, ${markerPoint.second}")
|
||||
|
||||
MarketChart(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.height(173.dp),
|
||||
state = chartState,
|
||||
splitChartSegmentColor = TangemTheme.colors.icon.inactive,
|
||||
backgroundSplitChartSegmentColorAlpha = 0.24f,
|
||||
backgroundColorAlpha = 0.24f,
|
||||
noChartContent = { },
|
||||
)
|
||||
SpacerH16()
|
||||
|
||||
Button(onClick = { chartState.startDrawingAnimation() }) {
|
||||
Text("Start drawing animation")
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
dataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(markerHighlightRightSide = !it.markerHighlightRightSide)
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = "Change marker highlight side",
|
||||
)
|
||||
}
|
||||
Button(onClick = {
|
||||
coroutineScope.launch {
|
||||
dataProducer.runTransactionSuspend {
|
||||
updateData {
|
||||
MarketChartData.Data(
|
||||
x = it.x,
|
||||
y = it.y.reversed(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},) {
|
||||
Text("Change Data")
|
||||
}
|
||||
Button(onClick = {
|
||||
dataProducer.runTransaction {
|
||||
updateLook { it.copy(animationOnDataChange = it.animationOnDataChange.not()) }
|
||||
}
|
||||
},) {
|
||||
Text("Change animationOnDataChange = ${look.animationOnDataChange}")
|
||||
}
|
||||
|
||||
Button(onClick = {
|
||||
dataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
type = if (it.type == MarketChartLook.Type.Growing) {
|
||||
MarketChartLook.Type.Falling
|
||||
} else {
|
||||
MarketChartLook.Type.Growing
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},) {
|
||||
Text("Change color type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Preview
|
||||
|
|
@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.Brush
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
|
||||
import com.patrykandpatrick.vico.compose.cartesian.fullWidth
|
||||
|
|
@ -27,13 +28,15 @@ import com.patrykandpatrick.vico.compose.common.shader.BrushShader
|
|||
import com.patrykandpatrick.vico.core.cartesian.HorizontalLayout
|
||||
import com.patrykandpatrick.vico.core.cartesian.Zoom
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.AxisValueOverrider
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.LineCartesianLayerModel
|
||||
import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer
|
||||
import com.patrykandpatrick.vico.core.common.data.ExtraStore
|
||||
import com.patrykandpatrick.vico.core.common.shader.ColorShader
|
||||
import com.patrykandpatrick.vico.core.common.shader.DynamicShader
|
||||
import com.tangem.common.ui.charts.preview.getPreviewBigCartData
|
||||
import com.tangem.common.ui.charts.preview.MarketChartPreviewDataProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Creates and remembers a LineCartesianLayer for a chart with specific characteristics.
|
||||
|
|
@ -51,19 +54,21 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun rememberMarketChartLayer(
|
||||
internal fun rememberMarketChartLayer(
|
||||
lineColor: Color,
|
||||
backgroundLineColor: Color,
|
||||
secondLineColor: Color,
|
||||
backgroundSecondLineColor: Color,
|
||||
startDrawingAnimation: MutableState<Boolean>,
|
||||
axisValueOverrider: AxisValueOverrider,
|
||||
secondColorOnTheRightSide: Boolean = false,
|
||||
@FloatRange(from = 0.0, to = 1.0) markerFraction: Float? = null,
|
||||
secondColorOnTheRightSide: Boolean,
|
||||
@FloatRange(from = 0.0, to = 1.0) markerFraction: Float?,
|
||||
canvasHeight: Int,
|
||||
): LineCartesianLayer {
|
||||
var animationFraction: Float? by remember { mutableStateOf(null) }
|
||||
|
||||
LaunchedEffect(startDrawingAnimation.value) {
|
||||
animationFraction = null
|
||||
if (startDrawingAnimation.value) {
|
||||
animate(
|
||||
initialValue = 0f,
|
||||
|
|
@ -89,9 +94,11 @@ fun rememberMarketChartLayer(
|
|||
secondColorOnTheRightSide = secondColorOnTheRightSide,
|
||||
markerFraction = markerFraction,
|
||||
animationFraction = animationFraction,
|
||||
canvasHeight = canvasHeight,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun rememberRawMarketChartLayer(
|
||||
lineColor: Color,
|
||||
|
|
@ -99,6 +106,7 @@ private fun rememberRawMarketChartLayer(
|
|||
secondLineColor: Color,
|
||||
backgroundSecondLineColor: Color,
|
||||
axisValueOverrider: AxisValueOverrider,
|
||||
canvasHeight: Int,
|
||||
secondColorOnTheRightSide: Boolean = false,
|
||||
@FloatRange(from = 0.0, to = 1.0) markerFraction: Float? = null,
|
||||
@FloatRange(from = 0.0, to = 1.0) animationFraction: Float? = null,
|
||||
|
|
@ -109,38 +117,36 @@ private fun rememberRawMarketChartLayer(
|
|||
val markerSet = markerFraction != null
|
||||
val animationRunning = animationFraction != null && animationFraction != 1f
|
||||
|
||||
val layerColors = remember(animationRunning, markerSet, secondColorOnTheRightSide) {
|
||||
when {
|
||||
!animationRunning && markerSet && secondColorOnTheRightSide -> {
|
||||
LayerColors(
|
||||
lineColor = lineColor,
|
||||
backLineColor = backgroundColorLineGradient,
|
||||
lineColorRight = secondLineColor,
|
||||
backLineColorRight = backgroundSecondLineColorGradient,
|
||||
)
|
||||
}
|
||||
!animationRunning && markerSet && !secondColorOnTheRightSide -> {
|
||||
LayerColors(
|
||||
lineColor = secondLineColor,
|
||||
backLineColor = backgroundSecondLineColorGradient,
|
||||
lineColorRight = lineColor,
|
||||
backLineColorRight = backgroundColorLineGradient,
|
||||
)
|
||||
}
|
||||
animationRunning -> {
|
||||
LayerColors(
|
||||
lineColor = lineColor,
|
||||
backLineColor = backgroundColorLineGradient,
|
||||
lineColorRight = Color.Transparent,
|
||||
backLineColorRight = listOf(Color.Transparent, Color.Transparent),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
LayerColors(
|
||||
lineColor = lineColor,
|
||||
backLineColor = backgroundColorLineGradient,
|
||||
)
|
||||
}
|
||||
val layerColors = when {
|
||||
!animationRunning && markerSet && secondColorOnTheRightSide -> {
|
||||
LayerColors(
|
||||
lineColor = lineColor,
|
||||
backLineColor = backgroundColorLineGradient,
|
||||
lineColorRight = secondLineColor,
|
||||
backLineColorRight = backgroundSecondLineColorGradient,
|
||||
)
|
||||
}
|
||||
!animationRunning && markerSet && !secondColorOnTheRightSide -> {
|
||||
LayerColors(
|
||||
lineColor = secondLineColor,
|
||||
backLineColor = backgroundSecondLineColorGradient,
|
||||
lineColorRight = lineColor,
|
||||
backLineColorRight = backgroundColorLineGradient,
|
||||
)
|
||||
}
|
||||
animationRunning -> {
|
||||
LayerColors(
|
||||
lineColor = lineColor,
|
||||
backLineColor = backgroundColorLineGradient,
|
||||
lineColorRight = Color.Transparent,
|
||||
backLineColorRight = listOf(Color.Transparent, Color.Transparent),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
LayerColors(
|
||||
lineColor = lineColor,
|
||||
backLineColor = backgroundColorLineGradient,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,6 +154,7 @@ private fun rememberRawMarketChartLayer(
|
|||
fractionValue = animationFraction ?: markerFraction,
|
||||
axisValueOverrider = axisValueOverrider,
|
||||
layerColors = layerColors,
|
||||
canvasHeight = canvasHeight,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -163,14 +170,26 @@ private fun rememberLayer(
|
|||
fractionValue: Float?,
|
||||
axisValueOverrider: AxisValueOverrider,
|
||||
layerColors: LayerColors,
|
||||
canvasHeight: Int,
|
||||
): LineCartesianLayer {
|
||||
val endGradientColorPosition = if (canvasHeight != 0) {
|
||||
canvasHeight * END_GRADIENT_COLOR_POSITION_PERCENTAGE
|
||||
} else {
|
||||
Float.POSITIVE_INFINITY
|
||||
}
|
||||
|
||||
return rememberLineCartesianLayer(
|
||||
listOf(
|
||||
if (layerColors.lineColorRight == null || layerColors.backLineColorRight == null || fractionValue == null) {
|
||||
rememberLineSpec(
|
||||
shader = remember(layerColors.lineColor) { ColorShader(color = layerColors.lineColor.toArgb()) },
|
||||
backgroundShader = remember(layerColors.backLineColor) {
|
||||
BrushShader(brush = Brush.verticalGradient(layerColors.backLineColor))
|
||||
backgroundShader = remember(layerColors.backLineColor, endGradientColorPosition) {
|
||||
BrushShader(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = layerColors.backLineColor,
|
||||
endY = endGradientColorPosition,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
|
|
@ -181,11 +200,21 @@ private fun rememberLayer(
|
|||
positions = floatArrayOf(fractionValue, fractionValue),
|
||||
)
|
||||
},
|
||||
backgroundShaderFirst = remember(layerColors.backLineColor) {
|
||||
BrushShader(brush = Brush.verticalGradient(layerColors.backLineColor))
|
||||
backgroundShaderFirst = remember(layerColors.backLineColor, endGradientColorPosition) {
|
||||
BrushShader(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = layerColors.backLineColor,
|
||||
endY = endGradientColorPosition,
|
||||
),
|
||||
)
|
||||
},
|
||||
backgroundShaderSecond = remember(layerColors.backLineColorRight) {
|
||||
BrushShader(brush = Brush.verticalGradient(layerColors.backLineColorRight))
|
||||
backgroundShaderSecond = remember(layerColors.backLineColorRight, endGradientColorPosition) {
|
||||
BrushShader(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = layerColors.backLineColorRight,
|
||||
endY = endGradientColorPosition,
|
||||
),
|
||||
)
|
||||
},
|
||||
xSplitFraction = fractionValue,
|
||||
)
|
||||
|
|
@ -195,14 +224,23 @@ private fun rememberLayer(
|
|||
)
|
||||
}
|
||||
|
||||
private const val END_GRADIENT_COLOR_POSITION_PERCENTAGE = 0.9f
|
||||
|
||||
// region Preview
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun LayerChartPreview() {
|
||||
val xListKey = ExtraStore.Key<List<Long>>()
|
||||
val model = getPreviewBigCartData(xListKey)
|
||||
private fun LayerChartPreview(
|
||||
@PreviewParameter(MarketChartPreviewDataProvider::class) previewData: Pair<List<BigDecimal>, List<BigDecimal>>,
|
||||
) {
|
||||
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 {
|
||||
mutableStateOf(Color.Blue)
|
||||
}
|
||||
|
||||
TangemThemePreview {
|
||||
Column(
|
||||
|
|
@ -213,12 +251,13 @@ private fun LayerChartPreview() {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
chart = rememberCartesianChart(
|
||||
rememberRawMarketChartLayer(
|
||||
lineColor = Color.Blue,
|
||||
backgroundLineColor = Color.Blue.copy(alpha = 0.24f),
|
||||
lineColor = lineColor,
|
||||
backgroundLineColor = lineColor.copy(alpha = 0.24f),
|
||||
secondLineColor = Color.Gray,
|
||||
backgroundSecondLineColor = Color.Gray.copy(alpha = 0.24f),
|
||||
secondColorOnTheRightSide = true,
|
||||
axisValueOverrider = AxisValueOverrider.fixed(minY = model.models[0].minY),
|
||||
canvasHeight = 495,
|
||||
),
|
||||
),
|
||||
zoomState = rememberVicoZoomState(initialZoom = Zoom.Content, zoomEnabled = false),
|
||||
|
|
@ -230,13 +269,14 @@ private fun LayerChartPreview() {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
chart = rememberCartesianChart(
|
||||
rememberRawMarketChartLayer(
|
||||
lineColor = Color.Blue,
|
||||
backgroundLineColor = Color.Blue.copy(alpha = 0.24f),
|
||||
lineColor = lineColor,
|
||||
backgroundLineColor = lineColor.copy(alpha = 0.24f),
|
||||
secondLineColor = Color.Gray,
|
||||
backgroundSecondLineColor = Color.Gray.copy(alpha = 0.24f),
|
||||
markerFraction = 0.35f,
|
||||
secondColorOnTheRightSide = true,
|
||||
axisValueOverrider = AxisValueOverrider.fixed(minY = model.models[0].minY),
|
||||
canvasHeight = 495,
|
||||
),
|
||||
),
|
||||
zoomState = rememberVicoZoomState(initialZoom = Zoom.Content, zoomEnabled = false),
|
||||
|
|
@ -248,14 +288,34 @@ private fun LayerChartPreview() {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
chart = rememberCartesianChart(
|
||||
rememberRawMarketChartLayer(
|
||||
lineColor = Color.Blue,
|
||||
backgroundLineColor = Color.Blue.copy(alpha = 0.24f),
|
||||
secondLineColor = Color.Blue,
|
||||
backgroundSecondLineColor = Color.Blue.copy(alpha = 0.24f),
|
||||
lineColor = lineColor,
|
||||
backgroundLineColor = lineColor.copy(alpha = 0.24f),
|
||||
secondLineColor = Color.Gray,
|
||||
backgroundSecondLineColor = Color.Gray.copy(alpha = 0.24f),
|
||||
markerFraction = 0.35f,
|
||||
secondColorOnTheRightSide = false,
|
||||
axisValueOverrider = AxisValueOverrider.fixed(minY = model.models[0].minY),
|
||||
canvasHeight = 495,
|
||||
),
|
||||
),
|
||||
zoomState = rememberVicoZoomState(initialZoom = Zoom.Content, zoomEnabled = false),
|
||||
horizontalLayout = HorizontalLayout.fullWidth(),
|
||||
model = model,
|
||||
)
|
||||
|
||||
CartesianChartHost(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
chart = rememberCartesianChart(
|
||||
rememberRawMarketChartLayer(
|
||||
lineColor = lineColor,
|
||||
backgroundLineColor = lineColor.copy(alpha = 0.24f),
|
||||
secondLineColor = lineColor,
|
||||
backgroundSecondLineColor = lineColor.copy(alpha = 0.24f),
|
||||
markerFraction = 0.35f,
|
||||
secondColorOnTheRightSide = true,
|
||||
animationFraction = 0.7f,
|
||||
axisValueOverrider = AxisValueOverrider.fixed(minY = model.models[0].minY),
|
||||
canvasHeight = 495,
|
||||
),
|
||||
),
|
||||
zoomState = rememberVicoZoomState(initialZoom = Zoom.Content, zoomEnabled = false),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
|
||||
import com.patrykandpatrick.vico.compose.cartesian.fullWidth
|
||||
|
|
@ -24,16 +25,18 @@ import com.patrykandpatrick.vico.compose.common.shader.color
|
|||
import com.patrykandpatrick.vico.compose.common.shape.dashed
|
||||
import com.patrykandpatrick.vico.core.cartesian.*
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.AxisValueOverrider
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.LineCartesianLayerModel
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker
|
||||
import com.patrykandpatrick.vico.core.common.Dimensions
|
||||
import com.patrykandpatrick.vico.core.common.component.TextComponent
|
||||
import com.patrykandpatrick.vico.core.common.data.ExtraStore
|
||||
import com.patrykandpatrick.vico.core.common.shader.DynamicShader
|
||||
import com.patrykandpatrick.vico.core.common.shape.Shape
|
||||
import com.tangem.common.ui.charts.preview.getPreviewBigCartData
|
||||
import com.tangem.common.ui.charts.preview.MarketChartPreviewDataProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* @param color The color of the indicator and guideline.
|
||||
|
|
@ -53,7 +56,11 @@ internal fun rememberTangemChartMarker(color: Color, innerCircleColor: Color): C
|
|||
)
|
||||
val indicatorRearComponent = rememberShapeComponent(
|
||||
shape = Shape.Pill,
|
||||
color = color.copy(alpha = INDICATOR_REAR_COLOR_ALPHA),
|
||||
color = if (color == Color.Transparent) {
|
||||
Color.Transparent
|
||||
} else {
|
||||
color.copy(alpha = INDICATOR_REAR_COLOR_ALPHA)
|
||||
},
|
||||
)
|
||||
val indicator = rememberLayeredComponent(
|
||||
rear = indicatorRearComponent,
|
||||
|
|
@ -105,10 +112,14 @@ private const val INDICATOR_REAR_COLOR_ALPHA = .24f
|
|||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TangemChartMarkerPreview() {
|
||||
private fun TangemChartMarkerPreview(
|
||||
@PreviewParameter(MarketChartPreviewDataProvider::class) previewData: Pair<List<BigDecimal>, List<BigDecimal>>,
|
||||
) {
|
||||
val marker = rememberTangemChartMarker(Color.Red, Color.White)
|
||||
val xListKey = ExtraStore.Key<List<Long>>()
|
||||
val model = getPreviewBigCartData(xListKey)
|
||||
val y = previewData.second.map { it.toFloat() }
|
||||
val x = List(y.size) { it.toFloat() }
|
||||
val model = CartesianChartModel(LineCartesianLayerModel.build { series(x, y) })
|
||||
|
||||
val centerAprx = (model.models[0].minX + model.models[0].maxX) / 2f
|
||||
val center = model.models[0].getXDeltaGcd().let { centerAprx - centerAprx % it }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
@file:Suppress("MagicNumber")
|
||||
|
||||
package com.tangem.common.ui.charts.preview
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class MarketChartPreviewDataProvider : PreviewParameterProvider<Pair<List<BigDecimal>, List<BigDecimal>>> {
|
||||
override val values: Sequence<Pair<List<BigDecimal>, List<BigDecimal>>>
|
||||
get() {
|
||||
val bitcoinPrice = listOf(
|
||||
59270, 61748, 61941, 62889, 62740, 62989, 63858, 63608, 63951, 63917,
|
||||
63253, 63765, 63862, 64502, 63876, 64028, 63875, 64531, 64249, 63680,
|
||||
63228, 63159, 63249, 63664, 63469, 63711, 63003, 62333, 62882, 62183,
|
||||
62231, 62244, 62200, 61188, 61683, 61199, 61036, 62116, 62436, 63065,
|
||||
62908, 63067, 63294, 60904, 60677, 60790, 60705, 61041, 60667, 61158,
|
||||
61112, 60795, 60900, 60771, 61124, 61343, 61371, 61484, 61133, 62372,
|
||||
62704, 63007, 63092, 62905, 62470, 62019, 61756, 61776, 61557, 61540,
|
||||
61904, 62151, 62419, 64663, 65955, 66229, 65991, 66176, 66517, 65838,
|
||||
65210, 65216, 65611, 66467, 66209, 67247, 66913, 67059, 66978, 66845,
|
||||
67240, 66874, 66993, 66940, 67185, 67330, 67340, 66845, 66062, 66273,
|
||||
66645, 66865, 67005, 67382, 70049, 71464, 71293, 70875, 71137, 69720,
|
||||
69323, 70139, 69964, 69716, 69855, 70442, 69774, 69125, 69404, 69714,
|
||||
69967, 68042, 67077, 67938, 67833, 67182, 67306, 68327, 69093, 68517,
|
||||
68726, 68759, 69061, 68880, 69148, 69305, 69044, 69313, 69122, 68821,
|
||||
68854, 68506, 68823, 68613, 68420, 70356, 69241, 69401, 68004, 67630,
|
||||
68311, 68311, 68291, 68302, 68717, 67926, 67690, 67314, 67285, 67567,
|
||||
68026, 67552, 67740, 68519, 68611, 68363, 68503, 68171, 68326, 67121,
|
||||
67626, 67481, 67657, 67567, 67608, 67607, 67683, 67729, 67733, 67709,
|
||||
).map { it.toBigDecimal() }
|
||||
val bitcoinTimestamps = listOf(
|
||||
1714752000000, 1714766400000, 1714780800000, 1714795200000, 1714809600000,
|
||||
1714824000000, 1714838400000, 1714852800000, 1714867200000, 1714881600000,
|
||||
1714896000000, 1714910400000, 1714924800000, 1714939200000, 1714953600000,
|
||||
1714968000000, 1714982400000, 1714996800000, 1715011200000, 1715025600000,
|
||||
1715040000000, 1715054400000, 1715068800000, 1715083200000, 1715097600000,
|
||||
1715112000000, 1715126400000, 1715140800000, 1715155200000, 1715169600000,
|
||||
1715184000000, 1715198400000, 1715212800000, 1715227200000, 1715241600000,
|
||||
1715256000000, 1715270400000, 1715284800000, 1715299200000, 1715313600000,
|
||||
1715328000000, 1715342400000, 1715356800000, 1715371200000, 1715385600000,
|
||||
1715400000000, 1715414400000, 1715428800000, 1715443200000, 1715457600000,
|
||||
1715472000000, 1715486400000, 1715500800000, 1715515200000, 1715529600000,
|
||||
1715544000000, 1715558400000, 1715572800000, 1715587200000, 1715601600000,
|
||||
1715616000000, 1715630400000, 1715644800000, 1715659200000, 1715673600000,
|
||||
1715688000000, 1715702400000, 1715716800000, 1715731200000, 1715745600000,
|
||||
1715760000000, 1715774400000, 1715788800000, 1715803200000, 1715817600000,
|
||||
1715832000000, 1715846400000, 1715860800000, 1715875200000, 1715889600000,
|
||||
1715904000000, 1715918400000, 1715932800000, 1715947200000, 1715961600000,
|
||||
1715976000000, 1715990400000, 1716004800000, 1716019200000, 1716033600000,
|
||||
1716048000000, 1716062400000, 1716076800000, 1716091200000, 1716105600000,
|
||||
1716120000000, 1716134400000, 1716148800000, 1716163200000, 1716177600000,
|
||||
1716192000000, 1716206400000, 1716220800000, 1716235200000, 1716249600000,
|
||||
1716264000000, 1716278400000, 1716292800000, 1716307200000, 1716321600000,
|
||||
1716336000000, 1716350400000, 1716364800000, 1716379200000, 1716393600000,
|
||||
1716408000000, 1716422400000, 1716436800000, 1716451200000, 1716465600000,
|
||||
1716480000000, 1716494400000, 1716508800000, 1716523200000, 1716537600000,
|
||||
1716552000000, 1716566400000, 1716580800000, 1716595200000, 1716609600000,
|
||||
1716624000000, 1716638400000, 1716652800000, 1716667200000, 1716681600000,
|
||||
1716696000000, 1716710400000, 1716724800000, 1716739200000, 1716753600000,
|
||||
1716768000000, 1716782400000, 1716796800000, 1716811200000, 1716825600000,
|
||||
1716840000000, 1716854400000, 1716868800000, 1716883200000, 1716897600000,
|
||||
1716912000000, 1716926400000, 1716940800000, 1716955200000, 1716969600000,
|
||||
1716984000000, 1716998400000, 1717012800000, 1717027200000, 1717041600000,
|
||||
1717056000000, 1717070400000, 1717084800000, 1717099200000, 1717113600000,
|
||||
1717128000000, 1717142400000, 1717156800000, 1717171200000, 1717185600000,
|
||||
1717200000000, 1717214400000, 1717228800000, 1717243200000, 1717257600000,
|
||||
1717272000000, 1717286400000, 1717300800000, 1717315200000, 1717329600000,
|
||||
).map { it.toBigDecimal() }
|
||||
val notcoinPrice = listOf(
|
||||
"0.02026708", "0.02033274", "0.02112643", "0.02090579",
|
||||
"0.02047231", "0.0215645", "0.0089055", "0.00674459",
|
||||
"0.00733586", "0.00758165", "0.0068592", "0.00680505",
|
||||
"0.00685493", "0.00702113", "0.00725545", "0.00696515",
|
||||
"0.00681556", "0.00677416", "0.00669383", "0.00659057",
|
||||
"0.00668081", "0.0066211", "0.0065232", "0.00612542",
|
||||
"0.00600531", "0.00570974", "0.00559875", "0.00555247",
|
||||
"0.005495", "0.00547841", "0.00545514", "0.00553865",
|
||||
"0.00563302", "0.00568475", "0.00562512", "0.00551265",
|
||||
"0.005398", "0.00560001", "0.00572701", "0.00563223",
|
||||
"0.00555209", "0.00549261", "0.00524999", "0.00531381",
|
||||
"0.00539702", "0.00530647", "0.00533963", "0.00527143",
|
||||
"0.00525981", "0.00495323", "0.00481584", "0.0048854",
|
||||
"0.00480298", "0.00471316", "0.00473191", "0.00476389",
|
||||
"0.00476992", "0.00484649", "0.00471095", "0.00501396",
|
||||
"0.00495481", "0.00545029", "0.0053947", "0.00533251",
|
||||
"0.00518064", "0.00504481", "0.00507209", "0.00516952",
|
||||
"0.00524886", "0.00542661", "0.00544231", "0.00579898",
|
||||
"0.00681472", "0.00720634", "0.00824023", "0.00856427",
|
||||
"0.00821144", "0.00818103", "0.00960254", "0.00911172",
|
||||
"0.00888481", "0.00925911", "0.0091132", "0.009285",
|
||||
"0.00886256", "0.00938211", "0.00936566", "0.0104387",
|
||||
"0.01088452", "0.01200575", "0.01217514", "0.011921",
|
||||
"0.01293908", "0.0126124", "0.01224851", "0.01191149",
|
||||
"0.01179783", "0.01164533", "0.01175718", "0.01169137",
|
||||
"0.01212768", "0.01215952", "0.01300256", "0.01589522",
|
||||
"0.01588223", "0.01780629", "0.01919794", "0.01922187",
|
||||
"0.02165268", "0.02400163", "0.02290975", "0.02383495",
|
||||
"0.02088105", "0.02373489", "0.02269442", "0.02226249",
|
||||
"0.02148038", "0.0232045", "0.02623129", "0.02378975",
|
||||
"0.02438442", "0.02417507", "0.02269891", "0.02236198",
|
||||
"0.02209164", "0.02169842", "0.02137774", "0.02188837",
|
||||
"0.0216619", "0.02238334", "0.02186701", "0.02186521",
|
||||
"0.0217595", "0.02099916", "0.02129634", "0.02143028",
|
||||
"0.02192513", "0.02172005", "0.02184525", "0.01873974",
|
||||
"0.01899164", "0.01957115", "0.0204723", "0.0199247",
|
||||
"0.01935441", "0.01886976", "0.01856944", "0.0179975",
|
||||
"0.0178625", "0.01796981",
|
||||
).map { BigDecimal(it) }
|
||||
val notcoinTimestamps = listOf<Long>(
|
||||
1715428800000, 1715443200000, 1715457600000, 1715472000000,
|
||||
1715486400000, 1715860800000, 1715875200000, 1715889600000,
|
||||
1715904000000, 1715918400000, 1715932800000, 1715947200000,
|
||||
1715961600000, 1715976000000, 1715990400000, 1716004800000,
|
||||
1716019200000, 1716033600000, 1716048000000, 1716062400000,
|
||||
1716076800000, 1716091200000, 1716105600000, 1716120000000,
|
||||
1716134400000, 1716148800000, 1716163200000, 1716177600000,
|
||||
1716192000000, 1716206400000, 1716220800000, 1716235200000,
|
||||
1716249600000, 1716264000000, 1716278400000, 1716292800000,
|
||||
1716307200000, 1716321600000, 1716336000000, 1716350400000,
|
||||
1716364800000, 1716379200000, 1716393600000, 1716408000000,
|
||||
1716422400000, 1716436800000, 1716451200000, 1716465600000,
|
||||
1716480000000, 1716494400000, 1716508800000, 1716523200000,
|
||||
1716537600000, 1716552000000, 1716566400000, 1716580800000,
|
||||
1716595200000, 1716609600000, 1716624000000, 1716638400000,
|
||||
1716652800000, 1716667200000, 1716681600000, 1716696000000,
|
||||
1716710400000, 1716724800000, 1716739200000, 1716753600000,
|
||||
1716768000000, 1716782400000, 1716796800000, 1716811200000,
|
||||
1716825600000, 1716840000000, 1716854400000, 1716868800000,
|
||||
1716883200000, 1716897600000, 1716912000000, 1716926400000,
|
||||
1716940800000, 1716955200000, 1716969600000, 1716984000000,
|
||||
1716998400000, 1717012800000, 1717027200000, 1717041600000,
|
||||
1717056000000, 1717070400000, 1717084800000, 1717099200000,
|
||||
1717113600000, 1717128000000, 1717142400000, 1717156800000,
|
||||
1717171200000, 1717185600000, 1717200000000, 1717214400000,
|
||||
1717228800000, 1717243200000, 1717257600000, 1717272000000,
|
||||
1717286400000, 1717300800000, 1717315200000, 1717329600000,
|
||||
1717344000000, 1717358400000, 1717372800000, 1717387200000,
|
||||
1717401600000, 1717416000000, 1717430400000, 1717444800000,
|
||||
1717459200000, 1717473600000, 1717488000000, 1717502400000,
|
||||
1717516800000, 1717531200000, 1717545600000, 1717560000000,
|
||||
1717574400000, 1717588800000, 1717603200000, 1717617600000,
|
||||
1717632000000, 1717646400000, 1717660800000, 1717675200000,
|
||||
1717689600000, 1717704000000, 1717718400000, 1717732800000,
|
||||
1717747200000, 1717761600000, 1717776000000, 1717790400000,
|
||||
1717804800000, 1717819200000, 1717833600000, 1717848000000,
|
||||
1717862400000, 1717876800000, 1717891200000, 1717905600000,
|
||||
1717920000000, 1717934400000,
|
||||
).map { it.toBigDecimal() }
|
||||
|
||||
return sequenceOf(bitcoinTimestamps to bitcoinPrice, notcoinTimestamps to notcoinPrice)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
@file:Suppress("MagicNumber", "LongMethod")
|
||||
|
||||
package com.tangem.common.ui.charts.preview
|
||||
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.LineCartesianLayerModel
|
||||
import com.patrykandpatrick.vico.core.common.data.ExtraStore
|
||||
import com.patrykandpatrick.vico.core.common.data.MutableExtraStore
|
||||
|
||||
internal fun getPreviewBigCartData(xListKey: ExtraStore.Key<List<Long>>): CartesianChartModel {
|
||||
val y = listOf(
|
||||
59270, 61748, 61941, 62889, 62740, 62989, 63858, 63608, 63951, 63917,
|
||||
63253, 63765, 63862, 64502, 63876, 64028, 63875, 64531, 64249, 63680,
|
||||
63228, 63159, 63249, 63664, 63469, 63711, 63003, 62333, 62882, 62183,
|
||||
62231, 62244, 62200, 61188, 61683, 61199, 61036, 62116, 62436, 63065,
|
||||
62908, 63067, 63294, 60904, 60677, 60790, 60705, 61041, 60667, 61158,
|
||||
61112, 60795, 60900, 60771, 61124, 61343, 61371, 61484, 61133, 62372,
|
||||
62704, 63007, 63092, 62905, 62470, 62019, 61756, 61776, 61557, 61540,
|
||||
61904, 62151, 62419, 64663, 65955, 66229, 65991, 66176, 66517, 65838,
|
||||
65210, 65216, 65611, 66467, 66209, 67247, 66913, 67059, 66978, 66845,
|
||||
67240, 66874, 66993, 66940, 67185, 67330, 67340, 66845, 66062, 66273,
|
||||
66645, 66865, 67005, 67382, 70049, 71464, 71293, 70875, 71137, 69720,
|
||||
69323, 70139, 69964, 69716, 69855, 70442, 69774, 69125, 69404, 69714,
|
||||
69967, 68042, 67077, 67938, 67833, 67182, 67306, 68327, 69093, 68517,
|
||||
68726, 68759, 69061, 68880, 69148, 69305, 69044, 69313, 69122, 68821,
|
||||
68854, 68506, 68823, 68613, 68420, 70356, 69241, 69401, 68004, 67630,
|
||||
68311, 68311, 68291, 68302, 68717, 67926, 67690, 67314, 67285, 67567,
|
||||
68026, 67552, 67740, 68519, 68611, 68363, 68503, 68171, 68326, 67121,
|
||||
67626, 67481, 67657, 67567, 67608, 67607, 67683, 67729, 67733, 67709,
|
||||
)
|
||||
val timestamps: List<Long> = listOf(
|
||||
1714752000000, 1714766400000, 1714780800000, 1714795200000, 1714809600000,
|
||||
1714824000000, 1714838400000, 1714852800000, 1714867200000, 1714881600000,
|
||||
1714896000000, 1714910400000, 1714924800000, 1714939200000, 1714953600000,
|
||||
1714968000000, 1714982400000, 1714996800000, 1715011200000, 1715025600000,
|
||||
1715040000000, 1715054400000, 1715068800000, 1715083200000, 1715097600000,
|
||||
1715112000000, 1715126400000, 1715140800000, 1715155200000, 1715169600000,
|
||||
1715184000000, 1715198400000, 1715212800000, 1715227200000, 1715241600000,
|
||||
1715256000000, 1715270400000, 1715284800000, 1715299200000, 1715313600000,
|
||||
1715328000000, 1715342400000, 1715356800000, 1715371200000, 1715385600000,
|
||||
1715400000000, 1715414400000, 1715428800000, 1715443200000, 1715457600000,
|
||||
1715472000000, 1715486400000, 1715500800000, 1715515200000, 1715529600000,
|
||||
1715544000000, 1715558400000, 1715572800000, 1715587200000, 1715601600000,
|
||||
1715616000000, 1715630400000, 1715644800000, 1715659200000, 1715673600000,
|
||||
1715688000000, 1715702400000, 1715716800000, 1715731200000, 1715745600000,
|
||||
1715760000000, 1715774400000, 1715788800000, 1715803200000, 1715817600000,
|
||||
1715832000000, 1715846400000, 1715860800000, 1715875200000, 1715889600000,
|
||||
1715904000000, 1715918400000, 1715932800000, 1715947200000, 1715961600000,
|
||||
1715976000000, 1715990400000, 1716004800000, 1716019200000, 1716033600000,
|
||||
1716048000000, 1716062400000, 1716076800000, 1716091200000, 1716105600000,
|
||||
1716120000000, 1716134400000, 1716148800000, 1716163200000, 1716177600000,
|
||||
1716192000000, 1716206400000, 1716220800000, 1716235200000, 1716249600000,
|
||||
1716264000000, 1716278400000, 1716292800000, 1716307200000, 1716321600000,
|
||||
1716336000000, 1716350400000, 1716364800000, 1716379200000, 1716393600000,
|
||||
1716408000000, 1716422400000, 1716436800000, 1716451200000, 1716465600000,
|
||||
1716480000000, 1716494400000, 1716508800000, 1716523200000, 1716537600000,
|
||||
1716552000000, 1716566400000, 1716580800000, 1716595200000, 1716609600000,
|
||||
1716624000000, 1716638400000, 1716652800000, 1716667200000, 1716681600000,
|
||||
1716696000000, 1716710400000, 1716724800000, 1716739200000, 1716753600000,
|
||||
1716768000000, 1716782400000, 1716796800000, 1716811200000, 1716825600000,
|
||||
1716840000000, 1716854400000, 1716868800000, 1716883200000, 1716897600000,
|
||||
1716912000000, 1716926400000, 1716940800000, 1716955200000, 1716969600000,
|
||||
1716984000000, 1716998400000, 1717012800000, 1717027200000, 1717041600000,
|
||||
1717056000000, 1717070400000, 1717084800000, 1717099200000, 1717113600000,
|
||||
1717128000000, 1717142400000, 1717156800000, 1717171200000, 1717185600000,
|
||||
1717200000000, 1717214400000, 1717228800000, 1717243200000, 1717257600000,
|
||||
1717272000000, 1717286400000, 1717300800000, 1717315200000, 1717329600000,
|
||||
)
|
||||
|
||||
val x = List(y.size) { index -> index }
|
||||
|
||||
return CartesianChartModel(LineCartesianLayerModel.build { series(x, y) }).copy(
|
||||
extraStore = MutableExtraStore().apply {
|
||||
set(xListKey, timestamps)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.common.ui.charts.state
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Used for formatting the axis labels in a chart.
|
||||
* It takes a BigDecimal value and returns a CharSequence that represents the formatted label.
|
||||
*
|
||||
* @param value The value to be formatted.
|
||||
* @return The formatted label as a CharSequence.
|
||||
*/
|
||||
@Stable
|
||||
fun interface AxisLabelFormatter {
|
||||
|
||||
fun format(value: BigDecimal): CharSequence
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.common.ui.charts.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Immutable
|
||||
sealed interface MarketChartData {
|
||||
|
||||
/**
|
||||
* This interface represents the state when there is no data for the Market Chart.
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface NoData : MarketChartData {
|
||||
@Immutable
|
||||
data object Empty : NoData
|
||||
|
||||
@Immutable
|
||||
data object Loading : NoData
|
||||
|
||||
@Immutable
|
||||
data object ErrorAndRetry : NoData
|
||||
}
|
||||
|
||||
/**
|
||||
* This data class represents the data for the Market Chart.
|
||||
* It includes properties for x and y values.
|
||||
*
|
||||
* @property x List of x values.
|
||||
* @property y List of y values.
|
||||
*/
|
||||
@Immutable
|
||||
data class Data(
|
||||
val x: List<BigDecimal> = listOf(),
|
||||
val y: List<BigDecimal> = listOf(),
|
||||
) : MarketChartData
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
package com.tangem.common.ui.charts.state
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.LineCartesianLayerModel
|
||||
import com.patrykandpatrick.vico.core.common.data.ExtraStore
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* This class represents a transaction for updating the state and look of a Market Chart.
|
||||
*
|
||||
* @property chartLook The updated look of the Market Chart.
|
||||
* @property chartData The updated state of the Market Chart.
|
||||
*/
|
||||
class Transaction(
|
||||
private val currentData: MarketChartData,
|
||||
private val currentLook: MarketChartLook,
|
||||
) {
|
||||
var chartLook: MarketChartLook? = null
|
||||
var chartData: MarketChartData.NoData? = null
|
||||
|
||||
fun updateLook(block: (prev: MarketChartLook) -> MarketChartLook) {
|
||||
chartLook = block(currentLook)
|
||||
}
|
||||
|
||||
fun updateState(block: (prev: MarketChartData) -> MarketChartData.NoData) {
|
||||
chartData = block(currentData)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a transaction for updating the state and look of a Market Chart.
|
||||
* It extends the Transaction class and allows update state by data.
|
||||
*
|
||||
* @property chartData The updated state of the Market Chart.
|
||||
* @property chartLook The updated look of the Market Chart.
|
||||
*/
|
||||
class TransactionSuspend(
|
||||
private val currentData: MarketChartData,
|
||||
private val currentLook: MarketChartLook,
|
||||
) {
|
||||
internal var nonSuspendTransaction: Transaction? = null
|
||||
var chartData: MarketChartData? = null
|
||||
var chartLook: MarketChartLook?
|
||||
get() = nonSuspendTransaction?.chartLook
|
||||
set(value) {
|
||||
if (nonSuspendTransaction == null) {
|
||||
nonSuspendTransaction = Transaction(currentData, currentLook)
|
||||
}
|
||||
nonSuspendTransaction?.chartLook = value
|
||||
}
|
||||
|
||||
fun updateLook(block: (prev: MarketChartLook) -> MarketChartLook) {
|
||||
chartLook = block(currentLook)
|
||||
}
|
||||
|
||||
internal fun updateState(block: (prev: MarketChartData) -> MarketChartData) {
|
||||
chartData = block(currentData)
|
||||
}
|
||||
|
||||
internal fun updateData(block: (prev: MarketChartData.Data) -> MarketChartData.Data) {
|
||||
chartData = when (val currentState = currentData) {
|
||||
is MarketChartData.Data -> block(currentState)
|
||||
else -> currentState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
class MarketChartDataProducer private constructor(
|
||||
initialData: MarketChartData,
|
||||
initialLook: MarketChartLook,
|
||||
private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
) {
|
||||
internal val startDrawingAnimation = MutableSharedFlow<Unit>()
|
||||
internal val dataState = MutableStateFlow(initialData)
|
||||
internal val lookState = MutableStateFlow(initialLook)
|
||||
internal val entries = MutableStateFlow<List<LineCartesianLayerModel.Entry>>(emptyList())
|
||||
|
||||
internal val modelProducer = CartesianChartModelProducer.build(dispatcher = dispatcher)
|
||||
|
||||
/**
|
||||
* This function runs a suspending transaction block to update the state and look of the Market Chart.
|
||||
*/
|
||||
suspend fun runTransactionSuspend(block: TransactionSuspend.() -> Unit) =
|
||||
handleTransactionSuspend(transaction = TransactionSuspend(dataState.value, lookState.value).apply(block))
|
||||
|
||||
/**
|
||||
* This function runs a non-suspending transaction block to update the state and look of the Market Chart.
|
||||
*/
|
||||
fun runTransaction(block: Transaction.() -> Unit) =
|
||||
handleTransaction(transaction = Transaction(dataState.value, lookState.value).apply(block))
|
||||
|
||||
private suspend fun handleTransactionSuspend(transaction: TransactionSuspend) {
|
||||
val nonSuspendTransaction = transaction.nonSuspendTransaction
|
||||
val chartData = transaction.chartData
|
||||
val oldData = dataState.value
|
||||
|
||||
if (chartData != null) {
|
||||
dataState.value = chartData
|
||||
}
|
||||
|
||||
if (chartData is MarketChartData.Data && (oldData !is MarketChartData.Data || oldData != chartData)) {
|
||||
if (lookState.value.animationOnDataChange) {
|
||||
startDrawingAnimation.emit(Unit)
|
||||
}
|
||||
withContext(dispatcher) {
|
||||
val minX = chartData.x.min()
|
||||
val minY = chartData.y.min()
|
||||
|
||||
val normY = chartData.y.map { normalize(it, minY) }
|
||||
val normX = chartData.x.map { normalize(it, minX) }
|
||||
|
||||
val entriesLocal = normX.mapIndexed { index, fl -> LineCartesianLayerModel.Entry(fl, normY[index]) }
|
||||
entries.value = entriesLocal
|
||||
|
||||
modelProducer.runTransaction {
|
||||
add(LineCartesianLayerModel.Partial(series = listOf(entriesLocal)))
|
||||
|
||||
updateExtras {
|
||||
it[entriesKey] = entriesLocal
|
||||
it[xKey] = chartData.x
|
||||
it[yKey] = chartData.y
|
||||
}
|
||||
}.await()
|
||||
}
|
||||
}
|
||||
|
||||
nonSuspendTransaction?.let { handleTransaction(it) }
|
||||
}
|
||||
|
||||
private fun handleTransaction(transaction: Transaction) {
|
||||
transaction.chartData?.let {
|
||||
dataState.value = it
|
||||
}
|
||||
transaction.chartLook?.let {
|
||||
lookState.value = it
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val entriesKey = ExtraStore.Key<List<LineCartesianLayerModel.Entry>>()
|
||||
internal val xKey = ExtraStore.Key<List<BigDecimal>>()
|
||||
internal val yKey = ExtraStore.Key<List<BigDecimal>>()
|
||||
|
||||
private val initialData: MarketChartData = MarketChartData.NoData.Empty
|
||||
private val initialLook: MarketChartLook = MarketChartLook()
|
||||
|
||||
/**
|
||||
* This function builds a MarketChartDataProducer with the given parameters.
|
||||
* It runs a suspending transaction block to initialize the data and look of the Market Chart.
|
||||
*
|
||||
* @param dispatcher The dispatcher to be used for data updates.
|
||||
* @param block The transaction block to be run.
|
||||
* @return A MarketChartDataProducer.
|
||||
*/
|
||||
suspend fun buildSuspend(
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
block: TransactionSuspend.() -> Unit,
|
||||
): MarketChartDataProducer {
|
||||
val transaction = TransactionSuspend(initialData, initialLook).apply(block)
|
||||
|
||||
return MarketChartDataProducer(
|
||||
initialData = initialData,
|
||||
initialLook = initialLook,
|
||||
dispatcher = dispatcher,
|
||||
).apply {
|
||||
handleTransactionSuspend(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function builds a MarketChartDataProducer with the given parameters.
|
||||
* It runs a non-suspending transaction block to initialize the data and look of the Market Chart.
|
||||
*
|
||||
* @param dispatcher The dispatcher to be used for data updates.
|
||||
* @param block The transaction block to be run.
|
||||
* @return A MarketChartDataProducer.
|
||||
*/
|
||||
fun build(
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
block: Transaction.() -> Unit,
|
||||
): MarketChartDataProducer {
|
||||
val transaction = Transaction(initialData, initialLook).apply(block)
|
||||
|
||||
return MarketChartDataProducer(
|
||||
initialData = transaction.chartData ?: initialData,
|
||||
initialLook = transaction.chartLook ?: initialLook,
|
||||
dispatcher = dispatcher,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalize(value: BigDecimal, min: BigDecimal, scale: Int = min.scale()): Float {
|
||||
val n = value - min
|
||||
return if (scale > 2) {
|
||||
n.movePointRight(scale - 2).toFloat()
|
||||
} else {
|
||||
n.toFloat()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.common.ui.charts.state
|
||||
|
||||
/**
|
||||
* This class represents the look and feel of a Market Chart.
|
||||
* It includes properties for type, marker highlight, animation on data change, animate data appearance,
|
||||
* 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 animationOnDataChange A boolean indicating whether to animate on data change.
|
||||
* @property animateDataAppearance A boolean indicating whether to animate data appearance.
|
||||
* @property xAxisFormatter A formatter for the x-axis labels.
|
||||
* @property yAxisFormatter A formatter for the y-axis labels.
|
||||
*/
|
||||
data class MarketChartLook(
|
||||
val type: Type = Type.Growing,
|
||||
val markerHighlightRightSide: Boolean = true,
|
||||
val animationOnDataChange: Boolean = false,
|
||||
val animateDataAppearance: Boolean = false,
|
||||
val xAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
||||
val yAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
||||
) {
|
||||
|
||||
enum class Type {
|
||||
Growing,
|
||||
Falling,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
package com.tangem.common.ui.charts.state
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerVisibilityListener
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.LineCartesianLayerMarkerTarget
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* MarketChartState used for MarketChart ui component.
|
||||
*
|
||||
* @param dataProducer The producer of the data for the Market Chart.
|
||||
* @param colorMapper A function that maps a MarketChartLook.Type to a Color.
|
||||
* @param onMarkerShown A callback function that is called when the marker is shown, hidden, or updated.
|
||||
* @return A MarketChartState.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberMarketChartState(
|
||||
dataProducer: MarketChartDataProducer = remember { MarketChartDataProducer.build {} },
|
||||
colorMapper: (MarketChartLook.Type) -> Color = {
|
||||
when (it) {
|
||||
MarketChartLook.Type.Growing -> Color.Green
|
||||
MarketChartLook.Type.Falling -> Color.Red
|
||||
}
|
||||
},
|
||||
onMarkerShown: (x: BigDecimal?, y: BigDecimal?) -> Unit = { _, _ -> },
|
||||
): MarketChartState {
|
||||
val lookState = dataProducer.lookState.collectAsStateWithLifecycle()
|
||||
|
||||
val state = remember(dataProducer, lookState, colorMapper, onMarkerShown) {
|
||||
MarketChartState(dataProducer, lookState, colorMapper, onMarkerShown)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
dataProducer.startDrawingAnimation.collect {
|
||||
state.startDrawingAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the state of a Market Chart.
|
||||
*
|
||||
* @property dataProducer The producer of the data for the Market Chart.
|
||||
* @property lookState The look state of the Market Chart.
|
||||
* @property colorMapper A function that maps a MarketChartLook.Type to a Color.
|
||||
* @property markerCallback A callback function that is called when the marker is shown, hidden, or updated.
|
||||
* @property isDrawingAnimationInProgress A boolean indicating whether the drawing animation is in progress.
|
||||
*/
|
||||
@Stable
|
||||
class MarketChartState internal constructor(
|
||||
private val dataProducer: MarketChartDataProducer,
|
||||
private val lookState: State<MarketChartLook>,
|
||||
private val colorMapper: (MarketChartLook.Type) -> Color,
|
||||
private val markerCallback: (x: BigDecimal?, y: BigDecimal?) -> Unit,
|
||||
) {
|
||||
internal val startDrawingAnimationState = mutableStateOf(false)
|
||||
internal val modelProducer = dataProducer.modelProducer
|
||||
|
||||
internal val chartColor by derivedStateOf {
|
||||
colorMapper(lookState.value.type)
|
||||
}
|
||||
|
||||
internal val markerHighlightRightSide by derivedStateOf {
|
||||
lookState.value.markerHighlightRightSide
|
||||
}
|
||||
|
||||
internal val xValueFormatter by derivedStateOf {
|
||||
CartesianValueFormatter { value, _, _ ->
|
||||
val state = dataProducer.dataState.value as? MarketChartData.Data
|
||||
?: return@CartesianValueFormatter value.toString()
|
||||
|
||||
val sMin = state.x.min()
|
||||
val scale = sMin.scale()
|
||||
val bVal = if (scale > 2) {
|
||||
value.toBigDecimal().movePointLeft(scale - 2) + sMin
|
||||
} else {
|
||||
value.toBigDecimal() + sMin
|
||||
}
|
||||
|
||||
lookState.value.xAxisFormatter.format(bVal)
|
||||
}
|
||||
}
|
||||
|
||||
internal val yValueFormatter by derivedStateOf {
|
||||
CartesianValueFormatter { value, _, _ ->
|
||||
val state = dataProducer.dataState.value as? MarketChartData.Data
|
||||
?: return@CartesianValueFormatter value.toString()
|
||||
|
||||
val sMin = state.y.min()
|
||||
val scale = sMin.scale()
|
||||
val bVal = if (scale > 2) {
|
||||
value.toBigDecimal().movePointLeft(scale - 2) + sMin
|
||||
} else {
|
||||
value.toBigDecimal() + sMin
|
||||
}
|
||||
|
||||
lookState.value.yAxisFormatter.format(bVal)
|
||||
}
|
||||
}
|
||||
|
||||
internal var markerFraction: Float? by mutableStateOf(null)
|
||||
|
||||
internal val markerVisibilityListener = object : CartesianMarkerVisibilityListener {
|
||||
override fun onShown(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
|
||||
val point = getPoint(targets) ?: run {
|
||||
markerCallback(null, null)
|
||||
return
|
||||
}
|
||||
markerCallback(point.first, point.second)
|
||||
}
|
||||
|
||||
override fun onHidden(marker: CartesianMarker) {
|
||||
markerCallback(null, null)
|
||||
}
|
||||
|
||||
override fun onUpdated(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
|
||||
val point = getPoint(targets) ?: run {
|
||||
markerCallback(null, null)
|
||||
return
|
||||
}
|
||||
markerCallback(point.first, point.second)
|
||||
}
|
||||
}
|
||||
|
||||
val isDrawingAnimationInProgress: Boolean by derivedStateOf {
|
||||
startDrawingAnimationState.value
|
||||
}
|
||||
|
||||
private fun getPoint(targets: List<CartesianMarker.Target>): Pair<BigDecimal, BigDecimal>? {
|
||||
val entry = (targets[0] as LineCartesianLayerMarkerTarget).points[0].entry
|
||||
val entryIndex = dataProducer.entries.value.indexOf(entry).takeIf { it != -1 } ?: return null
|
||||
val state = dataProducer.dataState.value as? MarketChartData.Data ?: return null
|
||||
val x = state.x.getOrNull(entryIndex) ?: return null
|
||||
val y = state.y.getOrNull(entryIndex) ?: return null
|
||||
return x to y
|
||||
}
|
||||
|
||||
fun startDrawingAnimation() {
|
||||
startDrawingAnimationState.value = true
|
||||
}
|
||||
|
||||
fun stopDrawingAnimation() {
|
||||
startDrawingAnimationState.value = false
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue