diff --git a/core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt b/core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt
index 9c140181a..964caec99 100644
--- a/core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt
+++ b/core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt
@@ -166,6 +166,8 @@ sealed interface SettingsRoute : Route {
@Serializable data object FilterSettings : SettingsRoute
+ @Serializable data object NodeList : SettingsRoute
+
// endregion
// region help & documentation routes
diff --git a/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt b/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
index 5ae16264c..d894e7644 100644
--- a/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
+++ b/feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
@@ -49,6 +49,7 @@ import org.meshtastic.core.resources.export_configuration
import org.meshtastic.core.resources.filter_settings
import org.meshtastic.core.resources.help_and_documentation
import org.meshtastic.core.resources.import_configuration
+import org.meshtastic.core.resources.node_layout_section_title
import org.meshtastic.core.resources.preferences_language
import org.meshtastic.core.resources.remotely_administrating
import org.meshtastic.core.resources.wifi_devices
@@ -57,12 +58,12 @@ import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.MeshtasticDialog
import org.meshtastic.core.ui.icon.FilterList
import org.meshtastic.core.ui.icon.HelpOutline
+import org.meshtastic.core.ui.icon.List
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Wifi
import org.meshtastic.feature.settings.component.AppInfoSection
import org.meshtastic.feature.settings.component.AppearanceSection
import org.meshtastic.feature.settings.component.ExpressiveSection
-import org.meshtastic.feature.settings.component.NodeLayoutSettings
import org.meshtastic.feature.settings.component.PersistenceSection
import org.meshtastic.feature.settings.component.PrivacySection
import org.meshtastic.feature.settings.component.ThemePickerDialog
@@ -241,39 +242,14 @@ fun SettingsScreen(
onShowThemePicker = { showThemePickerDialog = true },
)
- val densityName by settingsViewModel.nodeListDensity.collectAsStateWithLifecycle()
- val density = org.meshtastic.core.model.NodeListDensity.fromName(densityName)
- val showPower by settingsViewModel.shouldShowPower.collectAsStateWithLifecycle()
- val showLastHeard by settingsViewModel.shouldShowLastHeard.collectAsStateWithLifecycle()
- val lastHeardRelative by settingsViewModel.lastHeardIsRelative.collectAsStateWithLifecycle()
- val showLocation by settingsViewModel.shouldShowLocation.collectAsStateWithLifecycle()
- val showHops by settingsViewModel.shouldShowHops.collectAsStateWithLifecycle()
- val showSignal by settingsViewModel.shouldShowSignal.collectAsStateWithLifecycle()
- val showChannel by settingsViewModel.shouldShowChannel.collectAsStateWithLifecycle()
- val showRole by settingsViewModel.shouldShowRole.collectAsStateWithLifecycle()
- val showTelemetry by settingsViewModel.shouldShowTelemetry.collectAsStateWithLifecycle()
- NodeLayoutSettings(
- density = density,
- onDensityChange = { settingsViewModel.setNodeListDensity(it.name) },
- showPower = showPower,
- onShowPowerChange = { settingsViewModel.setShouldShowPower(it) },
- showLastHeard = showLastHeard,
- onShowLastHeardChange = { settingsViewModel.setShouldShowLastHeard(it) },
- lastHeardIsRelative = lastHeardRelative,
- onLastHeardIsRelativeChange = { settingsViewModel.setLastHeardIsRelative(it) },
- showLocation = showLocation,
- onShowLocationChange = { settingsViewModel.setShouldShowLocation(it) },
- showHops = showHops,
- onShowHopsChange = { settingsViewModel.setShouldShowHops(it) },
- showSignal = showSignal,
- onShowSignalChange = { settingsViewModel.setShouldShowSignal(it) },
- showChannel = showChannel,
- onShowChannelChange = { settingsViewModel.setShouldShowChannel(it) },
- showRole = showRole,
- onShowRoleChange = { settingsViewModel.setShouldShowRole(it) },
- showTelemetry = showTelemetry,
- onShowTelemetryChange = { settingsViewModel.setShouldShowTelemetry(it) },
- )
+ ExpressiveSection(title = stringResource(Res.string.node_layout_section_title)) {
+ ListItem(
+ text = stringResource(Res.string.node_layout_section_title),
+ leadingIcon = MeshtasticIcons.List,
+ ) {
+ onNavigate(SettingsRoute.NodeList)
+ }
+ }
ExpressiveSection(title = stringResource(Res.string.wifi_devices)) {
ListItem(text = stringResource(Res.string.wifi_devices), leadingIcon = MeshtasticIcons.Wifi) {
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/NodeListScreen.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/NodeListScreen.kt
new file mode 100644
index 000000000..a5a02e9a5
--- /dev/null
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/NodeListScreen.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 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 .
+ */
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
+package org.meshtastic.feature.settings
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import org.jetbrains.compose.resources.stringResource
+import org.meshtastic.core.resources.Res
+import org.meshtastic.core.resources.node_layout_section_title
+import org.meshtastic.core.ui.component.MainAppBar
+import org.meshtastic.feature.settings.component.NodeLayoutSettings
+
+/**
+ * Dedicated settings screen for node list display options (density and field visibility). Provides a focused interface
+ * for customizing how nodes are rendered in the list view.
+ *
+ * Material 3 expressive design highlights:
+ * - **Section organization**: NodeLayoutSettings component handles grouped layout with cards
+ * - **Visual hierarchy**: MainAppBar provides clear screen identity and navigation
+ * - **Spacing rhythm**: 16dp content padding with 16dp section gaps for consistent rhythm
+ * - **Scrollability**: Full vertical scroll support for all display variations
+ * - **Responsive preview**: Live preview updates as density and field options change
+ *
+ * @param settingsViewModel Provides access to node display preferences and update methods
+ * @param onNavigateUp Callback when user requests navigation back (back button in app bar)
+ */
+@Composable
+fun NodeListScreen(settingsViewModel: SettingsViewModel, onNavigateUp: () -> Unit, modifier: Modifier = Modifier) {
+ val settings by settingsViewModel.nodeListSettings.collectAsStateWithLifecycle()
+
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ MainAppBar(
+ title = stringResource(Res.string.node_layout_section_title),
+ canNavigateUp = true,
+ onNavigateUp = onNavigateUp,
+ ourNode = null,
+ showNodeChip = false,
+ actions = {},
+ onClickChip = {},
+ )
+ },
+ ) { paddingValues ->
+ Column(
+ modifier = Modifier.verticalScroll(rememberScrollState()).padding(paddingValues).padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ NodeLayoutSettings(
+ state = settings,
+ onDensityChange = { settingsViewModel.setNodeListDensity(it.name) },
+ onShowPowerChange = { settingsViewModel.setShouldShowPower(it) },
+ onShowLastHeardChange = { settingsViewModel.setShouldShowLastHeard(it) },
+ onLastHeardIsRelativeChange = { settingsViewModel.setLastHeardIsRelative(it) },
+ onShowLocationChange = { settingsViewModel.setShouldShowLocation(it) },
+ onShowHopsChange = { settingsViewModel.setShouldShowHops(it) },
+ onShowSignalChange = { settingsViewModel.setShouldShowSignal(it) },
+ onShowChannelChange = { settingsViewModel.setShouldShowChannel(it) },
+ onShowRoleChange = { settingsViewModel.setShouldShowRole(it) },
+ onShowTelemetryChange = { settingsViewModel.setShouldShowTelemetry(it) },
+ )
+ }
+ }
+}
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt
index afd92c322..fa54c8992 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt
@@ -20,6 +20,7 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -42,6 +43,7 @@ import org.meshtastic.core.domain.usecase.settings.SetThemeUseCase
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.Node
+import org.meshtastic.core.model.NodeListDensity
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.FileService
import org.meshtastic.core.repository.MeshLogPrefs
@@ -223,4 +225,62 @@ class SettingsViewModel(
fun setShouldShowRole(value: Boolean) = uiPrefs.setShouldShowRole(value)
fun setShouldShowTelemetry(value: Boolean) = uiPrefs.setShouldShowTelemetry(value)
+
+ // Aggregated node list settings — nested combines because typed overloads max at 5 args
+ val nodeListSettings =
+ combine(
+ combine(
+ nodeListDensity,
+ shouldShowPower,
+ shouldShowLastHeard,
+ lastHeardIsRelative,
+ shouldShowLocation,
+ ) { density, power, lastHeard, heardRelative, location ->
+ NodeListSettingsState(
+ density = NodeListDensity.fromName(density),
+ showPower = power,
+ showLastHeard = lastHeard,
+ lastHeardIsRelative = heardRelative,
+ showLocation = location,
+ )
+ },
+ combine(shouldShowHops, shouldShowSignal, shouldShowChannel, shouldShowRole, shouldShowTelemetry) {
+ hops,
+ signal,
+ channel,
+ role,
+ telemetry,
+ ->
+ NodeListSettingsState(
+ showHops = hops,
+ showSignal = signal,
+ showChannel = channel,
+ showRole = role,
+ showTelemetry = telemetry,
+ )
+ },
+ ) { first, second ->
+ first.copy(
+ showHops = second.showHops,
+ showSignal = second.showSignal,
+ showChannel = second.showChannel,
+ showRole = second.showRole,
+ showTelemetry = second.showTelemetry,
+ )
+ }
+ .stateInWhileSubscribed(initialValue = NodeListSettingsState())
}
+
+/** Aggregated state for node list display settings to reduce recomposition overhead. */
+data class NodeListSettingsState(
+ val density: NodeListDensity = NodeListDensity.COMPLETE,
+ val showPower: Boolean = false,
+ val showLastHeard: Boolean = false,
+ val lastHeardIsRelative: Boolean = false,
+ val showLocation: Boolean = false,
+ val showHops: Boolean = false,
+ val showSignal: Boolean = false,
+ val showChannel: Boolean = false,
+ val showRole: Boolean = false,
+ val showTelemetry: Boolean = false,
+)
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/component/NodeLayoutSettings.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/component/NodeLayoutSettings.kt
index 624030c23..82b87859a 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/component/NodeLayoutSettings.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/component/NodeLayoutSettings.kt
@@ -22,6 +22,8 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SegmentedButton
@@ -57,6 +59,7 @@ import org.meshtastic.core.resources.node_layout_signal_direct_only
import org.meshtastic.core.ui.component.NodeItem
import org.meshtastic.core.ui.component.NodeItemCompact
import org.meshtastic.core.ui.component.SwitchPreference
+import org.meshtastic.feature.settings.NodeListSettingsState
import org.meshtastic.proto.Config
import org.meshtastic.proto.DeviceMetrics
import org.meshtastic.proto.EnvironmentMetrics
@@ -68,25 +71,16 @@ import org.meshtastic.proto.User
@Composable
@Suppress("LongParameterList", "LongMethod")
fun NodeLayoutSettings(
- density: NodeListDensity,
+ state: NodeListSettingsState,
onDensityChange: (NodeListDensity) -> Unit,
- showPower: Boolean,
onShowPowerChange: (Boolean) -> Unit,
- showLastHeard: Boolean,
onShowLastHeardChange: (Boolean) -> Unit,
- lastHeardIsRelative: Boolean,
onLastHeardIsRelativeChange: (Boolean) -> Unit,
- showLocation: Boolean,
onShowLocationChange: (Boolean) -> Unit,
- showHops: Boolean,
onShowHopsChange: (Boolean) -> Unit,
- showSignal: Boolean,
onShowSignalChange: (Boolean) -> Unit,
- showChannel: Boolean,
onShowChannelChange: (Boolean) -> Unit,
- showRole: Boolean,
onShowRoleChange: (Boolean) -> Unit,
- showTelemetry: Boolean,
onShowTelemetryChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
@@ -104,7 +98,7 @@ fun NodeLayoutSettings(
SegmentedButton(
shape = SegmentedButtonDefaults.itemShape(index, NodeListDensity.entries.size),
onClick = { onDensityChange(option) },
- selected = density == option,
+ selected = state.density == option,
label = { Text(label) },
)
}
@@ -122,7 +116,7 @@ fun NodeLayoutSettings(
val localNode = remember { previewLocalNode() }
Box(modifier = Modifier.animateContentSize().padding(bottom = 8.dp)) {
- when (density) {
+ when (state.density) {
NodeListDensity.COMPLETE ->
NodeItem(
thisNode = localNode,
@@ -130,7 +124,7 @@ fun NodeLayoutSettings(
distanceUnits = 0,
tempInFahrenheit = false,
connectionState = ConnectionState.Connected,
- showTelemetry = showTelemetry,
+ showTelemetry = state.showTelemetry,
)
NodeListDensity.COMPACT ->
@@ -138,15 +132,15 @@ fun NodeLayoutSettings(
thisNode = localNode,
thatNode = previewNode,
distanceUnits = 0,
- showPower = showPower,
- showLastHeard = showLastHeard,
- lastHeardIsRelative = lastHeardIsRelative,
- showLocation = showLocation,
- showHops = showHops,
- showSignal = showSignal,
- showChannel = showChannel,
- showRole = showRole,
- showTelemetry = showTelemetry,
+ showPower = state.showPower,
+ showLastHeard = state.showLastHeard,
+ lastHeardIsRelative = state.lastHeardIsRelative,
+ showLocation = state.showLocation,
+ showHops = state.showHops,
+ showSignal = state.showSignal,
+ showChannel = state.showChannel,
+ showRole = state.showRole,
+ showTelemetry = state.showTelemetry,
)
}
}
@@ -157,12 +151,12 @@ fun NodeLayoutSettings(
// Shared toggle — applies to both layouts
SwitchPreference(
title = stringResource(Res.string.node_layout_log_icons),
- checked = showTelemetry,
+ checked = state.showTelemetry,
enabled = true,
onCheckedChange = onShowTelemetryChange,
)
- if (density == NodeListDensity.COMPLETE) {
+ if (state.density == NodeListDensity.COMPLETE) {
Text(
text = stringResource(Res.string.node_layout_complete_description),
style = MaterialTheme.typography.bodyMedium,
@@ -176,56 +170,62 @@ fun NodeLayoutSettings(
text = stringResource(Res.string.node_layout_compact_fields_header),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
- modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 4.dp),
- )
- SwitchPreference(
- title = stringResource(Res.string.node_layout_power),
- checked = showPower,
- enabled = true,
- onCheckedChange = onShowPowerChange,
- )
- SwitchPreference(
- title = stringResource(Res.string.node_layout_last_heard_time),
- checked = showLastHeard,
- enabled = true,
- onCheckedChange = onShowLastHeardChange,
- )
- SwitchPreference(
- title = stringResource(Res.string.node_layout_relative_last_heard),
- checked = lastHeardIsRelative,
- enabled = showLastHeard,
- onCheckedChange = onLastHeardIsRelativeChange,
- )
- SwitchPreference(
- title = stringResource(Res.string.node_layout_distance_and_bearing),
- checked = showLocation,
- enabled = true,
- onCheckedChange = onShowLocationChange,
- )
- SwitchPreference(
- title = stringResource(Res.string.node_layout_hops_away),
- checked = showHops,
- enabled = true,
- onCheckedChange = onShowHopsChange,
- )
- SwitchPreference(
- title = stringResource(Res.string.node_layout_signal_direct_only),
- checked = showSignal,
- enabled = true,
- onCheckedChange = onShowSignalChange,
- )
- SwitchPreference(
- title = stringResource(Res.string.node_layout_channel),
- checked = showChannel,
- enabled = true,
- onCheckedChange = onShowChannelChange,
- )
- SwitchPreference(
- title = stringResource(Res.string.node_layout_device_role),
- checked = showRole,
- enabled = true,
- onCheckedChange = onShowRoleChange,
+ modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 8.dp),
)
+ Card(
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLow),
+ ) {
+ SwitchPreference(
+ title = stringResource(Res.string.node_layout_power),
+ checked = state.showPower,
+ enabled = true,
+ onCheckedChange = onShowPowerChange,
+ )
+ SwitchPreference(
+ title = stringResource(Res.string.node_layout_last_heard_time),
+ checked = state.showLastHeard,
+ enabled = true,
+ onCheckedChange = onShowLastHeardChange,
+ )
+ SwitchPreference(
+ title = stringResource(Res.string.node_layout_relative_last_heard),
+ checked = state.lastHeardIsRelative,
+ enabled = state.showLastHeard,
+ onCheckedChange = onLastHeardIsRelativeChange,
+ )
+ SwitchPreference(
+ title = stringResource(Res.string.node_layout_distance_and_bearing),
+ checked = state.showLocation,
+ enabled = true,
+ onCheckedChange = onShowLocationChange,
+ )
+ SwitchPreference(
+ title = stringResource(Res.string.node_layout_hops_away),
+ checked = state.showHops,
+ enabled = true,
+ onCheckedChange = onShowHopsChange,
+ )
+ SwitchPreference(
+ title = stringResource(Res.string.node_layout_signal_direct_only),
+ checked = state.showSignal,
+ enabled = true,
+ onCheckedChange = onShowSignalChange,
+ )
+ SwitchPreference(
+ title = stringResource(Res.string.node_layout_channel),
+ checked = state.showChannel,
+ enabled = true,
+ onCheckedChange = onShowChannelChange,
+ )
+ SwitchPreference(
+ title = stringResource(Res.string.node_layout_device_role),
+ checked = state.showRole,
+ enabled = true,
+ onCheckedChange = onShowRoleChange,
+ )
+ }
+ Spacer(modifier = Modifier.height(16.dp))
}
}
}
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/component/NodeLayoutSettingsPreviews.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/component/NodeLayoutSettingsPreviews.kt
index 82dab3551..fe9c94b02 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/component/NodeLayoutSettingsPreviews.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/component/NodeLayoutSettingsPreviews.kt
@@ -33,6 +33,7 @@ import org.meshtastic.core.model.NodeListDensity
import org.meshtastic.core.ui.component.NodeItem
import org.meshtastic.core.ui.component.NodeItemCompact
import org.meshtastic.core.ui.theme.AppTheme
+import org.meshtastic.feature.settings.NodeListSettingsState
@PreviewLightDark
@Composable
@@ -40,25 +41,28 @@ fun NodeLayoutSettingsCompactPreview() {
AppTheme {
Surface {
NodeLayoutSettings(
- density = NodeListDensity.COMPACT,
+ state =
+ NodeListSettingsState(
+ density = NodeListDensity.COMPACT,
+ showPower = true,
+ showLastHeard = true,
+ lastHeardIsRelative = true,
+ showLocation = true,
+ showHops = true,
+ showSignal = true,
+ showChannel = false,
+ showRole = true,
+ showTelemetry = true,
+ ),
onDensityChange = {},
- showPower = true,
onShowPowerChange = {},
- showLastHeard = true,
onShowLastHeardChange = {},
- lastHeardIsRelative = true,
onLastHeardIsRelativeChange = {},
- showLocation = true,
onShowLocationChange = {},
- showHops = true,
onShowHopsChange = {},
- showSignal = true,
onShowSignalChange = {},
- showChannel = false,
onShowChannelChange = {},
- showRole = true,
onShowRoleChange = {},
- showTelemetry = true,
onShowTelemetryChange = {},
)
}
@@ -71,25 +75,28 @@ fun NodeLayoutSettingsCompletePreview() {
AppTheme {
Surface {
NodeLayoutSettings(
- density = NodeListDensity.COMPLETE,
+ state =
+ NodeListSettingsState(
+ density = NodeListDensity.COMPLETE,
+ showPower = true,
+ showLastHeard = true,
+ lastHeardIsRelative = true,
+ showLocation = true,
+ showHops = true,
+ showSignal = true,
+ showChannel = true,
+ showRole = true,
+ showTelemetry = true,
+ ),
onDensityChange = {},
- showPower = true,
onShowPowerChange = {},
- showLastHeard = true,
onShowLastHeardChange = {},
- lastHeardIsRelative = true,
onLastHeardIsRelativeChange = {},
- showLocation = true,
onShowLocationChange = {},
- showHops = true,
onShowHopsChange = {},
- showSignal = true,
onShowSignalChange = {},
- showChannel = true,
onShowChannelChange = {},
- showRole = true,
onShowRoleChange = {},
- showTelemetry = true,
onShowTelemetryChange = {},
)
}
@@ -103,25 +110,28 @@ fun NodeLayoutSettingsCompactMinimalPreview() {
AppTheme {
Surface {
NodeLayoutSettings(
- density = NodeListDensity.COMPACT,
+ state =
+ NodeListSettingsState(
+ density = NodeListDensity.COMPACT,
+ showPower = false,
+ showLastHeard = true,
+ lastHeardIsRelative = true,
+ showLocation = false,
+ showHops = false,
+ showSignal = true,
+ showChannel = false,
+ showRole = false,
+ showTelemetry = false,
+ ),
onDensityChange = {},
- showPower = false,
onShowPowerChange = {},
- showLastHeard = true,
onShowLastHeardChange = {},
- lastHeardIsRelative = true,
onLastHeardIsRelativeChange = {},
- showLocation = false,
onShowLocationChange = {},
- showHops = false,
onShowHopsChange = {},
- showSignal = true,
onShowSignalChange = {},
- showChannel = false,
onShowChannelChange = {},
- showRole = false,
onShowRoleChange = {},
- showTelemetry = false,
onShowTelemetryChange = {},
)
}
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt
index 790b13f1f..fb300eb4f 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt
@@ -34,6 +34,7 @@ import org.meshtastic.feature.settings.AboutScreen
import org.meshtastic.feature.settings.AdministrationScreen
import org.meshtastic.feature.settings.DeviceConfigurationScreen
import org.meshtastic.feature.settings.ModuleConfigurationScreen
+import org.meshtastic.feature.settings.NodeListScreen
import org.meshtastic.feature.settings.SettingsViewModel
import org.meshtastic.feature.settings.debugging.DebugScreen
import org.meshtastic.feature.settings.debugging.DebugViewModel
@@ -78,9 +79,7 @@ fun getRadioConfigViewModel(backStack: NavBackStack, destNumOverride: In
?: remember(backStack.toList()) {
backStack.lastOrNull { it is SettingsRoute.Settings }?.let { (it as SettingsRoute.Settings).destNum }
}
- return koinViewModel(key = destNum?.toString()) {
- parametersOf(destNum)
- }
+ return koinViewModel(key = destNum?.toString()) { parametersOf(destNum) }
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
@@ -243,6 +242,14 @@ fun EntryProviderScope.settingsGraph(backStack: NavBackStack) {
val viewModel: FilterSettingsViewModel = koinViewModel()
FilterSettingsScreen(viewModel = viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() })
}
+
+ entry {
+ val settingsViewModel: SettingsViewModel = koinViewModel()
+ NodeListScreen(
+ settingsViewModel = settingsViewModel,
+ onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() },
+ )
+ }
}
/** Expect declaration for the platform-specific settings main screen. */
diff --git a/feature/settings/src/jvmMain/kotlin/org/meshtastic/feature/settings/DesktopSettingsScreen.kt b/feature/settings/src/jvmMain/kotlin/org/meshtastic/feature/settings/DesktopSettingsScreen.kt
index 36cacaf5d..1036a0891 100644
--- a/feature/settings/src/jvmMain/kotlin/org/meshtastic/feature/settings/DesktopSettingsScreen.kt
+++ b/feature/settings/src/jvmMain/kotlin/org/meshtastic/feature/settings/DesktopSettingsScreen.kt
@@ -52,6 +52,7 @@ import org.meshtastic.core.resources.help_and_documentation
import org.meshtastic.core.resources.info
import org.meshtastic.core.resources.modules_already_unlocked
import org.meshtastic.core.resources.modules_unlocked
+import org.meshtastic.core.resources.node_layout_section_title
import org.meshtastic.core.resources.preferences_language
import org.meshtastic.core.resources.remotely_administrating
import org.meshtastic.core.resources.theme
@@ -65,13 +66,13 @@ import org.meshtastic.core.ui.icon.FormatPaint
import org.meshtastic.core.ui.icon.HelpOutline
import org.meshtastic.core.ui.icon.Info
import org.meshtastic.core.ui.icon.Language
+import org.meshtastic.core.ui.icon.List
import org.meshtastic.core.ui.icon.Memory
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Wifi
import org.meshtastic.core.ui.util.rememberShowToastResource
import org.meshtastic.feature.settings.component.ExpressiveSection
import org.meshtastic.feature.settings.component.HomoglyphSetting
-import org.meshtastic.feature.settings.component.NodeLayoutSettings
import org.meshtastic.feature.settings.component.NotificationSection
import org.meshtastic.feature.settings.component.ThemePickerDialog
import org.meshtastic.feature.settings.navigation.ConfigRoute
@@ -203,39 +204,14 @@ fun DesktopSettingsScreen(
)
}
- val densityName by settingsViewModel.nodeListDensity.collectAsStateWithLifecycle()
- val density = org.meshtastic.core.model.NodeListDensity.fromName(densityName)
- val showPower by settingsViewModel.shouldShowPower.collectAsStateWithLifecycle()
- val showLastHeard by settingsViewModel.shouldShowLastHeard.collectAsStateWithLifecycle()
- val lastHeardRelative by settingsViewModel.lastHeardIsRelative.collectAsStateWithLifecycle()
- val showLocation by settingsViewModel.shouldShowLocation.collectAsStateWithLifecycle()
- val showHops by settingsViewModel.shouldShowHops.collectAsStateWithLifecycle()
- val showSignal by settingsViewModel.shouldShowSignal.collectAsStateWithLifecycle()
- val showChannel by settingsViewModel.shouldShowChannel.collectAsStateWithLifecycle()
- val showRole by settingsViewModel.shouldShowRole.collectAsStateWithLifecycle()
- val showTelemetry by settingsViewModel.shouldShowTelemetry.collectAsStateWithLifecycle()
- NodeLayoutSettings(
- density = density,
- onDensityChange = { settingsViewModel.setNodeListDensity(it.name) },
- showPower = showPower,
- onShowPowerChange = { settingsViewModel.setShouldShowPower(it) },
- showLastHeard = showLastHeard,
- onShowLastHeardChange = { settingsViewModel.setShouldShowLastHeard(it) },
- lastHeardIsRelative = lastHeardRelative,
- onLastHeardIsRelativeChange = { settingsViewModel.setLastHeardIsRelative(it) },
- showLocation = showLocation,
- onShowLocationChange = { settingsViewModel.setShouldShowLocation(it) },
- showHops = showHops,
- onShowHopsChange = { settingsViewModel.setShouldShowHops(it) },
- showSignal = showSignal,
- onShowSignalChange = { settingsViewModel.setShouldShowSignal(it) },
- showChannel = showChannel,
- onShowChannelChange = { settingsViewModel.setShouldShowChannel(it) },
- showRole = showRole,
- onShowRoleChange = { settingsViewModel.setShouldShowRole(it) },
- showTelemetry = showTelemetry,
- onShowTelemetryChange = { settingsViewModel.setShouldShowTelemetry(it) },
- )
+ ExpressiveSection(title = stringResource(Res.string.node_layout_section_title)) {
+ ListItem(
+ text = stringResource(Res.string.node_layout_section_title),
+ leadingIcon = MeshtasticIcons.List,
+ ) {
+ onNavigate(SettingsRoute.NodeList)
+ }
+ }
ExpressiveSection(title = stringResource(Res.string.wifi_devices)) {
ListItem(text = stringResource(Res.string.wifi_devices), leadingIcon = MeshtasticIcons.Wifi) {
diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompactMinimal_Dark_d19fbf1f_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompactMinimal_Dark_d19fbf1f_0.png
index 2bb7a1aca..ab30b43ff 100644
Binary files a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompactMinimal_Dark_d19fbf1f_0.png and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompactMinimal_Dark_d19fbf1f_0.png differ
diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompactMinimal_Light_b29dc7a7_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompactMinimal_Light_b29dc7a7_0.png
index f862285e8..4968e3466 100644
Binary files a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompactMinimal_Light_b29dc7a7_0.png and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompactMinimal_Light_b29dc7a7_0.png differ
diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompact_Dark_d19fbf1f_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompact_Dark_d19fbf1f_0.png
index 0b9e9dae4..eed0d7927 100644
Binary files a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompact_Dark_d19fbf1f_0.png and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompact_Dark_d19fbf1f_0.png differ
diff --git a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompact_Light_b29dc7a7_0.png b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompact_Light_b29dc7a7_0.png
index 91c444721..afd72f12a 100644
Binary files a/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompact_Light_b29dc7a7_0.png and b/screenshot-tests/src/screenshotTestDebug/reference/org/meshtastic/screenshots/feature/SettingsScreenshotTestsKt/ScreenshotNodeLayoutSettingsCompact_Light_b29dc7a7_0.png differ