From 843e423648edbac0dc42b153b1c041602100162c Mon Sep 17 00:00:00 2001 From: Robert-0410 <62630290+Robert-0410@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:57:01 -0700 Subject: [PATCH] Device metric lines and info dialog (#1252) * Removed constants from CommonCharts only used in specific charts. * Altered CommonCharts.ChartOverlay to take a list of colors for the lines. Adjusted the device metrics line colors for channel utilization. * Started an info dialog in the device metric chart to help users better understand Meshtastic. --- .../mesh/ui/components/CommonCharts.kt | 26 ++-- .../mesh/ui/components/DeviceMetrics.kt | 112 ++++++++++++++++-- .../mesh/ui/components/EnvironmentMetrics.kt | 11 +- app/src/main/res/values/strings.xml | 3 + 4 files changed, 125 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/CommonCharts.kt b/app/src/main/java/com/geeksville/mesh/ui/components/CommonCharts.kt index c766b86a0..ee56eabf9 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/CommonCharts.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/CommonCharts.kt @@ -27,28 +27,21 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.geeksville.mesh.R -import com.geeksville.mesh.ui.components.CommonCharts.LINE_OFF -import com.geeksville.mesh.ui.components.CommonCharts.LINE_ON import com.geeksville.mesh.ui.components.CommonCharts.TIME_FORMAT -import com.geeksville.mesh.ui.components.CommonCharts.LINE_LIMIT -import com.geeksville.mesh.ui.components.CommonCharts.TEXT_PAINT_ALPHA -import com.geeksville.mesh.ui.theme.Orange import java.text.DateFormat object CommonCharts { - val DEVICE_METRICS_COLORS = listOf(Color.Green, Color.Magenta, Color.Cyan) - val ENVIRONMENT_METRICS_COLORS = listOf(Color.Red, Color.Blue) val TIME_FORMAT: DateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM) - const val MAX_PERCENT_VALUE = 100f - const val LINE_LIMIT = 4 - const val TEXT_PAINT_ALPHA = 192 - const val LINE_ON = 10f - const val LINE_OFF = 20f const val LEFT_CHART_SPACING = 8f const val MS_PER_SEC = 1000.0f } +private const val LINE_LIMIT = 4 +private const val TEXT_PAINT_ALPHA = 192 +private const val LINE_ON = 10f +private const val LINE_OFF = 20f + @Composable fun ChartHeader(amount: Int) { @@ -68,11 +61,13 @@ fun ChartHeader(amount: Int) { /** * Draws chart lines and labels with respect to the Y-axis range; defined by (`maxValue` - `minValue`). + * Assumes `lineColors` is a list of 5 `Color`s with index 0 being the lowest line on the chart. */ @Composable fun ChartOverlay( modifier: Modifier, graphColor: Color, + lineColors: List, minValue: Float, maxValue: Float ) { @@ -89,15 +84,10 @@ fun ChartOverlay( for (i in 0..LINE_LIMIT) { val ratio = (lineY - minValue) / range val y = height - (ratio * height) - val color: Color = when (i) { - 1 -> Color.Red - 2 -> Orange - else -> graphColor - } drawLine( start = Offset(0f, y), end = Offset(width, y), - color = color, + color = lineColors[i], strokeWidth = 1.dp.toPx(), cap = StrokeCap.Round, pathEffect = PathEffect.dashPathEffect(floatArrayOf(LINE_ON, LINE_OFF), 0f) diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/DeviceMetrics.kt b/app/src/main/java/com/geeksville/mesh/ui/components/DeviceMetrics.kt index e73577d0e..c4f9cbcf2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/DeviceMetrics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/DeviceMetrics.kt @@ -1,6 +1,7 @@ package com.geeksville.mesh.ui.components import androidx.compose.foundation.Canvas +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,39 +16,62 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.AlertDialog import androidx.compose.material.Card +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.geeksville.mesh.R import com.geeksville.mesh.TelemetryProtos.Telemetry import com.geeksville.mesh.ui.BatteryInfo -import com.geeksville.mesh.ui.components.CommonCharts.DEVICE_METRICS_COLORS import com.geeksville.mesh.ui.components.CommonCharts.LEFT_CHART_SPACING -import com.geeksville.mesh.ui.components.CommonCharts.MAX_PERCENT_VALUE import com.geeksville.mesh.ui.components.CommonCharts.MS_PER_SEC import com.geeksville.mesh.ui.components.CommonCharts.TIME_FORMAT +import com.geeksville.mesh.ui.theme.Orange +private val DEVICE_METRICS_COLORS = listOf(Color.Green, Color.Magenta, Color.Cyan) +private const val MAX_PERCENT_VALUE = 100f + @Composable fun DeviceMetricsScreen(telemetries: List) { + + var displayInfoDialog by remember { mutableStateOf(false) } + Column { + + if (displayInfoDialog) + DeviceInfoDialog { displayInfoDialog = false } + DeviceMetricsChart( modifier = Modifier .fillMaxWidth() .fillMaxHeight(fraction = 0.33f), - telemetries.reversed() + telemetries.reversed(), + promptInfoDialog = { displayInfoDialog = true } ) /* Device Metric Cards */ LazyColumn( @@ -60,7 +84,11 @@ fun DeviceMetricsScreen(telemetries: List) { @Suppress("LongMethod") @Composable -private fun DeviceMetricsChart(modifier: Modifier = Modifier, telemetries: List) { +private fun DeviceMetricsChart( + modifier: Modifier = Modifier, + telemetries: List, + promptInfoDialog: () -> Unit +) { ChartHeader(amount = telemetries.size) if (telemetries.isEmpty()) @@ -73,7 +101,18 @@ private fun DeviceMetricsChart(modifier: Modifier = Modifier, telemetries: List< Box(contentAlignment = Alignment.TopStart) { - ChartOverlay(modifier, graphColor, minValue = 0f, maxValue = 100f) + /* + * The order of the colors are with respect to the ChUtil. + * 25 - 49 Orange + * 50 - 100 Red + */ + ChartOverlay( + modifier, + graphColor, + lineColors = listOf(graphColor, Orange, Color.Red, graphColor, graphColor), + minValue = 0f, + maxValue = 100f + ) /* Plot Battery Line, ChUtil, and AirUtilTx */ Canvas(modifier = modifier) { @@ -142,7 +181,7 @@ private fun DeviceMetricsChart(modifier: Modifier = Modifier, telemetries: List< } Spacer(modifier = Modifier.height(16.dp)) - DeviceLegend() + DeviceLegend(promptInfoDialog) Spacer(modifier = Modifier.height(16.dp)) } @@ -205,7 +244,7 @@ private fun DeviceMetricsCard(telemetry: Telemetry) { } @Composable -private fun DeviceLegend() { +private fun DeviceLegend(promptInfoDialog: () -> Unit) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, @@ -223,6 +262,65 @@ private fun DeviceLegend() { LegendLabel(text = stringResource(R.string.air_utilization), color = DEVICE_METRICS_COLORS[2]) + Spacer(modifier = Modifier.width(4.dp)) + + Icon( + imageVector = Icons.Default.Info, + modifier = Modifier.clickable { promptInfoDialog() }, + contentDescription = stringResource(R.string.info) + ) + Spacer(modifier = Modifier.weight(1f)) } } + +@Composable +private fun DeviceInfoDialog(onDismiss: () -> Unit) { + AlertDialog( + title = { + Text( + text = stringResource(R.string.info), + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + }, + text = { + Column { + Text( + text = stringResource(R.string.channel_utilization), + style = TextStyle(fontWeight = FontWeight.Bold), + textDecoration = TextDecoration.Underline + ) + Text( + text = stringResource(R.string.ch_util_definition), + style = TextStyle.Default, + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = stringResource(R.string.air_utilization), + style = TextStyle(fontWeight = FontWeight.Bold), + textDecoration = TextDecoration.Underline + ) + Text( + text = stringResource(R.string.air_util_definition), + style = TextStyle.Default + ) + } + }, + onDismissRequest = onDismiss, + confirmButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.okay)) + } + }, + backgroundColor = MaterialTheme.colors.background + ) +} + +@Preview +@Composable +private fun DeviceInfoDialogPreview() { + DeviceInfoDialog {} +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt b/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt index 7937adf2b..cb787ff91 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt @@ -36,12 +36,13 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.geeksville.mesh.R import com.geeksville.mesh.TelemetryProtos.Telemetry -import com.geeksville.mesh.ui.components.CommonCharts.ENVIRONMENT_METRICS_COLORS import com.geeksville.mesh.ui.components.CommonCharts.LEFT_CHART_SPACING import com.geeksville.mesh.ui.components.CommonCharts.MS_PER_SEC import com.geeksville.mesh.ui.components.CommonCharts.TIME_FORMAT +private val ENVIRONMENT_METRICS_COLORS = listOf(Color.Red, Color.Blue) + @Composable fun EnvironmentMetricsScreen(telemetries: List) { Column { @@ -95,7 +96,13 @@ private fun EnvironmentMetricsChart(modifier: Modifier = Modifier, telemetries: Box(contentAlignment = Alignment.TopStart) { - ChartOverlay(modifier = modifier, graphColor = graphColor, minValue = min, maxValue = max) + ChartOverlay( + modifier = modifier, + graphColor = graphColor, + lineColors = List(size = 5) { graphColor }, + minValue = min, + maxValue = max + ) /* Plot Temperature and Relative Humidity */ Canvas(modifier = modifier) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e6901ec3..53ed1bdff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -227,4 +227,7 @@ Humidity Logs Hops Away + Information + Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise). + Percent of airtime for transmission used within the last hour.