kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat: Updated the env metrics graph to use the latest graph feature (#1667)
refactor: removed unused constants and functionpull/1672/head
rodzic
5846bf5ee4
commit
7189d44b9c
|
@ -57,21 +57,14 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.LINE_LIMIT
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.TEXT_PAINT_ALPHA
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.LEFT_LABEL_SPACING
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.MAX_PERCENT_VALUE
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.MS_PER_SEC
|
||||
import java.text.DateFormat
|
||||
|
||||
object CommonCharts {
|
||||
val DATE_TIME_FORMAT: DateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM)
|
||||
const val X_AXIS_SPACING = 8f
|
||||
const val LEFT_LABEL_SPACING = 36
|
||||
const val MS_PER_SEC = 1000L
|
||||
const val LINE_LIMIT = 4
|
||||
const val TEXT_PAINT_ALPHA = 192
|
||||
const val MAX_PERCENT_VALUE = 100f
|
||||
val INFANTRY_BLUE = Color(75, 119, 190)
|
||||
}
|
||||
|
@ -81,6 +74,8 @@ private const val LINE_OFF = 20f
|
|||
private val TIME_FORMAT: DateFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM)
|
||||
private val DATE_FORMAT: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT)
|
||||
private const val DATE_Y = 32f
|
||||
private const val LINE_LIMIT = 4
|
||||
private const val TEXT_PAINT_ALPHA = 192
|
||||
|
||||
data class LegendData(val nameRes: Int, val color: Color, val isLine: Boolean = false)
|
||||
|
||||
|
@ -100,78 +95,10 @@ fun ChartHeader(amount: Int) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws chart lines and labels with respect to the Y-axis range; defined by (`maxValue` - `minValue`).
|
||||
*
|
||||
* @param labelColor The color to be used for the Y labels.
|
||||
* @param lineColors A list of 5 `Color`s for the chart lines, 0 being the lowest line on the chart.
|
||||
* @param leaveSpace When true the lines will leave space for Y labels on the left side of the graph.
|
||||
*/
|
||||
@Deprecated("Will soon be replaced with YAxisLabels() and HorizontalLinesOverlay()", level = DeprecationLevel.WARNING)
|
||||
@Composable
|
||||
fun ChartOverlay(
|
||||
modifier: Modifier,
|
||||
labelColor: Color,
|
||||
lineColors: List<Color>,
|
||||
minValue: Float,
|
||||
maxValue: Float,
|
||||
leaveSpace: Boolean = false
|
||||
) {
|
||||
val range = maxValue - minValue
|
||||
val verticalSpacing = range / LINE_LIMIT
|
||||
val density = LocalDensity.current
|
||||
Canvas(modifier = modifier) {
|
||||
|
||||
val lineStart = if (leaveSpace) LEFT_LABEL_SPACING.dp.toPx() else 0f
|
||||
val height = size.height
|
||||
val width = size.width - 28.dp.toPx()
|
||||
|
||||
/* Horizontal Lines */
|
||||
var lineY = minValue
|
||||
for (i in 0..LINE_LIMIT) {
|
||||
val ratio = (lineY - minValue) / range
|
||||
val y = height - (ratio * height)
|
||||
drawLine(
|
||||
start = Offset(lineStart, y),
|
||||
end = Offset(width, y),
|
||||
color = lineColors[i],
|
||||
strokeWidth = 1.dp.toPx(),
|
||||
cap = StrokeCap.Round,
|
||||
pathEffect = PathEffect.dashPathEffect(floatArrayOf(LINE_ON, LINE_OFF), 0f)
|
||||
)
|
||||
lineY += verticalSpacing
|
||||
}
|
||||
|
||||
/* Y Labels */
|
||||
|
||||
val textPaint = Paint().apply {
|
||||
color = labelColor.toArgb()
|
||||
textAlign = Paint.Align.LEFT
|
||||
textSize = density.run { 12.dp.toPx() }
|
||||
typeface = setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
|
||||
alpha = TEXT_PAINT_ALPHA
|
||||
}
|
||||
drawContext.canvas.nativeCanvas.apply {
|
||||
var label = minValue
|
||||
for (i in 0..LINE_LIMIT) {
|
||||
val ratio = (label - minValue) / range
|
||||
val y = height - (ratio * height)
|
||||
drawText(
|
||||
"${label.toInt()}",
|
||||
width + 4.dp.toPx(),
|
||||
y + 4.dp.toPx(),
|
||||
textPaint
|
||||
)
|
||||
label += verticalSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws chart lines with respect to the Y-axis.
|
||||
*
|
||||
* @param lineColors A list of 5 `Color`s for the chart lines, 0 being the lowest line on the chart.
|
||||
* @param lineColors A list of 5 [Color]s for the chart lines, 0 being the lowest line on the chart.
|
||||
*/
|
||||
@Composable
|
||||
fun HorizontalLinesOverlay(
|
||||
|
|
|
@ -158,8 +158,7 @@ private fun DeviceMetricsChart(
|
|||
val graphColor = MaterialTheme.colors.onSurface
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||
val dp by remember(key1 = selectedTime) {
|
||||
mutableStateOf(selectedTime.dp(screenWidth, time = timeDiff.toLong()))
|
||||
}
|
||||
|
@ -169,7 +168,7 @@ private fun DeviceMetricsChart(
|
|||
contentAlignment = Alignment.TopStart,
|
||||
modifier = Modifier
|
||||
.horizontalScroll(state = scrollState, reverseScrolling = true)
|
||||
.weight(1f)
|
||||
.weight(weight = 1f)
|
||||
) {
|
||||
|
||||
/*
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -31,6 +32,7 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.MaterialTheme
|
||||
|
@ -44,13 +46,9 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.asAndroidPath
|
||||
import androidx.compose.ui.graphics.asComposePath
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
@ -62,10 +60,11 @@ import com.geeksville.mesh.TelemetryProtos.Telemetry
|
|||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.TimeFrame
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.X_AXIS_SPACING
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.MS_PER_SEC
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.ui.components.CommonCharts.INFANTRY_BLUE
|
||||
import com.geeksville.mesh.util.GraphUtil.createPath
|
||||
import com.geeksville.mesh.util.GraphUtil.drawPathWithGradient
|
||||
|
||||
private enum class Environment(val color: Color) {
|
||||
TEMPERATURE(Color.Red),
|
||||
|
@ -135,6 +134,7 @@ fun EnvironmentMetricsScreen(
|
|||
.fillMaxWidth()
|
||||
.fillMaxHeight(fraction = 0.33f),
|
||||
telemetries = processedTelemetries.reversed(),
|
||||
selectedTimeFrame,
|
||||
promptInfoDialog = { displayInfoDialog = true }
|
||||
)
|
||||
|
||||
|
@ -165,32 +165,31 @@ fun EnvironmentMetricsScreen(
|
|||
private fun EnvironmentMetricsChart(
|
||||
modifier: Modifier = Modifier,
|
||||
telemetries: List<Telemetry>,
|
||||
selectedTime: TimeFrame,
|
||||
promptInfoDialog: () -> Unit
|
||||
) {
|
||||
ChartHeader(amount = telemetries.size)
|
||||
if (telemetries.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val (oldest, newest) = remember(key1 = telemetries) {
|
||||
Pair(
|
||||
telemetries.minBy { it.time },
|
||||
telemetries.maxBy { it.time }
|
||||
)
|
||||
}
|
||||
val timeDiff = newest.time - oldest.time
|
||||
|
||||
TimeLabels(
|
||||
oldest = telemetries.first().time,
|
||||
newest = telemetries.last().time
|
||||
oldest = oldest.time,
|
||||
newest = newest.time
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
val graphColor = MaterialTheme.colors.onSurface
|
||||
val transparentTemperatureColor = remember {
|
||||
Environment.TEMPERATURE.color.copy(alpha = 0.5f)
|
||||
}
|
||||
val transparentHumidityColor = remember {
|
||||
Environment.HUMIDITY.color.copy(alpha = 0.5f)
|
||||
}
|
||||
val transparentIAQColor = remember {
|
||||
Environment.IAQ.color.copy(alpha = 0.5f)
|
||||
}
|
||||
val spacing = X_AXIS_SPACING
|
||||
|
||||
/* Since both temperature and humidity are being plotted we need a combined min and max. */
|
||||
/* Grab the combined min and max for all data being plotted. */
|
||||
val (minTemp, maxTemp) = remember(key1 = telemetries) {
|
||||
Pair(
|
||||
telemetries.minBy { it.environmentMetrics.temperature },
|
||||
|
@ -221,182 +220,128 @@ private fun EnvironmentMetricsChart(
|
|||
)
|
||||
val diff = max - min
|
||||
|
||||
Box(contentAlignment = Alignment.TopStart) {
|
||||
ChartOverlay(
|
||||
modifier = modifier,
|
||||
labelColor = graphColor,
|
||||
lineColors = List(size = 5) { graphColor },
|
||||
val scrollState = rememberScrollState()
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||
val dp by remember(key1 = selectedTime) {
|
||||
mutableStateOf(selectedTime.dp(screenWidth, time = timeDiff.toLong()))
|
||||
}
|
||||
|
||||
Row {
|
||||
Box(
|
||||
contentAlignment = Alignment.TopStart,
|
||||
modifier = Modifier
|
||||
.horizontalScroll(state = scrollState, reverseScrolling = true)
|
||||
.weight(weight = 1f)
|
||||
) {
|
||||
|
||||
HorizontalLinesOverlay(
|
||||
modifier.width(dp),
|
||||
lineColors = List(size = 5) { graphColor }
|
||||
)
|
||||
|
||||
TimeAxisOverlay(
|
||||
modifier = modifier.width(dp),
|
||||
oldest = oldest.time,
|
||||
newest = newest.time,
|
||||
selectedTime.lineInterval()
|
||||
)
|
||||
|
||||
Canvas(modifier = modifier.width(dp)) {
|
||||
val height = size.height
|
||||
val width = size.width
|
||||
|
||||
/* Temperature */
|
||||
var index = 0
|
||||
var first: Int
|
||||
while (index < telemetries.size) {
|
||||
first = index
|
||||
val path = Path()
|
||||
index = createPath(
|
||||
telemetries = telemetries,
|
||||
index = index,
|
||||
path = path,
|
||||
oldestTime = oldest.time,
|
||||
timeRange = timeDiff,
|
||||
width = width,
|
||||
timeThreshold = selectedTime.timeThreshold()
|
||||
) { i ->
|
||||
val telemetry = telemetries.getOrNull(i) ?: telemetries.last()
|
||||
val ratio = (telemetry.environmentMetrics.temperature - min) / diff
|
||||
val y = height - (ratio * height)
|
||||
return@createPath y
|
||||
}
|
||||
drawPathWithGradient(
|
||||
path = path,
|
||||
color = Environment.TEMPERATURE.color,
|
||||
height = height,
|
||||
x1 = ((telemetries[index - 1].time - oldest.time).toFloat() / timeDiff) * width,
|
||||
x2 = ((telemetries[first].time - oldest.time).toFloat() / timeDiff) * width
|
||||
)
|
||||
}
|
||||
|
||||
/* Relative Humidity */
|
||||
index = 0
|
||||
while (index < telemetries.size) {
|
||||
first = index
|
||||
val path = Path()
|
||||
index = createPath(
|
||||
telemetries = telemetries,
|
||||
index = index,
|
||||
path = path,
|
||||
oldestTime = oldest.time,
|
||||
timeRange = timeDiff,
|
||||
width = width,
|
||||
timeThreshold = selectedTime.timeThreshold()
|
||||
) { i ->
|
||||
val telemetry = telemetries.getOrNull(i) ?: telemetries.last()
|
||||
val ratio = (telemetry.environmentMetrics.relativeHumidity - min) / diff
|
||||
val y = height - (ratio * height)
|
||||
return@createPath y
|
||||
}
|
||||
drawPathWithGradient(
|
||||
path = path,
|
||||
color = Environment.HUMIDITY.color,
|
||||
height = height,
|
||||
x1 = ((telemetries[index - 1].time - oldest.time).toFloat() / timeDiff) * width,
|
||||
x2 = ((telemetries[first].time - oldest.time).toFloat() / timeDiff) * width
|
||||
)
|
||||
}
|
||||
|
||||
/* Air Quality */
|
||||
index = 0
|
||||
while (index < telemetries.size) {
|
||||
first = index
|
||||
val path = Path()
|
||||
index = createPath(
|
||||
telemetries = telemetries,
|
||||
index = index,
|
||||
path = path,
|
||||
oldestTime = oldest.time,
|
||||
timeRange = timeDiff,
|
||||
width = width,
|
||||
timeThreshold = selectedTime.timeThreshold()
|
||||
) { i ->
|
||||
val telemetry = telemetries.getOrNull(i) ?: telemetries.last()
|
||||
val ratio = (telemetry.environmentMetrics.iaq - min) / diff
|
||||
val y = height - (ratio * height)
|
||||
return@createPath y
|
||||
}
|
||||
drawPathWithGradient(
|
||||
path = path,
|
||||
color = Environment.IAQ.color,
|
||||
height = height,
|
||||
x1 = ((telemetries[index - 1].time - oldest.time).toFloat() / timeDiff) * width,
|
||||
x2 = ((telemetries[first].time - oldest.time).toFloat() / timeDiff) * width
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
YAxisLabels(
|
||||
modifier = modifier.weight(weight = .1f),
|
||||
graphColor,
|
||||
minValue = min,
|
||||
maxValue = max
|
||||
)
|
||||
|
||||
/* Plot Temperature and Relative Humidity */
|
||||
Canvas(modifier = modifier) {
|
||||
val height = size.height
|
||||
val width = size.width - 28.dp.toPx()
|
||||
val spacePerEntry = (width - spacing) / telemetries.size
|
||||
|
||||
/* Temperature */
|
||||
var lastTempX = 0f
|
||||
val temperaturePath = Path().apply {
|
||||
for (i in telemetries.indices) {
|
||||
val envMetrics = telemetries[i].environmentMetrics
|
||||
val nextEnvMetrics =
|
||||
(telemetries.getOrNull(i + 1) ?: telemetries.last()).environmentMetrics
|
||||
val leftRatio = (envMetrics.temperature - min) / diff
|
||||
val rightRatio = (nextEnvMetrics.temperature - min) / diff
|
||||
|
||||
val x1 = spacing + i * spacePerEntry
|
||||
val y1 = height - (leftRatio * height)
|
||||
|
||||
val x2 = spacing + (i + 1) * spacePerEntry
|
||||
val y2 = height - (rightRatio * height)
|
||||
if (i == 0) {
|
||||
moveTo(x1, y1)
|
||||
}
|
||||
lastTempX = (x1 + x2) / 2f
|
||||
quadraticTo(
|
||||
x1, y1, lastTempX, (y1 + y2) / 2f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val fillPath = android.graphics.Path(temperaturePath.asAndroidPath())
|
||||
.asComposePath()
|
||||
.apply {
|
||||
lineTo(lastTempX, height)
|
||||
lineTo(spacing, height)
|
||||
close()
|
||||
}
|
||||
|
||||
drawPath(
|
||||
path = fillPath,
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
transparentTemperatureColor,
|
||||
Color.Transparent
|
||||
),
|
||||
endY = height
|
||||
),
|
||||
)
|
||||
|
||||
drawPath(
|
||||
path = temperaturePath,
|
||||
color = Environment.TEMPERATURE.color,
|
||||
style = Stroke(
|
||||
width = 2.dp.toPx(),
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
)
|
||||
|
||||
/* Relative Humidity */
|
||||
var lastHumidityX = 0f
|
||||
val humidityPath = Path().apply {
|
||||
for (i in telemetries.indices) {
|
||||
val envMetrics = telemetries[i].environmentMetrics
|
||||
val nextEnvMetrics =
|
||||
(telemetries.getOrNull(i + 1) ?: telemetries.last()).environmentMetrics
|
||||
val leftRatio = (envMetrics.relativeHumidity - min) / diff
|
||||
val rightRatio = (nextEnvMetrics.relativeHumidity - min) / diff
|
||||
|
||||
val x1 = spacing + i * spacePerEntry
|
||||
val y1 = height - (leftRatio * height)
|
||||
|
||||
val x2 = spacing + (i + 1) * spacePerEntry
|
||||
val y2 = height - (rightRatio * height)
|
||||
if (i == 0) {
|
||||
moveTo(x1, y1)
|
||||
}
|
||||
lastHumidityX = (x1 + x2) / 2f
|
||||
quadraticTo(
|
||||
x1, y1, lastHumidityX, (y1 + y2) / 2f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val fillHumidityPath = android.graphics.Path(humidityPath.asAndroidPath())
|
||||
.asComposePath()
|
||||
.apply {
|
||||
lineTo(lastHumidityX, height)
|
||||
lineTo(spacing, height)
|
||||
close()
|
||||
}
|
||||
|
||||
drawPath(
|
||||
path = fillHumidityPath,
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
transparentHumidityColor,
|
||||
Color.Transparent
|
||||
),
|
||||
endY = height
|
||||
),
|
||||
)
|
||||
|
||||
drawPath(
|
||||
path = humidityPath,
|
||||
color = Environment.HUMIDITY.color,
|
||||
style = Stroke(
|
||||
width = 2.dp.toPx(),
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
)
|
||||
|
||||
/* Air Quality */
|
||||
var lastIaqX = 0f
|
||||
val iaqPath = Path().apply {
|
||||
for (i in telemetries.indices) {
|
||||
val envMetrics = telemetries[i].environmentMetrics
|
||||
val nextEnvMetrics =
|
||||
(telemetries.getOrNull(i + 1) ?: telemetries.last()).environmentMetrics
|
||||
val leftRatio = (envMetrics.iaq - min) / diff
|
||||
val rightRatio = (nextEnvMetrics.iaq - min) / diff
|
||||
|
||||
val x1 = spacing + i * spacePerEntry
|
||||
val y1 = height - (leftRatio * height)
|
||||
|
||||
val x2 = spacing + (i + 1) * spacePerEntry
|
||||
val y2 = height - (rightRatio * height)
|
||||
if (i == 0) {
|
||||
moveTo(x1, y1)
|
||||
}
|
||||
lastIaqX = (x1 + x2) / 2f
|
||||
quadraticTo(
|
||||
x1,
|
||||
y1,
|
||||
lastIaqX,
|
||||
(y1 + y2) / 2f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val fillIaqPath = android.graphics.Path(iaqPath.asAndroidPath())
|
||||
.asComposePath()
|
||||
.apply {
|
||||
lineTo(lastIaqX, height)
|
||||
lineTo(spacing, height)
|
||||
close()
|
||||
}
|
||||
drawPath(
|
||||
path = fillIaqPath,
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
transparentIAQColor,
|
||||
Color.Transparent
|
||||
),
|
||||
endY = height
|
||||
),
|
||||
)
|
||||
|
||||
drawPath(
|
||||
path = iaqPath,
|
||||
color = Environment.IAQ.color,
|
||||
style = Stroke(
|
||||
width = 2.dp.toPx(),
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
|
|
@ -20,8 +20,15 @@ package com.geeksville.mesh.util
|
|||
import android.content.res.Resources
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.asAndroidPath
|
||||
import androidx.compose.ui.graphics.asComposePath
|
||||
import androidx.compose.ui.graphics.drawscope.DrawContext
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.TelemetryProtos.Telemetry
|
||||
|
||||
object GraphUtil {
|
||||
|
@ -104,4 +111,38 @@ object GraphUtil {
|
|||
}
|
||||
return i
|
||||
}
|
||||
|
||||
fun DrawScope.drawPathWithGradient(
|
||||
path: Path,
|
||||
color: Color,
|
||||
height: Float,
|
||||
x1: Float,
|
||||
x2: Float
|
||||
) {
|
||||
drawPath(
|
||||
path = path,
|
||||
color = color,
|
||||
style = Stroke(
|
||||
width = 2.dp.toPx(),
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
)
|
||||
val fillPath = android.graphics.Path(path.asAndroidPath())
|
||||
.asComposePath()
|
||||
.apply {
|
||||
lineTo(x1, height)
|
||||
lineTo(x2, height)
|
||||
close()
|
||||
}
|
||||
drawPath(
|
||||
path = fillPath,
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
color.copy(alpha = 0.5f),
|
||||
Color.Transparent
|
||||
),
|
||||
endY = height
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue