mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
fix(release): fixes to prep for release (#4546)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
c5f2b1bbea
commit
80d9a2e0aa
30 changed files with 2324 additions and 312 deletions
|
|
@ -73,6 +73,7 @@ fun DeviceDetailsSection(state: MetricsState, modifier: Modifier = Modifier) {
|
|||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
SectionDivider()
|
||||
|
||||
val deviceText =
|
||||
state.reportedTarget?.let { target -> "${deviceHardware.displayName} ($target)" }
|
||||
?: deviceHardware.displayName
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package org.meshtastic.feature.node.component
|
||||
|
||||
import android.content.ClipData
|
||||
|
|
@ -30,6 +32,7 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.Notes
|
||||
import androidx.compose.material.icons.rounded.Numbers
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -48,6 +51,7 @@ import androidx.compose.ui.semantics.semantics
|
|||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
|
@ -69,10 +73,12 @@ import org.meshtastic.core.strings.role
|
|||
import org.meshtastic.core.strings.rssi
|
||||
import org.meshtastic.core.strings.short_name
|
||||
import org.meshtastic.core.strings.snr
|
||||
import org.meshtastic.core.strings.status_message
|
||||
import org.meshtastic.core.strings.supported
|
||||
import org.meshtastic.core.strings.uptime
|
||||
import org.meshtastic.core.strings.user_id
|
||||
import org.meshtastic.core.strings.via_mqtt
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
import org.meshtastic.core.ui.icon.ArrowCircleUp
|
||||
import org.meshtastic.core.ui.icon.ChannelUtilization
|
||||
import org.meshtastic.core.ui.icon.Cloud
|
||||
|
|
@ -84,6 +90,7 @@ import org.meshtastic.core.ui.icon.MeshtasticIcons
|
|||
import org.meshtastic.core.ui.icon.Person
|
||||
import org.meshtastic.core.ui.icon.Role
|
||||
import org.meshtastic.core.ui.icon.Verified
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.util.formatAgo
|
||||
|
||||
@Composable
|
||||
|
|
@ -135,13 +142,17 @@ private fun MismatchKeyWarning(modifier: Modifier = Modifier) {
|
|||
private fun MainNodeDetails(node: Node) {
|
||||
Column {
|
||||
NameAndRoleRow(node)
|
||||
node.nodeStatus?.let { status ->
|
||||
SectionDivider()
|
||||
StatusMessageRow(status)
|
||||
}
|
||||
SectionDivider()
|
||||
NodeIdentificationRow(node)
|
||||
SectionDivider()
|
||||
HearsAndHopsRow(node)
|
||||
SectionDivider()
|
||||
UserAndUptimeRow(node)
|
||||
if (node.hopsAway == 0) {
|
||||
if (node.hopsAway == 0 && !node.viaMqtt) {
|
||||
SectionDivider()
|
||||
SignalRow(node)
|
||||
}
|
||||
|
|
@ -175,6 +186,16 @@ private fun NameAndRoleRow(node: Node) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusMessageRow(status: String) {
|
||||
InfoItem(
|
||||
label = stringResource(Res.string.status_message),
|
||||
value = status,
|
||||
icon = Icons.AutoMirrored.Rounded.Notes,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NodeIdentificationRow(node: Node) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
|
|
@ -352,3 +373,12 @@ private fun PublicKeyItem(publicKeyBytes: ByteArray) {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun NodeDetailsSectionPreview() {
|
||||
AppTheme {
|
||||
val node = NodePreviewParameterProvider().values.last().copy(nodeStatus = "Going to the farm.. to grow wheat.")
|
||||
NodeDetailsSection(node = node)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,12 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
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
|
||||
import androidx.compose.material3.contentColorFor
|
||||
|
|
@ -84,6 +87,7 @@ import org.meshtastic.core.ui.component.Snr
|
|||
import org.meshtastic.core.ui.component.SoilMoistureInfo
|
||||
import org.meshtastic.core.ui.component.SoilTemperatureInfo
|
||||
import org.meshtastic.core.ui.component.TemperatureInfo
|
||||
import org.meshtastic.core.ui.component.TransportIcon
|
||||
import org.meshtastic.core.ui.component.determineSignalQuality
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
import org.meshtastic.core.ui.icon.AirUtilization
|
||||
|
|
@ -171,6 +175,28 @@ fun NodeItem(
|
|||
contentColor = contentColor,
|
||||
)
|
||||
|
||||
thatNode.nodeStatus?.let { status ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.Notes,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = contentColor.copy(alpha = 0.7f),
|
||||
)
|
||||
Text(
|
||||
text = status,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = contentColor,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
NodeBatteryPositionRow(
|
||||
thatNode = thatNode,
|
||||
distance = distance,
|
||||
|
|
@ -252,7 +278,7 @@ private fun NodeSignalRow(thatNode: Node, isThisNode: Boolean, contentColor: Col
|
|||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
if (thatNode.hopsAway > 0) {
|
||||
HopsInfo(hops = thatNode.hopsAway, contentColor = contentColor)
|
||||
} else {
|
||||
} else if (thatNode.hopsAway == 0 && !thatNode.viaMqtt) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -395,13 +421,21 @@ private fun NodeItemHeader(
|
|||
)
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = longName,
|
||||
style = MaterialTheme.typography.titleMediumEmphasized.copy(fontStyle = style),
|
||||
textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(
|
||||
text = longName,
|
||||
style = MaterialTheme.typography.titleMediumEmphasized.copy(fontStyle = style),
|
||||
textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
)
|
||||
TransportIcon(
|
||||
transport = thatNode.lastTransport,
|
||||
viaMqtt = thatNode.viaMqtt,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
}
|
||||
LastHeardInfo(lastHeard = thatNode.lastHeard, showLabel = false, contentColor = contentColor)
|
||||
}
|
||||
|
||||
|
|
@ -439,6 +473,17 @@ fun NodeInfoSimplePreview() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
fun NodeInfoStatusPreview() {
|
||||
AppTheme {
|
||||
val thisNode = NodePreviewParameterProvider().values.first()
|
||||
val thatNode =
|
||||
NodePreviewParameterProvider().values.last().copy(nodeStatus = "Going to the farm.. to grow wheat.")
|
||||
NodeItem(thisNode = thisNode, thatNode = thatNode, 0, true, connectionState = ConnectionState.Connected)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
fun NodeInfoSignalPreview() {
|
||||
|
|
|
|||
|
|
@ -1,274 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.feature.node.detail
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.ui.component.SharedContactDialog
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.feature.node.compass.CompassUiState
|
||||
import org.meshtastic.feature.node.compass.CompassViewModel
|
||||
import org.meshtastic.feature.node.component.AdministrationSection
|
||||
import org.meshtastic.feature.node.component.CompassSheetContent
|
||||
import org.meshtastic.feature.node.component.DeviceActions
|
||||
import org.meshtastic.feature.node.component.DeviceDetailsSection
|
||||
import org.meshtastic.feature.node.component.FirmwareReleaseSheetContent
|
||||
import org.meshtastic.feature.node.component.NodeDetailsSection
|
||||
import org.meshtastic.feature.node.component.NodeMenuAction
|
||||
import org.meshtastic.feature.node.component.NotesSection
|
||||
import org.meshtastic.feature.node.component.PositionSection
|
||||
import org.meshtastic.feature.node.model.LogsType
|
||||
import org.meshtastic.feature.node.model.MetricsState
|
||||
import org.meshtastic.feature.node.model.NodeDetailAction
|
||||
|
||||
@Composable
|
||||
fun NodeDetailContent(
|
||||
node: Node,
|
||||
ourNode: Node?,
|
||||
metricsState: MetricsState,
|
||||
lastTracerouteTime: Long?,
|
||||
lastRequestNeighborsTime: Long?,
|
||||
availableLogs: Set<LogsType>,
|
||||
onAction: (NodeDetailAction) -> Unit,
|
||||
onSaveNotes: (nodeNum: Int, notes: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var showShareDialog by remember { mutableStateOf(false) }
|
||||
if (showShareDialog) {
|
||||
SharedContactDialog(node) { showShareDialog = false }
|
||||
}
|
||||
|
||||
NodeDetailList(
|
||||
node = node,
|
||||
lastTracerouteTime = lastTracerouteTime,
|
||||
lastRequestNeighborsTime = lastRequestNeighborsTime,
|
||||
ourNode = ourNode,
|
||||
metricsState = metricsState,
|
||||
onAction = { action ->
|
||||
if (action is NodeDetailAction.ShareContact) {
|
||||
showShareDialog = true
|
||||
} else {
|
||||
onAction(action)
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
availableLogs = availableLogs,
|
||||
onSaveNotes = onSaveNotes,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
fun NodeDetailList(
|
||||
node: Node,
|
||||
lastTracerouteTime: Long?,
|
||||
lastRequestNeighborsTime: Long?,
|
||||
ourNode: Node?,
|
||||
metricsState: MetricsState,
|
||||
onAction: (NodeDetailAction) -> Unit,
|
||||
availableLogs: Set<LogsType>,
|
||||
onSaveNotes: (Int, String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var showFirmwareSheet by remember { mutableStateOf(false) }
|
||||
var selectedFirmware by remember { mutableStateOf<FirmwareRelease?>(null) }
|
||||
var showCompassSheet by remember { mutableStateOf(false) }
|
||||
|
||||
val inspectionMode = LocalInspectionMode.current
|
||||
val compassViewModel = if (inspectionMode) null else hiltViewModel<CompassViewModel>()
|
||||
val compassUiState by
|
||||
compassViewModel?.uiState?.collectAsStateWithLifecycle() ?: remember { mutableStateOf(CompassUiState()) }
|
||||
var compassTargetNode by remember { mutableStateOf<Node?>(null) }
|
||||
|
||||
val permissionLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { _ -> }
|
||||
val locationSettingsLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ -> }
|
||||
|
||||
FirmwareSheetHost(
|
||||
showFirmwareSheet = showFirmwareSheet,
|
||||
onDismiss = { showFirmwareSheet = false },
|
||||
firmwareRelease = selectedFirmware,
|
||||
)
|
||||
|
||||
CompassSheetHost(
|
||||
showCompassSheet = showCompassSheet,
|
||||
compassViewModel = compassViewModel,
|
||||
compassUiState = compassUiState,
|
||||
onDismiss = { showCompassSheet = false },
|
||||
permissionLauncher = permissionLauncher,
|
||||
locationSettingsLauncher = locationSettingsLauncher,
|
||||
onRequestPosition = {
|
||||
compassTargetNode?.let { target ->
|
||||
onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.RequestPosition(target)))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp).focusable(),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
NodeDetailsSection(node)
|
||||
|
||||
DeviceActions(
|
||||
isLocal = metricsState.isLocal,
|
||||
lastTracerouteTime = lastTracerouteTime,
|
||||
lastRequestNeighborsTime = lastRequestNeighborsTime,
|
||||
node = node,
|
||||
availableLogs = availableLogs,
|
||||
onAction = onAction,
|
||||
metricsState = metricsState,
|
||||
)
|
||||
|
||||
PositionSection(
|
||||
node = node,
|
||||
ourNode = ourNode,
|
||||
metricsState = metricsState,
|
||||
availableLogs = availableLogs,
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
is NodeDetailAction.OpenCompass -> {
|
||||
compassViewModel?.start(action.node, action.displayUnits)
|
||||
compassTargetNode = action.node
|
||||
showCompassSheet = compassViewModel != null
|
||||
}
|
||||
|
||||
else -> onAction(action)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (metricsState.deviceHardware != null) {
|
||||
DeviceDetailsSection(metricsState)
|
||||
}
|
||||
|
||||
NotesSection(node = node, onSaveNotes = onSaveNotes)
|
||||
|
||||
if (!metricsState.isManaged) {
|
||||
AdministrationSection(
|
||||
node = node,
|
||||
metricsState = metricsState,
|
||||
onAction = onAction,
|
||||
onFirmwareSelect = { firmware ->
|
||||
selectedFirmware = firmware
|
||||
showFirmwareSheet = true
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun FirmwareSheetHost(showFirmwareSheet: Boolean, onDismiss: () -> Unit, firmwareRelease: FirmwareRelease?) {
|
||||
if (showFirmwareSheet) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false)
|
||||
ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) {
|
||||
firmwareRelease?.let { FirmwareReleaseSheetContent(firmwareRelease = it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
private fun CompassSheetHost(
|
||||
showCompassSheet: Boolean,
|
||||
compassViewModel: CompassViewModel?,
|
||||
compassUiState: CompassUiState,
|
||||
onDismiss: () -> Unit,
|
||||
permissionLauncher: ManagedActivityResultLauncher<Array<String>, Map<String, Boolean>>,
|
||||
locationSettingsLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
|
||||
onRequestPosition: () -> Unit,
|
||||
) {
|
||||
if (showCompassSheet && compassViewModel != null) {
|
||||
DisposableEffect(Unit) { onDispose { compassViewModel.stop() } }
|
||||
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false)
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = {
|
||||
compassViewModel.stop()
|
||||
onDismiss()
|
||||
},
|
||||
sheetState = sheetState,
|
||||
) {
|
||||
CompassSheetContent(
|
||||
uiState = compassUiState,
|
||||
onRequestLocationPermission = {
|
||||
permissionLauncher.launch(
|
||||
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION),
|
||||
)
|
||||
},
|
||||
onOpenLocationSettings = {
|
||||
locationSettingsLauncher.launch(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
|
||||
},
|
||||
onRequestPosition = onRequestPosition,
|
||||
modifier = Modifier.padding(bottom = 24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NodeDetailsPreview(@PreviewParameter(NodePreviewParameterProvider::class) node: Node) {
|
||||
AppTheme {
|
||||
NodeDetailList(
|
||||
node = node,
|
||||
ourNode = node,
|
||||
lastTracerouteTime = null,
|
||||
lastRequestNeighborsTime = null,
|
||||
metricsState = MetricsState.Companion.Empty,
|
||||
availableLogs = emptySet(),
|
||||
onAction = {},
|
||||
onSaveNotes = { _, _ -> },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -52,6 +52,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
|
|
@ -61,9 +63,12 @@ import org.meshtastic.core.database.entity.FirmwareRelease
|
|||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.details
|
||||
import org.meshtastic.core.strings.loading
|
||||
import org.meshtastic.core.ui.component.MainAppBar
|
||||
import org.meshtastic.core.ui.component.SharedContactDialog
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.feature.node.compass.CompassUiState
|
||||
import org.meshtastic.feature.node.compass.CompassViewModel
|
||||
import org.meshtastic.feature.node.component.AdministrationSection
|
||||
|
|
@ -75,6 +80,7 @@ import org.meshtastic.feature.node.component.NodeDetailsSection
|
|||
import org.meshtastic.feature.node.component.NodeMenuAction
|
||||
import org.meshtastic.feature.node.component.NotesSection
|
||||
import org.meshtastic.feature.node.component.PositionSection
|
||||
import org.meshtastic.feature.node.model.MetricsState
|
||||
import org.meshtastic.feature.node.model.NodeDetailAction
|
||||
|
||||
private sealed interface NodeDetailOverlay {
|
||||
|
|
@ -143,7 +149,8 @@ private fun NodeDetailScaffold(
|
|||
modifier = modifier,
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
title = node?.user?.long_name ?: "",
|
||||
title = getString(Res.string.details),
|
||||
subtitle = node?.user?.long_name ?: "",
|
||||
ourNode = uiState.ourNode,
|
||||
showNodeChip = false,
|
||||
canNavigateUp = true,
|
||||
|
|
@ -343,3 +350,26 @@ private fun handleNodeAction(
|
|||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NodeDetailListPreview(@PreviewParameter(NodePreviewParameterProvider::class) node: Node) {
|
||||
AppTheme {
|
||||
val uiState =
|
||||
NodeDetailUiState(
|
||||
node = node,
|
||||
ourNode = node,
|
||||
metricsState = MetricsState(node = node, isLocal = true, isManaged = false),
|
||||
availableLogs = emptySet(),
|
||||
)
|
||||
NodeDetailList(
|
||||
node = node,
|
||||
ourNode = node,
|
||||
uiState = uiState,
|
||||
listState = rememberLazyListState(),
|
||||
onAction = {},
|
||||
onFirmwareSelect = {},
|
||||
onSaveNotes = { _, _ -> },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import org.meshtastic.core.database.entity.MyNodeEntity
|
|||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.util.isLora
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
|
|
@ -216,7 +217,13 @@ constructor(
|
|||
deviceMetrics = data.telemetry.filter { it.device_metrics != null },
|
||||
powerMetrics = data.telemetry.filter { it.power_metrics != null },
|
||||
hostMetrics = data.telemetry.filter { it.host_metrics != null },
|
||||
signalMetrics = data.packets.filter { (it.rx_time ?: 0) > 0 },
|
||||
signalMetrics =
|
||||
data.packets.filter { pkt ->
|
||||
(pkt.rx_time ?: 0) > 0 &&
|
||||
pkt.hop_start == pkt.hop_limit &&
|
||||
pkt.via_mqtt != true &&
|
||||
pkt.isLora()
|
||||
},
|
||||
positionLogs = data.positionPackets.mapNotNull { it.toPosition() },
|
||||
paxMetrics = data.paxLogs,
|
||||
tracerouteRequests = data.tracerouteRequests,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue