Add dividers to node details (#3466)

This commit is contained in:
Phil Oliver 2025-10-14 22:06:45 -04:00 committed by GitHub
parent d64825f4f4
commit 73b37c17dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 122 additions and 6 deletions

View file

@ -32,15 +32,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.SharedContactDialog
import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.feature.node.component.AdministrationSection
@ -116,7 +113,7 @@ fun NodeDetailList(
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
if (metricsState.deviceHardware != null) {
TitledCard(title = stringResource(R.string.device)) { DeviceDetailsSection(metricsState) }
DeviceDetailsSection(metricsState)
}
NodeDetailsSection(node)

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2025 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.core.ui.component
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun InsetDivider(
modifier: Modifier = Modifier,
inset: Dp = 16.dp,
thickness: Dp = DividerDefaults.Thickness,
color: Color = DividerDefaults.color,
) {
InsetDivider(modifier = modifier, startInset = inset, endInset = inset, thickness = thickness, color = color)
}
@Composable
fun InsetDivider(
modifier: Modifier = Modifier,
startInset: Dp = 0.dp,
endInset: Dp = 0.dp,
thickness: Dp = DividerDefaults.Thickness,
color: Color = DividerDefaults.color,
) {
HorizontalDivider(
modifier = modifier.padding(start = startInset, end = endInset),
thickness = thickness,
color = color,
)
}

View file

@ -8,6 +8,7 @@
<ID>ComposableParamOrder:NodeItem.kt$NodeItem</ID>
<ID>ComposableParamOrder:SatelliteCountInfo.kt$SatelliteCountInfo</ID>
<ID>ComposableParamOrder:TracerouteButton.kt$TracerouteButton</ID>
<ID>LongMethod:NodeDetailsSection.kt$@Composable private fun MainNodeDetails(node: Node)</ID>
<ID>ModifierMissing:NodeStatusIcons.kt$NodeStatusIcons</ID>
<ID>MultipleEmitters:NodeDetailsSection.kt$MainNodeDetails</ID>
<ID>MultipleEmitters:RemoteDeviceActions.kt$RemoteDeviceActions</ID>

View file

@ -22,7 +22,6 @@ import androidx.compose.material.icons.filled.ForkLeft
import androidx.compose.material.icons.filled.Icecream
import androidx.compose.material.icons.filled.Memory
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -35,6 +34,7 @@ import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.core.service.ServiceAction
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.InsetDivider
import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
@ -61,6 +61,9 @@ fun AdministrationSection(
trailingIcon = null,
onClick = { onAction(NodeDetailAction.TriggerServiceAction(ServiceAction.GetDeviceMetadata(node.num))) },
)
InsetDivider()
ListItem(
text = stringResource(id = R.string.remote_admin),
leadingIcon = Icons.Default.Settings,
@ -95,6 +98,8 @@ fun AdministrationSection(
val deviceVersion = DeviceVersion(firmwareVersion.substringBeforeLast("."))
val statusColor = deviceVersion.determineFirmwareStatusColor(latestStable, latestAlpha)
InsetDivider()
ListItem(
text = stringResource(R.string.installed_firmware_version),
leadingIcon = Icons.Default.Memory,
@ -103,7 +108,9 @@ fun AdministrationSection(
leadingIconTint = statusColor,
trailingIcon = null,
)
HorizontalDivider()
InsetDivider()
ListItem(
text = stringResource(R.string.latest_stable_firmware),
leadingIcon = Icons.Default.Memory,
@ -113,6 +120,9 @@ fun AdministrationSection(
trailingIcon = null,
onClick = { onFirmwareSelect(latestStable) },
)
InsetDivider()
ListItem(
text = stringResource(R.string.latest_alpha_firmware),
leadingIcon = Icons.Default.Memory,

View file

@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.InsetDivider
import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.core.ui.component.SwitchListItem
import org.meshtastic.core.ui.component.TitledCard
@ -74,8 +75,12 @@ fun DeviceActions(
onClick = { onAction(NodeDetailAction.ShareContact) },
)
if (!isLocal) {
InsetDivider()
RemoteDeviceActions(node = node, lastTracerouteTime = lastTracerouteTime, onAction = onAction)
}
InsetDivider()
SwitchListItem(
text = stringResource(R.string.favorite),
leadingIcon = if (node.isFavorite) Icons.Default.Star else Icons.Default.StarBorder,
@ -83,6 +88,9 @@ fun DeviceActions(
checked = node.isFavorite,
onClick = { displayFavoriteDialog = true },
)
InsetDivider()
SwitchListItem(
text = stringResource(R.string.ignore),
leadingIcon =
@ -90,6 +98,9 @@ fun DeviceActions(
checked = node.isIgnored,
onClick = { displayIgnoreDialog = true },
)
InsetDivider()
ListItem(
text = stringResource(id = R.string.remove),
leadingIcon = Icons.Rounded.Delete,

View file

@ -45,6 +45,7 @@ import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.InsetDivider
import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
@ -58,6 +59,8 @@ fun DeviceDetailsSection(state: MetricsState, modifier: Modifier = Modifier) {
val hwModelName = deviceHardware.displayName
val isSupported = deviceHardware.activelySupported
TitledCard(stringResource(R.string.device), modifier = modifier) {
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier =
Modifier.align(Alignment.CenterHorizontally)
@ -71,6 +74,8 @@ fun DeviceDetailsSection(state: MetricsState, modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.height(16.dp))
InsetDivider()
ListItem(
text = stringResource(R.string.hardware),
leadingIcon = Icons.Default.Router,
@ -78,6 +83,9 @@ fun DeviceDetailsSection(state: MetricsState, modifier: Modifier = Modifier) {
copyable = true,
trailingIcon = null,
)
InsetDivider()
ListItem(
text =
if (isSupported) {

View file

@ -45,6 +45,7 @@ import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.util.formatAgo
import org.meshtastic.core.model.util.formatUptime
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.InsetDivider
import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.feature.node.model.isEffectivelyUnmessageable
@ -87,6 +88,9 @@ private fun MainNodeDetails(node: Node) {
copyable = true,
trailingIcon = null,
)
InsetDivider()
ListItem(
text = stringResource(R.string.short_name),
leadingIcon = Icons.Outlined.Person,
@ -94,6 +98,9 @@ private fun MainNodeDetails(node: Node) {
copyable = true,
trailingIcon = null,
)
InsetDivider()
ListItem(
text = stringResource(R.string.node_number),
leadingIcon = Icons.Default.Numbers,
@ -101,6 +108,9 @@ private fun MainNodeDetails(node: Node) {
copyable = true,
trailingIcon = null,
)
InsetDivider()
ListItem(
text = stringResource(R.string.user_id),
leadingIcon = Icons.Default.Person,
@ -108,13 +118,19 @@ private fun MainNodeDetails(node: Node) {
copyable = true,
trailingIcon = null,
)
InsetDivider()
ListItem(
text = stringResource(R.string.role),
leadingIcon = Icons.Default.Work,
supportingText = node.user.role.name,
trailingIcon = null,
)
if (node.isEffectivelyUnmessageable) {
InsetDivider()
ListItem(
text = stringResource(R.string.unmonitored_or_infrastructure),
leadingIcon = Icons.Outlined.NoCell,
@ -122,6 +138,8 @@ private fun MainNodeDetails(node: Node) {
)
}
if (node.deviceMetrics.uptimeSeconds > 0) {
InsetDivider()
ListItem(
text = stringResource(R.string.uptime),
leadingIcon = Icons.Default.CheckCircle,
@ -129,6 +147,9 @@ private fun MainNodeDetails(node: Node) {
trailingIcon = null,
)
}
InsetDivider()
ListItem(
text = stringResource(R.string.node_sort_last_heard),
leadingIcon = Icons.Default.History,

View file

@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.util.toDistanceString
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.InsetDivider
import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.feature.node.model.LogsType
@ -60,6 +61,8 @@ fun PositionSection(
// Distance (if available)
if (distance != null && distance.isNotEmpty()) {
InsetDivider()
ListItem(
text = stringResource(R.string.node_sort_distance),
leadingIcon = Icons.Default.SocialDistance,
@ -69,6 +72,8 @@ fun PositionSection(
)
}
InsetDivider()
// Exchange position action
ListItem(
text = stringResource(id = R.string.exchange_position),
@ -79,6 +84,8 @@ fun PositionSection(
// Node Map log
if (availableLogs.contains(LogsType.NODE_MAP)) {
InsetDivider()
ListItem(text = stringResource(LogsType.NODE_MAP.titleRes), leadingIcon = LogsType.NODE_MAP.icon) {
onAction(NodeDetailAction.Navigate(LogsType.NODE_MAP.route))
}
@ -86,6 +93,8 @@ fun PositionSection(
// Positions Log
if (availableLogs.contains(LogsType.POSITIONS)) {
InsetDivider()
ListItem(text = stringResource(LogsType.POSITIONS.titleRes), leadingIcon = LogsType.POSITIONS.icon) {
onAction(NodeDetailAction.Navigate(LogsType.POSITIONS.route))
}

View file

@ -24,6 +24,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.InsetDivider
import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.feature.node.model.NodeDetailAction
import org.meshtastic.feature.node.model.isEffectivelyUnmessageable
@ -37,13 +38,19 @@ internal fun RemoteDeviceActions(node: Node, lastTracerouteTime: Long?, onAction
trailingIcon = null,
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.DirectMessage(node))) },
)
InsetDivider()
}
ListItem(
text = stringResource(id = R.string.exchange_userinfo),
leadingIcon = Icons.Default.Person,
trailingIcon = null,
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.RequestUserInfo(node))) },
)
InsetDivider()
TracerouteButton(
lastTracerouteTime = lastTracerouteTime,
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.TraceRoute(node))) },