mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Pref fixes (#3175)
This commit is contained in:
parent
c5c433c165
commit
a1d9f926cb
7 changed files with 99 additions and 29 deletions
|
|
@ -139,6 +139,8 @@ android {
|
|||
release {
|
||||
if (keystoreProperties["storeFile"] != null) {
|
||||
signingConfig = signingConfigs.named("release").get()
|
||||
} else {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import androidx.compose.ui.platform.LocalView
|
|||
import androidx.core.net.toUri
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
|
|
@ -48,8 +49,9 @@ import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC
|
|||
import com.geeksville.mesh.ui.intro.AppIntroductionScreen
|
||||
import com.geeksville.mesh.ui.sharing.toSharedContact
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
|
@ -61,7 +63,7 @@ class MainActivity :
|
|||
// This is aware of the Activity lifecycle and handles binding to the mesh service.
|
||||
@Inject internal lateinit var meshServiceClient: MeshServiceClient
|
||||
|
||||
@Inject internal lateinit var uiPrefs: UiPrefs
|
||||
@Inject internal lateinit var uiPreferencesDataSource: UiPreferencesDataSource
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
|
|
@ -77,8 +79,11 @@ class MainActivity :
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (uiPrefs.appIntroCompleted) {
|
||||
(application as GeeksvilleApplication).askToRate(this)
|
||||
lifecycleScope.launch {
|
||||
val appIntroCompleted = uiPreferencesDataSource.appIntroCompleted.value
|
||||
if (appIntroCompleted) {
|
||||
(application as GeeksvilleApplication).askToRate(this@MainActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ import kotlinx.coroutines.flow.shareIn
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.strings.R
|
||||
|
|
@ -188,11 +189,12 @@ constructor(
|
|||
private val quickChatActionRepository: QuickChatActionRepository,
|
||||
firmwareReleaseRepository: FirmwareReleaseRepository,
|
||||
private val uiPrefs: UiPrefs,
|
||||
private val uiPreferencesDataSource: UiPreferencesDataSource,
|
||||
private val meshServiceNotifications: MeshServiceNotifications,
|
||||
) : ViewModel(),
|
||||
Logging {
|
||||
|
||||
val theme: StateFlow<Int> = uiPrefs.themeFlow
|
||||
val theme: StateFlow<Int> = uiPreferencesDataSource.theme
|
||||
|
||||
private val _lastTraceRouteTime = MutableStateFlow<Long?>(null)
|
||||
val lastTraceRouteTime: StateFlow<Long?> = _lastTraceRouteTime.asStateFlow()
|
||||
|
|
@ -822,9 +824,9 @@ constructor(
|
|||
nodeFilterText.value = text
|
||||
}
|
||||
|
||||
val appIntroCompleted: StateFlow<Boolean> = uiPrefs.appIntroCompletedFlow
|
||||
val appIntroCompleted: StateFlow<Boolean> = uiPreferencesDataSource.appIntroCompleted
|
||||
|
||||
fun onAppIntroCompleted() {
|
||||
uiPrefs.appIntroCompleted = true
|
||||
uiPreferencesDataSource.setAppIntroCompleted(true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import java.io.BufferedWriter
|
||||
import java.io.FileNotFoundException
|
||||
|
|
@ -65,6 +66,7 @@ constructor(
|
|||
private val nodeRepository: NodeRepository,
|
||||
private val meshLogRepository: MeshLogRepository,
|
||||
private val uiPrefs: UiPrefs,
|
||||
private val uiPreferencesDataSource: UiPreferencesDataSource,
|
||||
) : ViewModel(),
|
||||
Logging {
|
||||
val myNodeInfo: StateFlow<MyNodeEntity?> = nodeRepository.myNodeInfo
|
||||
|
|
@ -109,11 +111,11 @@ constructor(
|
|||
}
|
||||
|
||||
fun setTheme(theme: Int) {
|
||||
uiPrefs.theme = theme
|
||||
uiPreferencesDataSource.setTheme(theme)
|
||||
}
|
||||
|
||||
fun showAppIntro() {
|
||||
uiPrefs.appIntroCompleted = false
|
||||
uiPreferencesDataSource.setAppIntroCompleted(false)
|
||||
}
|
||||
|
||||
fun unlockExcludedModules() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.datastore
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
internal const val KEY_APP_INTRO_COMPLETED = "app_intro_completed"
|
||||
internal const val KEY_THEME = "theme"
|
||||
|
||||
@Singleton
|
||||
class UiPreferencesDataSource @Inject constructor(private val dataStore: DataStore<Preferences>) {
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
val appIntroCompleted: StateFlow<Boolean> = dataStore.prefStateFlow(key = APP_INTRO_COMPLETED, default = false)
|
||||
|
||||
// Default value for AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
val theme: StateFlow<Int> = dataStore.prefStateFlow(key = THEME, default = -1)
|
||||
|
||||
fun setAppIntroCompleted(completed: Boolean) {
|
||||
dataStore.setPref(key = APP_INTRO_COMPLETED, value = completed)
|
||||
}
|
||||
|
||||
fun setTheme(value: Int) {
|
||||
dataStore.setPref(key = THEME, value = value)
|
||||
}
|
||||
|
||||
private fun <T : Any> DataStore<Preferences>.prefStateFlow(key: Preferences.Key<T>, default: T): StateFlow<T> =
|
||||
data.map { it[key] ?: default }.stateIn(scope = scope, started = SharingStarted.Lazily, initialValue = default)
|
||||
|
||||
private fun <T : Any> DataStore<Preferences>.setPref(key: Preferences.Key<T>, value: T) {
|
||||
scope.launch { edit { it[key] = value } }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val APP_INTRO_COMPLETED = booleanPreferencesKey(KEY_APP_INTRO_COMPLETED)
|
||||
val THEME = intPreferencesKey(KEY_THEME)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,8 @@ import dagger.hilt.components.SingletonComponent
|
|||
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_THEME
|
||||
import org.meshtastic.core.datastore.serializer.ChannelSetSerializer
|
||||
import org.meshtastic.core.datastore.serializer.LocalConfigSerializer
|
||||
import org.meshtastic.core.datastore.serializer.ModuleConfigSerializer
|
||||
|
|
@ -53,7 +55,15 @@ object DataStoreModule {
|
|||
fun providePreferencesDataStore(@ApplicationContext appContext: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }),
|
||||
migrations = listOf(SharedPreferencesMigration(appContext, USER_PREFERENCES_NAME)),
|
||||
migrations =
|
||||
listOf(
|
||||
SharedPreferencesMigration(context = appContext, sharedPreferencesName = USER_PREFERENCES_NAME),
|
||||
SharedPreferencesMigration(
|
||||
context = appContext,
|
||||
sharedPreferencesName = "ui-prefs",
|
||||
keysToMigrate = setOf(KEY_APP_INTRO_COMPLETED, KEY_THEME),
|
||||
),
|
||||
),
|
||||
scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
|
||||
produceFile = { appContext.preferencesDataStoreFile(USER_PREFERENCES_NAME) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,12 +18,10 @@
|
|||
package org.meshtastic.core.prefs.ui
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.edit
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.meshtastic.core.prefs.PrefDelegate
|
||||
import org.meshtastic.core.prefs.di.UiSharedPreferences
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
|
@ -31,10 +29,6 @@ import javax.inject.Inject
|
|||
import javax.inject.Singleton
|
||||
|
||||
interface UiPrefs {
|
||||
var theme: Int
|
||||
val themeFlow: StateFlow<Int>
|
||||
var appIntroCompleted: Boolean
|
||||
val appIntroCompletedFlow: StateFlow<Boolean>
|
||||
var hasShownNotPairedWarning: Boolean
|
||||
var nodeSortOption: Int
|
||||
var includeUnknown: Boolean
|
||||
|
|
@ -49,28 +43,15 @@ interface UiPrefs {
|
|||
fun setShouldProvideNodeLocation(nodeNum: Int, value: Boolean)
|
||||
}
|
||||
|
||||
const val KEY_THEME = "theme"
|
||||
const val KEY_APP_INTRO_COMPLETED = "app_intro_completed"
|
||||
|
||||
@Singleton
|
||||
class UiPrefsImpl @Inject constructor(@UiSharedPreferences private val prefs: SharedPreferences) : UiPrefs {
|
||||
|
||||
override var theme: Int by PrefDelegate(prefs, KEY_THEME, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
private var _themeFlow = MutableStateFlow(theme)
|
||||
override val themeFlow = _themeFlow.asStateFlow()
|
||||
|
||||
override var appIntroCompleted: Boolean by PrefDelegate(prefs, KEY_APP_INTRO_COMPLETED, false)
|
||||
private var _appIntroCompletedFlow = MutableStateFlow(appIntroCompleted)
|
||||
override val appIntroCompletedFlow = _appIntroCompletedFlow.asStateFlow()
|
||||
|
||||
// Maps nodeNum to a flow for the for the "provide-location-nodeNum" pref
|
||||
private val provideNodeLocationFlows = ConcurrentHashMap<Int, MutableStateFlow<Boolean>>()
|
||||
|
||||
private val sharedPreferencesListener =
|
||||
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
||||
when (key) {
|
||||
KEY_THEME -> _themeFlow.update { theme }
|
||||
KEY_APP_INTRO_COMPLETED -> _appIntroCompletedFlow.update { appIntroCompleted }
|
||||
// Check if the changed key is one of our node location keys
|
||||
else ->
|
||||
provideNodeLocationFlows.keys.forEach { nodeNum ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue