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.
This commit is contained in:
James Rich 2024-09-18 17:37:55 -05:00 committed by GitHub
parent 825516e9ac
commit 98d11115c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 14 deletions

View file

@ -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<Telemetry> = emptyList(),
val environmentMetrics: List<Telemetry> = 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<Telemetry>()
val environmentList = mutableListOf<Telemetry>()
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

View file

@ -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
)
}
}
}

View file

@ -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<Telemetry>) {
fun EnvironmentMetricsScreen(telemetries: List<Telemetry>, environmentDisplayFahrenheit: Boolean) {
/* Convert Celsius to Fahrenheit */
@Suppress("MagicNumber")
fun celsiusToFahrenheit(celsius: Float): Float {
return (celsius * 1.8F) + 32
}
val processedTelemetries: List<Telemetry> = 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
),