From 98d11115c61875229ebb54d064908b8b724086c6 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:37:55 -0500 Subject: [PATCH] feat: Add ability to display environment metrics in Fahrenheit (#1253) * feat: Add ability to display environment metrics in Fahrenheit The temperature values in the environment metrics charts and cards are now displayed in Fahrenheit or Celsius based on the user's preference. Celsius is still used as the base unit for calculations and storage. * Refactor: Rename environmentDisplayTempInFahrenheit to environmentDisplayFahrenheit Renamed the variable environmentDisplayTempInFahrenheit to environmentDisplayFahrenheit for better clarity and consistency. * Refactor: Remove unused ENVIRONMENT_METRICS_COLORS Removed the unused `ENVIRONMENT_METRICS_COLORS` variable from the `EnvironmentMetrics` component. * Update: Add support for plotting iaq Added green to the list of colors used for plotting environment metrics to support the newly added iaq readings. --- .../geeksville/mesh/model/MetricsViewModel.kt | 15 +++++-- .../com/geeksville/mesh/ui/MetricsFragment.kt | 8 +++- .../mesh/ui/components/EnvironmentMetrics.kt | 40 +++++++++++++++---- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt index 256049861..f5f2531d3 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope import com.geeksville.mesh.R import com.geeksville.mesh.TelemetryProtos.Telemetry import com.geeksville.mesh.database.MeshLogRepository +import com.geeksville.mesh.repository.datastore.RadioConfigRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed @@ -29,6 +30,7 @@ data class MetricsState( val isLoading: Boolean = false, val deviceMetrics: List = emptyList(), val environmentMetrics: List = emptyList(), + val environmentDisplayFahrenheit: Boolean = false, ) { companion object { val Empty = MetricsState() @@ -38,7 +40,8 @@ data class MetricsState( @HiltViewModel class MetricsViewModel @Inject constructor( val nodeDB: NodeDB, - private val meshLogRepository: MeshLogRepository + private val meshLogRepository: MeshLogRepository, + radioConfigRepository: RadioConfigRepository, ) : ViewModel() { private val isLoading = MutableStateFlow(false) @@ -49,11 +52,13 @@ class MetricsViewModel @Inject constructor( isLoading, _deviceMetrics, _environmentMetrics, - ) { isLoading, device, environment -> + radioConfigRepository.deviceProfileFlow, + ) { isLoading, device, environment, profile -> MetricsState( isLoading = isLoading, deviceMetrics = device, environmentMetrics = environment, + environmentDisplayFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit, ) }.stateIn( scope = viewModelScope, @@ -76,11 +81,13 @@ class MetricsViewModel @Inject constructor( val deviceList = mutableListOf() val environmentList = mutableListOf() for (telemetry in it) { - if (telemetry.hasDeviceMetrics()) + if (telemetry.hasDeviceMetrics()) { deviceList.add(telemetry) + } /* Avoiding negative outliers */ - if (telemetry.hasEnvironmentMetrics() && telemetry.environmentMetrics.relativeHumidity >= 0f) + if (telemetry.hasEnvironmentMetrics() && telemetry.environmentMetrics.relativeHumidity >= 0f) { environmentList.add(telemetry) + } } _deviceMetrics.value = deviceList _environmentMetrics.value = environmentList diff --git a/app/src/main/java/com/geeksville/mesh/ui/MetricsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MetricsFragment.kt index e3dbf235f..db73f1695 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MetricsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MetricsFragment.kt @@ -70,8 +70,9 @@ class MetricsFragment : ScreenFragment("Metrics"), Logging { savedInstanceState: Bundle? ): View { val nodeNum = arguments?.getInt("nodeNum") - if (nodeNum != null) + if (nodeNum != null) { model.setSelectedNode(nodeNum) + } val nodeName = model.getNodeName(nodeNum ?: 0) @@ -176,7 +177,10 @@ fun MetricsPagerScreen( } else { when (pages[index]) { MetricsPage.DEVICE -> DeviceMetricsScreen(deviceMetrics) - MetricsPage.ENVIRONMENT -> EnvironmentMetricsScreen(environmentMetrics) + MetricsPage.ENVIRONMENT -> EnvironmentMetricsScreen( + environmentMetrics, + state.environmentDisplayFahrenheit + ) } } } 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 e49464ccc..23010b166 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,28 +36,53 @@ 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.copy 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) +private val ENVIRONMENT_METRICS_COLORS = listOf(Color.Red, Color.Blue, Color.Green) @Composable -fun EnvironmentMetricsScreen(telemetries: List) { +fun EnvironmentMetricsScreen(telemetries: List, environmentDisplayFahrenheit: Boolean) { + /* Convert Celsius to Fahrenheit */ + @Suppress("MagicNumber") + fun celsiusToFahrenheit(celsius: Float): Float { + return (celsius * 1.8F) + 32 + } + + val processedTelemetries: List = if (environmentDisplayFahrenheit) { + telemetries.map { telemetry -> + val temperatureFahrenheit = + celsiusToFahrenheit(telemetry.environmentMetrics.temperature) + telemetry.copy { + environmentMetrics = + telemetry.environmentMetrics.copy { temperature = temperatureFahrenheit } + } + } + } else { + telemetries + } + Column { EnvironmentMetricsChart( modifier = Modifier .fillMaxWidth() .fillMaxHeight(fraction = 0.33f), - telemetries = telemetries.reversed() + telemetries = processedTelemetries.reversed(), ) /* Environment Metric Cards */ LazyColumn( modifier = Modifier.fillMaxSize() ) { - items(telemetries) { telemetry -> EnvironmentMetricsCard(telemetry) } + items(processedTelemetries) { telemetry -> + EnvironmentMetricsCard( + telemetry, + environmentDisplayFahrenheit + ) + } } } } @@ -110,7 +135,6 @@ private fun EnvironmentMetricsChart(modifier: Modifier = Modifier, telemetries: val diff = max - min Box(contentAlignment = Alignment.TopStart) { - ChartOverlay( modifier = modifier, graphColor = graphColor, @@ -304,7 +328,7 @@ private fun EnvironmentMetricsChart(modifier: Modifier = Modifier, telemetries: @Suppress("LongMethod") @Composable -private fun EnvironmentMetricsCard(telemetry: Telemetry) { +private fun EnvironmentMetricsCard(telemetry: Telemetry, environmentDisplayFahrenheit: Boolean) { val envMetrics = telemetry.environmentMetrics val time = telemetry.time * MS_PER_SEC Card( @@ -330,9 +354,9 @@ private fun EnvironmentMetricsCard(telemetry: Telemetry) { style = TextStyle(fontWeight = FontWeight.Bold), fontSize = MaterialTheme.typography.button.fontSize ) - + val textFormat = if (environmentDisplayFahrenheit) "%s %.1f°F" else "%s %.1f°C" Text( - text = "%s %.1f°C".format( + text = textFormat.format( stringResource(id = R.string.temperature), envMetrics.temperature ),