From 32b73f7f15ef1e43e51da03e991f6913203f1705 Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:57:47 -0400 Subject: [PATCH] Migrate node sort prefs to datastore (#3241) --- .../geeksville/mesh/ui/node/NodesViewModel.kt | 32 ++++++++++------ .../core/datastore/UiPreferencesDataSource.kt | 38 +++++++++++++++++++ .../core/datastore/di/DataStoreModule.kt | 16 +++++++- .../org/meshtastic/core/prefs/ui/UiPrefs.kt | 10 ----- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/NodesViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/node/NodesViewModel.kt index c5d5af0fe..6ba62efd2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/node/NodesViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/node/NodesViewModel.kt @@ -40,6 +40,7 @@ import org.meshtastic.core.data.repository.NodeRepository import org.meshtastic.core.data.repository.RadioConfigRepository import org.meshtastic.core.database.model.Node import org.meshtastic.core.database.model.NodeSortOption +import org.meshtastic.core.datastore.UiPreferencesDataSource import org.meshtastic.core.prefs.ui.UiPrefs import timber.log.Timber import javax.inject.Inject @@ -52,6 +53,7 @@ constructor( radioConfigRepository: RadioConfigRepository, private val serviceRepository: ServiceRepository, private val uiPrefs: UiPrefs, + private val uiPreferencesDataSource: UiPreferencesDataSource, ) : ViewModel() { val ourNodeInfo: StateFlow = nodeRepository.ourNodeInfo @@ -76,14 +78,13 @@ constructor( val sharedContactRequested = _sharedContactRequested.asStateFlow() private val nodeSortOption = - MutableStateFlow(NodeSortOption.entries.getOrElse(uiPrefs.nodeSortOption) { NodeSortOption.VIA_FAVORITE }) + uiPreferencesDataSource.nodeSort.map { NodeSortOption.entries.getOrElse(it) { NodeSortOption.VIA_FAVORITE } } private val nodeFilterText = MutableStateFlow("") - private val includeUnknown = MutableStateFlow(uiPrefs.includeUnknown) - private val onlyOnline = MutableStateFlow(uiPrefs.onlyOnline) - private val onlyDirect = MutableStateFlow(uiPrefs.onlyDirect) - private val _showIgnored = MutableStateFlow(uiPrefs.showIgnored) - val showIgnored: StateFlow = _showIgnored + private val includeUnknown = uiPreferencesDataSource.includeUnknown + private val onlyOnline = uiPreferencesDataSource.onlyOnline + private val onlyDirect = uiPreferencesDataSource.onlyDirect + private val showIgnored = uiPreferencesDataSource.showIgnored private val nodeFilter: Flow = combine(nodeFilterText, includeUnknown, onlyOnline, onlyDirect, showIgnored) { @@ -151,17 +152,24 @@ constructor( nodeFilterText.value = text } - fun toggleIncludeUnknown() = toggle(includeUnknown) { uiPrefs.includeUnknown = it } + fun toggleIncludeUnknown() { + uiPreferencesDataSource.setIncludeUnknown(!includeUnknown.value) + } - fun toggleOnlyOnline() = toggle(onlyOnline) { uiPrefs.onlyOnline = it } + fun toggleOnlyOnline() { + uiPreferencesDataSource.setOnlyOnline(!onlyOnline.value) + } - fun toggleOnlyDirect() = toggle(onlyDirect) { uiPrefs.onlyDirect = it } + fun toggleOnlyDirect() { + uiPreferencesDataSource.setOnlyDirect(!onlyDirect.value) + } - fun toggleShowIgnored() = toggle(_showIgnored) { uiPrefs.showIgnored = it } + fun toggleShowIgnored() { + uiPreferencesDataSource.setShowIgnored(!showIgnored.value) + } fun setSortOption(sort: NodeSortOption) { - nodeSortOption.value = sort - uiPrefs.nodeSortOption = sort.ordinal + uiPreferencesDataSource.setNodeSort(sort.ordinal) } fun toggleShowDetails() = toggle(showDetails) { uiPrefs.showDetails = it } diff --git a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/UiPreferencesDataSource.kt b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/UiPreferencesDataSource.kt index ef47432dd..62dace0df 100644 --- a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/UiPreferencesDataSource.kt +++ b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/UiPreferencesDataSource.kt @@ -36,6 +36,13 @@ import javax.inject.Singleton internal const val KEY_APP_INTRO_COMPLETED = "app_intro_completed" internal const val KEY_THEME = "theme" +// Node list filters/sort +internal const val KEY_NODE_SORT = "node-sort-option" +internal const val KEY_INCLUDE_UNKNOWN = "include-unknown" +internal const val KEY_ONLY_ONLINE = "only-online" +internal const val KEY_ONLY_DIRECT = "only-direct" +internal const val KEY_SHOW_IGNORED = "show-ignored" + @Singleton class UiPreferencesDataSource @Inject constructor(private val dataStore: DataStore) { @@ -46,6 +53,12 @@ class UiPreferencesDataSource @Inject constructor(private val dataStore: DataSto // Default value for AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM val theme: StateFlow = dataStore.prefStateFlow(key = THEME, default = -1) + val nodeSort: StateFlow = dataStore.prefStateFlow(key = NODE_SORT, default = -1) + val includeUnknown: StateFlow = dataStore.prefStateFlow(key = INCLUDE_UNKNOWN, default = false) + val onlyOnline: StateFlow = dataStore.prefStateFlow(key = ONLY_ONLINE, default = false) + val onlyDirect: StateFlow = dataStore.prefStateFlow(key = ONLY_DIRECT, default = false) + val showIgnored: StateFlow = dataStore.prefStateFlow(key = SHOW_IGNORED, default = false) + fun setAppIntroCompleted(completed: Boolean) { dataStore.setPref(key = APP_INTRO_COMPLETED, value = completed) } @@ -54,6 +67,26 @@ class UiPreferencesDataSource @Inject constructor(private val dataStore: DataSto dataStore.setPref(key = THEME, value = value) } + fun setNodeSort(value: Int) { + dataStore.setPref(key = NODE_SORT, value = value) + } + + fun setIncludeUnknown(value: Boolean) { + dataStore.setPref(key = INCLUDE_UNKNOWN, value = value) + } + + fun setOnlyOnline(value: Boolean) { + dataStore.setPref(key = ONLY_ONLINE, value = value) + } + + fun setOnlyDirect(value: Boolean) { + dataStore.setPref(key = ONLY_DIRECT, value = value) + } + + fun setShowIgnored(value: Boolean) { + dataStore.setPref(key = SHOW_IGNORED, value = value) + } + private fun DataStore.prefStateFlow(key: Preferences.Key, default: T): StateFlow = data.map { it[key] ?: default }.stateIn(scope = scope, started = SharingStarted.Lazily, initialValue = default) @@ -64,5 +97,10 @@ class UiPreferencesDataSource @Inject constructor(private val dataStore: DataSto private companion object { val APP_INTRO_COMPLETED = booleanPreferencesKey(KEY_APP_INTRO_COMPLETED) val THEME = intPreferencesKey(KEY_THEME) + val NODE_SORT = intPreferencesKey(KEY_NODE_SORT) + val INCLUDE_UNKNOWN = booleanPreferencesKey(KEY_INCLUDE_UNKNOWN) + val ONLY_ONLINE = booleanPreferencesKey(KEY_ONLY_ONLINE) + val ONLY_DIRECT = booleanPreferencesKey(KEY_ONLY_DIRECT) + val SHOW_IGNORED = booleanPreferencesKey(KEY_SHOW_IGNORED) } } diff --git a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt index 2438a2759..29a019b02 100644 --- a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt +++ b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt @@ -39,6 +39,11 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import org.meshtastic.core.datastore.KEY_APP_INTRO_COMPLETED +import org.meshtastic.core.datastore.KEY_INCLUDE_UNKNOWN +import org.meshtastic.core.datastore.KEY_NODE_SORT +import org.meshtastic.core.datastore.KEY_ONLY_DIRECT +import org.meshtastic.core.datastore.KEY_ONLY_ONLINE +import org.meshtastic.core.datastore.KEY_SHOW_IGNORED import org.meshtastic.core.datastore.KEY_THEME import org.meshtastic.core.datastore.serializer.ChannelSetSerializer import org.meshtastic.core.datastore.serializer.LocalConfigSerializer @@ -61,7 +66,16 @@ object DataStoreModule { SharedPreferencesMigration( context = appContext, sharedPreferencesName = "ui-prefs", - keysToMigrate = setOf(KEY_APP_INTRO_COMPLETED, KEY_THEME), + keysToMigrate = + setOf( + KEY_APP_INTRO_COMPLETED, + KEY_THEME, + KEY_NODE_SORT, + KEY_INCLUDE_UNKNOWN, + KEY_ONLY_ONLINE, + KEY_ONLY_DIRECT, + KEY_SHOW_IGNORED, + ), ), ), scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), diff --git a/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/ui/UiPrefs.kt b/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/ui/UiPrefs.kt index 262a5252b..1da38bcea 100644 --- a/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/ui/UiPrefs.kt +++ b/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/ui/UiPrefs.kt @@ -30,12 +30,7 @@ import javax.inject.Singleton interface UiPrefs { var hasShownNotPairedWarning: Boolean - var nodeSortOption: Int - var includeUnknown: Boolean var showDetails: Boolean - var onlyOnline: Boolean - var onlyDirect: Boolean - var showIgnored: Boolean var showQuickChat: Boolean fun shouldProvideNodeLocation(nodeNum: Int): StateFlow @@ -68,12 +63,7 @@ class UiPrefsImpl @Inject constructor(@UiSharedPreferences private val prefs: Sh } override var hasShownNotPairedWarning: Boolean by PrefDelegate(prefs, "has_shown_not_paired_warning", false) - override var nodeSortOption: Int by PrefDelegate(prefs, "node-sort-option", -1) - override var includeUnknown: Boolean by PrefDelegate(prefs, "include-unknown", false) override var showDetails: Boolean by PrefDelegate(prefs, "show-details", false) - override var onlyOnline: Boolean by PrefDelegate(prefs, "only-online", false) - override var onlyDirect: Boolean by PrefDelegate(prefs, "only-direct", false) - override var showIgnored: Boolean by PrefDelegate(prefs, "show-ignored", false) override var showQuickChat: Boolean by PrefDelegate(prefs, "show-quick-chat", false) override fun shouldProvideNodeLocation(nodeNum: Int): StateFlow = provideNodeLocationFlows