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