feat: add support for Device Hardware json and svg graphics (#1449)

* feat: add support for Device Hardware json and svg graphics

Allows for better hardware device display names, graphics, and indication of support.

* make detekt happy

* Fix: Use first image name to find vector drawable

Use the first image name from the `images` list (after removing the ".svg" suffix) to find the corresponding vector drawable resource.

* Refactor: Update device detail layout

Updated the device detail layout to group device-specific information under a "Device" category.
Added a circular background with device-specific color behind the device icon.
Moved hardware, support status details to the Device section.

* Refactor: Move device hardware logic to MetricsViewModel

Moves the logic for retrieving device hardware information and image resources from NodeDetail to MetricsViewModel.

Also replaces id lookup with when statement for image resource id mapping.

* fix: cache deviceHardwareList, add exception handling

* refactor: mutable list unnecessary

* default to hw_unknown device image
This commit is contained in:
James Rich 2024-12-10 09:02:57 -06:00 committed by GitHub
parent f08916764c
commit 993f659742
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 36489 additions and 13 deletions

View file

@ -15,10 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@file:Suppress("TooManyFunctions")
@file:Suppress("TooManyFunctions", "LongMethod")
package com.geeksville.mesh.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -36,6 +38,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
@ -64,6 +67,7 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.SignalCellularAlt
import androidx.compose.material.icons.filled.Speed
import androidx.compose.material.icons.filled.Thermostat
import androidx.compose.material.icons.filled.Verified
import androidx.compose.material.icons.filled.WaterDrop
import androidx.compose.material.icons.filled.Work
import androidx.compose.material.icons.outlined.Navigation
@ -71,6 +75,7 @@ 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.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
@ -98,8 +103,8 @@ import kotlin.math.ln
@Composable
fun NodeDetailScreen(
viewModel: MetricsViewModel = hiltViewModel(),
modifier: Modifier = Modifier,
viewModel: MetricsViewModel = hiltViewModel(),
onNavigate: (Any) -> Unit,
) {
val state by viewModel.state.collectAsStateWithLifecycle()
@ -124,15 +129,20 @@ fun NodeDetailScreen(
@Composable
private fun NodeDetailList(
modifier: Modifier = Modifier,
node: NodeEntity,
metricsState: MetricsState,
modifier: Modifier = Modifier,
onNavigate: (Any) -> Unit = {},
) {
LazyColumn(
modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 16.dp),
) {
item {
PreferenceCategory("Device") {
DeviceDetailsContent(metricsState)
}
}
item {
PreferenceCategory("Details") {
NodeDetailsContent(node)
@ -176,7 +186,12 @@ private fun NodeDetailList(
}
@Composable
private fun NodeDetailRow(label: String, icon: ImageVector, value: String) {
private fun NodeDetailRow(
label: String,
icon: ImageVector,
value: String,
iconTint: Color = MaterialTheme.colors.onSurface
) {
Row(
modifier = Modifier
.fillMaxWidth()
@ -186,7 +201,8 @@ private fun NodeDetailRow(label: String, icon: ImageVector, value: String) {
Icon(
imageVector = icon,
contentDescription = label,
modifier = Modifier.size(24.dp)
modifier = Modifier.size(24.dp),
tint = iconTint
)
Spacer(modifier = Modifier.width(8.dp))
Text(label)
@ -196,7 +212,50 @@ private fun NodeDetailRow(label: String, icon: ImageVector, value: String) {
}
@Composable
private fun NodeDetailsContent(node: NodeEntity) {
private fun DeviceDetailsContent(
state: MetricsState,
) {
val node = state.node ?: return
val deviceHardware = state.deviceHardware ?: return
val deviceImageRes = state.deviceImageRes
val hwModelName = deviceHardware.displayName
val isSupported = deviceHardware.activelySupported
Box(
modifier = Modifier
.size(100.dp)
.padding(4.dp)
.clip(CircleShape)
.background(
color = Color(node.colors.second).copy(alpha = .5f),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.padding(16.dp),
imageVector = ImageVector.vectorResource(deviceImageRes),
contentDescription = hwModelName,
)
}
NodeDetailRow(
label = "Hardware",
icon = Icons.Default.Router,
value = hwModelName
)
if (isSupported) {
NodeDetailRow(
label = "Supported",
icon = Icons.Default.Verified,
value = "",
iconTint = Color.Green
)
}
}
@Composable
private fun NodeDetailsContent(
node: NodeEntity,
) {
if (node.mismatchKey) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
@ -232,11 +291,6 @@ private fun NodeDetailsContent(node: NodeEntity) {
icon = Icons.Default.Work,
value = node.user.role.name
)
NodeDetailRow(
label = "Hardware",
icon = Icons.Default.Router,
value = node.user.hwModel.name
)
if (node.deviceMetrics.uptimeSeconds > 0) {
NodeDetailRow(
label = "Uptime",
@ -541,6 +595,9 @@ private fun NodeDetailsPreview(
node: NodeEntity
) {
AppTheme {
NodeDetailList(node, MetricsState.Empty)
NodeDetailList(
node = node,
metricsState = MetricsState.Empty,
)
}
}