Extract node list display settings to dedicated screen (#5580)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@@ -166,6 +166,8 @@ sealed interface SettingsRoute : Route {
|
||||
|
||||
@Serializable data object FilterSettings : SettingsRoute
|
||||
|
||||
@Serializable data object NodeList : SettingsRoute
|
||||
|
||||
// endregion
|
||||
|
||||
// region help & documentation routes
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<NavKey>, destNumOverride: In
|
||||
?: remember(backStack.toList()) {
|
||||
backStack.lastOrNull { it is SettingsRoute.Settings }?.let { (it as SettingsRoute.Settings).destNum }
|
||||
}
|
||||
return koinViewModel<RadioConfigViewModel>(key = destNum?.toString()) {
|
||||
parametersOf(destNum)
|
||||
}
|
||||
return koinViewModel<RadioConfigViewModel>(key = destNum?.toString()) { parametersOf(destNum) }
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@@ -243,6 +242,14 @@ fun EntryProviderScope<NavKey>.settingsGraph(backStack: NavBackStack<NavKey>) {
|
||||
val viewModel: FilterSettingsViewModel = koinViewModel()
|
||||
FilterSettingsScreen(viewModel = viewModel, onBack = dropUnlessResumed { backStack.removeLastOrNull() })
|
||||
}
|
||||
|
||||
entry<SettingsRoute.NodeList> {
|
||||
val settingsViewModel: SettingsViewModel = koinViewModel()
|
||||
NodeListScreen(
|
||||
settingsViewModel = settingsViewModel,
|
||||
onNavigateUp = dropUnlessResumed { backStack.removeLastOrNull() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Expect declaration for the platform-specific settings main screen. */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 137 KiB |