feat: migrate to Material 3 Expressive APIs (#4934)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich
2026-03-26 11:42:46 -05:00
committed by GitHub
parent c259c76550
commit 141b54ff9c
12 changed files with 112 additions and 103 deletions

View File

@@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -39,7 +40,7 @@ import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.ic_meshtastic
import org.meshtastic.core.resources.navigate_back
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
fun MainAppBar(
modifier: Modifier = Modifier,
@@ -54,26 +55,25 @@ fun MainAppBar(
) {
TopAppBar(
title = {
androidx.compose.foundation.layout.Column {
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge,
)
},
subtitle = {
subtitle?.let {
Text(
text = title,
text = it,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
subtitle?.let {
Text(
text = it,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
},
modifier = modifier,
navigationIcon =
if (canNavigateUp) {
{
IconButton(onClick = onNavigateUp) {

View File

@@ -16,34 +16,23 @@
*/
package org.meshtastic.core.ui.component
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.OfflineShare
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButtonMenu
import androidx.compose.material3.FloatingActionButtonMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleFloatingActionButton
import androidx.compose.material3.ToggleFloatingActionButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MenuFAB(
expanded: Boolean,
@@ -53,61 +42,31 @@ fun MenuFAB(
contentDescription: String? = null,
testTag: String? = null,
) {
Column(
FloatingActionButtonMenu(
modifier = modifier.then(if (testTag != null) Modifier.testTag(testTag) else Modifier),
expanded = expanded,
button = {
ToggleFloatingActionButton(
checked = expanded,
onCheckedChange = onExpandedChange,
content = {
val imageVector = if (expanded) Icons.Filled.Close else Icons.AutoMirrored.Rounded.OfflineShare
Icon(imageVector = imageVector, contentDescription = contentDescription)
},
containerColor = ToggleFloatingActionButtonDefaults.containerColor(),
)
},
horizontalAlignment = Alignment.End,
) {
AnimatedVisibility(
visible = expanded,
enter = fadeIn() + slideInVertically(initialOffsetY = { it / 2 }),
exit = fadeOut() + slideOutVertically(targetOffsetY = { it / 2 }),
) {
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(bottom = 16.dp),
) {
items.forEach { item ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = if (item.testTag != null) Modifier.testTag(item.testTag) else Modifier,
) {
Surface(
shape = MaterialTheme.shapes.small,
color = MaterialTheme.colorScheme.surfaceContainerHigh,
modifier = Modifier.padding(end = 8.dp),
) {
Text(
text = item.label,
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
)
}
SmallFloatingActionButton(
onClick = {
item.onClick()
onExpandedChange(false)
},
) {
Icon(item.icon, contentDescription = item.label)
}
}
}
}
}
val rotation by animateFloatAsState(targetValue = if (expanded) 180f else 0f, label = "fab_rotation")
FloatingActionButton(
onClick = { onExpandedChange(!expanded) },
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
) {
Icon(
imageVector = if (expanded) Icons.Filled.Close else Icons.AutoMirrored.Rounded.OfflineShare,
contentDescription = contentDescription,
modifier = Modifier.rotate(rotation),
items.forEach { item ->
FloatingActionButtonMenuItem(
modifier = if (item.testTag != null) Modifier.testTag(item.testTag) else Modifier,
onClick = {
item.onClick()
onExpandedChange(false)
},
icon = { Icon(item.icon, contentDescription = null) },
text = { Text(item.label) },
)
}
}

View File

@@ -19,7 +19,9 @@
package org.meshtastic.core.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MotionScheme.Companion.expressive
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
@@ -265,6 +267,7 @@ data class ColorFamily(val color: Color, val onColor: Color, val colorContainer:
val unspecified_scheme = ColorFamily(Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified)
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
@@ -276,7 +279,12 @@ fun AppTheme(
val dynamicScheme = if (dynamicColor) dynamicColorScheme(darkTheme) else null
val colorScheme = dynamicScheme ?: if (darkTheme) darkScheme else lightScheme
MaterialTheme(colorScheme = colorScheme, typography = AppTypography, content = content)
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = AppTypography,
motionScheme = expressive(),
content = content,
)
}
const val MODE_DYNAMIC = 6969420

View File

@@ -33,6 +33,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.Notes
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -95,6 +96,7 @@ private const val ACTIVE_ALPHA = 0.5f
private const val INACTIVE_ALPHA = 0.2f
private const val GRID_COLUMNS = 3
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Suppress("LongMethod")
fun NodeItem(
@@ -389,6 +391,7 @@ private fun MetricsGrid(items: List<@Composable () -> Unit>) {
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun NodeItemHeader(
thatNode: Node,
@@ -424,7 +427,7 @@ private fun NodeItemHeader(
) {
Text(
text = longName,
style = MaterialTheme.typography.titleMedium.copy(fontStyle = style),
style = MaterialTheme.typography.titleMediumEmphasized.copy(fontStyle = style),
textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
maxLines = 1,
overflow = TextOverflow.Ellipsis,

View File

@@ -30,8 +30,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
@@ -62,6 +64,7 @@ import org.meshtastic.feature.node.component.NodeFilterTextField
import org.meshtastic.feature.node.component.NodeItem
@Suppress("LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun NodeListScreen(
navigateToNodeDetails: (Int) -> Unit,
@@ -114,16 +117,19 @@ fun NodeListScreen(
},
floatingActionButton = {
val shareCapable = ourNode?.capabilities?.supportsQrCodeSharing ?: false
if (!isScrollInProgress && connectionState == ConnectionState.Connected && shareCapable) {
MeshtasticImportFAB(
onImport = { uriString ->
onHandleDeepLink(org.meshtastic.core.common.util.MeshtasticUri(uriString)) {
scope.launch { showToast(Res.string.channel_invalid) }
}
},
isContactContext = true,
)
}
MeshtasticImportFAB(
modifier =
Modifier.animateFloatingActionButton(
visible = !isScrollInProgress && connectionState == ConnectionState.Connected && shareCapable,
alignment = androidx.compose.ui.Alignment.BottomEnd,
),
onImport = { uriString ->
onHandleDeepLink(org.meshtastic.core.common.util.MeshtasticUri(uriString)) {
scope.launch { showToast(Res.string.channel_invalid) }
}
},
isContactContext = true,
)
},
) { contentPadding ->
Box(modifier = Modifier.fillMaxSize().padding(contentPadding).focusable()) {

View File

@@ -35,6 +35,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -214,6 +215,7 @@ fun DeviceMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun DeviceMetricsChart(
modifier: Modifier = Modifier,
telemetries: List<Telemetry>,
@@ -386,6 +388,7 @@ private fun DeviceMetricsChart(
@Suppress("detekt:MagicNumber", "UnusedPrivateMember") // Compose preview with fake data
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun DeviceMetricsChartPreview() {
val now = nowSeconds.toInt()
val telemetries =
@@ -416,6 +419,7 @@ private fun DeviceMetricsChartPreview() {
@Composable
@Suppress("LongMethod")
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun DeviceMetricsCard(telemetry: Telemetry, isSelected: Boolean, onClick: () -> Unit) {
val deviceMetrics = telemetry.device_metrics
val time = telemetry.time.toLong() * MS_PER_SEC
@@ -444,7 +448,7 @@ private fun DeviceMetricsCard(telemetry: Telemetry, isSelected: Boolean, onClick
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
text = CommonCharts.formatDateTime(time),
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.titleMediumEmphasized,
fontWeight = FontWeight.Bold,
)
@@ -518,6 +522,7 @@ private fun DeviceMetricsCard(telemetry: Telemetry, isSelected: Boolean, onClick
@Suppress("detekt:MagicNumber", "UnusedPrivateMember") // Compose preview with fake data
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun DeviceMetricsCardPreview() {
val now = nowSeconds.toInt()
val telemetry =
@@ -537,6 +542,7 @@ private fun DeviceMetricsCardPreview() {
@Suppress("detekt:MagicNumber", "UnusedPrivateMember") // Compose preview with fake data
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun DeviceMetricsScreenPreview() {
val now = nowSeconds.toInt()
val telemetries =

View File

@@ -34,6 +34,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -119,6 +120,7 @@ fun EnvironmentMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Un
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun TemperatureDisplay(
envMetrics: org.meshtastic.proto.EnvironmentMetrics,
environmentDisplayFahrenheit: Boolean,
@@ -140,6 +142,7 @@ private fun TemperatureDisplay(
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun HumidityAndBarometricPressureDisplay(envMetrics: org.meshtastic.proto.EnvironmentMetrics) {
val hasHumidity = envMetrics.relative_humidity?.let { !it.isNaN() } == true
val hasPressure = envMetrics.barometric_pressure?.let { !it.isNaN() && it > 0 } == true
@@ -180,6 +183,7 @@ private fun HumidityAndBarometricPressureDisplay(envMetrics: org.meshtastic.prot
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun SoilMetricsDisplay(
envMetrics: org.meshtastic.proto.EnvironmentMetrics,
environmentDisplayFahrenheit: Boolean,
@@ -232,6 +236,7 @@ private fun SoilMetricsDisplay(
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun LuxUVLuxDisplay(envMetrics: org.meshtastic.proto.EnvironmentMetrics) {
val hasLux = envMetrics.lux != null && !envMetrics.lux!!.isNaN()
val hasUvLux = envMetrics.uv_lux != null && !envMetrics.uv_lux!!.isNaN()
@@ -267,6 +272,7 @@ private fun LuxUVLuxDisplay(envMetrics: org.meshtastic.proto.EnvironmentMetrics)
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun VoltageCurrentDisplay(envMetrics: org.meshtastic.proto.EnvironmentMetrics) {
val hasVoltage = envMetrics.voltage != null && !envMetrics.voltage!!.isNaN()
val hasCurrent = envMetrics.current != null && !envMetrics.current!!.isNaN()
@@ -294,6 +300,7 @@ private fun VoltageCurrentDisplay(envMetrics: org.meshtastic.proto.EnvironmentMe
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun GasCompositionDisplay(envMetrics: org.meshtastic.proto.EnvironmentMetrics) {
val iaqValue = envMetrics.iaq
val gasResistance = envMetrics.gas_resistance
@@ -329,6 +336,7 @@ private fun GasCompositionDisplay(envMetrics: org.meshtastic.proto.EnvironmentMe
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun RadiationDisplay(envMetrics: org.meshtastic.proto.EnvironmentMetrics) {
envMetrics.radiation?.let { radiation ->
if (!radiation.isNaN() && radiation > 0f) {
@@ -344,6 +352,7 @@ private fun RadiationDisplay(envMetrics: org.meshtastic.proto.EnvironmentMetrics
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun EnvironmentMetricsCard(
telemetry: Telemetry,
environmentDisplayFahrenheit: Boolean,
@@ -370,6 +379,7 @@ private fun EnvironmentMetricsCard(
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun EnvironmentMetricsContent(telemetry: Telemetry, environmentDisplayFahrenheit: Boolean) {
val envMetrics = telemetry.environment_metrics ?: org.meshtastic.proto.EnvironmentMetrics()
val time = telemetry.time.toLong() * MS_PER_SEC
@@ -378,7 +388,7 @@ private fun EnvironmentMetricsContent(telemetry: Telemetry, environmentDisplayFa
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
text = CommonCharts.formatDateTime(time),
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.titleMediumEmphasized,
fontWeight = FontWeight.Bold,
)
TemperatureDisplay(envMetrics, environmentDisplayFahrenheit)
@@ -401,6 +411,7 @@ private fun EnvironmentMetricsContent(telemetry: Telemetry, environmentDisplayFa
@Suppress("MagicNumber", "UnusedPrivateMember") // Compose preview with fake data
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun PreviewEnvironmentMetricsContent() {
val fakeEnvMetrics =
org.meshtastic.proto.EnvironmentMetrics(

View File

@@ -32,6 +32,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
@@ -103,6 +104,7 @@ fun HostMetricsLogScreen(metricsViewModel: MetricsViewModel, onNavigateUp: () ->
@Suppress("LongMethod", "MagicNumber")
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun HostMetricsItem(modifier: Modifier = Modifier, telemetry: Telemetry) {
val hostMetrics = telemetry.host_metrics
val time = telemetry.time.toLong() * TimeConstants.MS_PER_SEC
@@ -119,7 +121,7 @@ fun HostMetricsItem(modifier: Modifier = Modifier, telemetry: Telemetry) {
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.End,
text = DateFormatter.formatDateTime(time),
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.titleMediumEmphasized,
fontWeight = FontWeight.Bold,
)
hostMetrics?.uptime_seconds?.let {

View File

@@ -30,6 +30,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -48,6 +49,7 @@ import org.meshtastic.core.ui.icon.MeshtasticIcons
/** Shared metric log/list UI components used by TracerouteLog, NeighborInfoLog, HostMetricsLog, and PositionLog. */
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun MetricLogItem(icon: ImageVector, text: String, contentDescription: String, modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth().heightIn(min = 64.dp).padding(vertical = 4.dp, horizontal = 8.dp),
@@ -72,7 +74,7 @@ fun MetricLogItem(icon: ImageVector, text: String, contentDescription: String, m
}
Text(
text = text,
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.titleMediumEmphasized,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)

View File

@@ -30,6 +30,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -86,6 +87,7 @@ private val LEGEND_DATA =
@Suppress("LongMethod")
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun PaxMetricsChart(
modifier: Modifier = Modifier,
totalSeries: List<Pair<Int, Int>>,
@@ -262,6 +264,7 @@ fun PaxcountInfo(
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun PaxMetricsItem(log: MeshLog, pax: ProtoPaxcount, isSelected: Boolean, onClick: () -> Unit) {
Card(
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp).clickable { onClick() },
@@ -279,7 +282,7 @@ fun PaxMetricsItem(log: MeshLog, pax: ProtoPaxcount, isSelected: Boolean, onClic
Column(modifier = Modifier.fillMaxWidth().padding(12.dp)) {
Text(
text = DateFormatter.formatDateTime(log.received_date),
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.titleMediumEmphasized,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.End,
modifier = Modifier.fillMaxWidth(),

View File

@@ -34,6 +34,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -168,6 +169,7 @@ fun PowerMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
@Suppress("LongMethod")
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun PowerMetricsChart(
modifier: Modifier = Modifier,
telemetries: List<Telemetry>,
@@ -295,6 +297,7 @@ private fun PowerMetricsChart(
@Composable
@Suppress("CyclomaticComplexMethod")
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun PowerMetricsCard(telemetry: Telemetry, isSelected: Boolean, onClick: () -> Unit) {
val time = telemetry.time.toLong() * MS_PER_SEC
Card(
@@ -318,7 +321,7 @@ private fun PowerMetricsCard(telemetry: Telemetry, isSelected: Boolean, onClick:
Row {
Text(
text = CommonCharts.formatDateTime(time),
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.titleMediumEmphasized,
fontWeight = FontWeight.Bold,
)
}
@@ -347,6 +350,7 @@ private fun PowerMetricsCard(telemetry: Telemetry, isSelected: Boolean, onClick:
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun PowerChannelColumn(titleRes: StringResource, voltage: Float, current: Float) {
Column {
Text(
@@ -376,6 +380,7 @@ private fun PowerChannelColumn(titleRes: StringResource, voltage: Float, current
}
/** Retrieves the appropriate voltage depending on `channelSelected`. */
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun retrieveVoltage(channelSelected: PowerChannel, telemetry: Telemetry): Float = when (channelSelected) {
PowerChannel.ONE -> telemetry.power_metrics?.ch1_voltage ?: Float.NaN
PowerChannel.TWO -> telemetry.power_metrics?.ch2_voltage ?: Float.NaN
@@ -383,6 +388,7 @@ private fun retrieveVoltage(channelSelected: PowerChannel, telemetry: Telemetry)
}
/** Retrieves the appropriate current depending on `channelSelected`. */
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun retrieveCurrent(channelSelected: PowerChannel, telemetry: Telemetry): Float = when (channelSelected) {
PowerChannel.ONE -> telemetry.power_metrics?.ch1_current ?: Float.NaN
PowerChannel.TWO -> telemetry.power_metrics?.ch2_current ?: Float.NaN

View File

@@ -34,6 +34,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -134,6 +135,7 @@ fun SignalMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun SignalMetricsChart(
modifier: Modifier = Modifier,
meshPackets: List<MeshPacket>,
@@ -245,6 +247,7 @@ private fun SignalMetricsChart(
}
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun SignalMetricsCard(meshPacket: MeshPacket, isSelected: Boolean, onClick: () -> Unit) {
val time = meshPacket.rx_time.toLong() * MS_PER_SEC
Card(
@@ -270,7 +273,7 @@ private fun SignalMetricsCard(meshPacket: MeshPacket, isSelected: Boolean, onCli
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Text(
text = CommonCharts.formatDateTime(time),
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.titleMediumEmphasized,
fontWeight = FontWeight.Bold,
)
}