Modularize prefs classes (#3171)

This commit is contained in:
Phil Oliver 2025-09-23 05:51:03 -04:00 committed by GitHub
parent 53fdda3a9c
commit b98e533123
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 290 additions and 234 deletions

View file

@ -18,8 +18,8 @@
package com.geeksville.mesh
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
import dagger.hilt.android.HiltAndroidApp
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import javax.inject.Inject
@HiltAndroidApp

View file

@ -28,8 +28,8 @@ import com.geeksville.mesh.analytics.AnalyticsProvider
import com.geeksville.mesh.analytics.NopAnalytics
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.BuildUtils.info
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import timber.log.Timber
abstract class GeeksvilleApplication :

View file

@ -17,11 +17,11 @@
package com.geeksville.mesh.ui.map
import com.geeksville.mesh.android.prefs.MapPrefs
import com.geeksville.mesh.database.NodeRepository
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import org.meshtastic.core.prefs.map.MapPrefs
import javax.inject.Inject
@HiltViewModel

View file

@ -18,8 +18,8 @@
package com.geeksville.mesh
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
import dagger.hilt.android.HiltAndroidApp
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import javax.inject.Inject
@HiltAndroidApp

View file

@ -48,7 +48,6 @@ import com.datadog.android.trace.opentelemetry.DatadogOpenTelemetry
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.analytics.AnalyticsProvider
import com.geeksville.mesh.analytics.FirebaseAnalytics
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
import com.geeksville.mesh.util.exceptionReporter
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailabilityLight
@ -60,6 +59,7 @@ import com.google.firebase.initialize
import com.suddenh4x.ratingdialog.AppRating
import io.opentelemetry.api.GlobalOpenTelemetry
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import timber.log.Timber
abstract class GeeksvilleApplication :

View file

@ -17,7 +17,6 @@
package com.geeksville.mesh.repository.map
import com.geeksville.mesh.android.prefs.MapTileProviderPrefs
import com.geeksville.mesh.di.IoDispatcher
import com.geeksville.mesh.ui.map.CustomTileProviderConfig
import kotlinx.coroutines.CoroutineDispatcher
@ -27,6 +26,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import org.meshtastic.core.prefs.map.MapTileProviderPrefs
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

View file

@ -23,8 +23,6 @@ import androidx.core.net.toFile
import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.prefs.GoogleMapsPrefs
import com.geeksville.mesh.android.prefs.MapPrefs
import com.geeksville.mesh.database.NodeRepository
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
@ -52,6 +50,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import org.json.JSONObject
import org.meshtastic.core.prefs.map.GoogleMapsPrefs
import org.meshtastic.core.prefs.map.MapPrefs
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream

View file

@ -41,7 +41,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.prefs.UiPrefs
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.MainScreen
import com.geeksville.mesh.ui.common.theme.AppTheme
@ -50,6 +49,7 @@ import com.geeksville.mesh.ui.intro.AppIntroductionScreen
import com.geeksville.mesh.ui.sharing.toSharedContact
import dagger.hilt.android.AndroidEntryPoint
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.prefs.ui.UiPrefs
import javax.inject.Inject
@AndroidEntryPoint

View file

@ -1,170 +0,0 @@
/*
* 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 com.geeksville.mesh.android.prefs
import android.content.Context
import android.content.SharedPreferences
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Qualifier
import javax.inject.Singleton
// These pref store qualifiers are private to prevent prefs stores from being injected directly.
// Consuming code should always inject one of the prefs repositories.
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class AnalyticsSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class AppSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class CustomEmojiSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class MapSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class MapConsentSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class MapTileProviderSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class MeshSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class RadioSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class UiSharedPreferences
@Suppress("TooManyFunctions")
@InstallIn(SingletonComponent::class)
@Module
object PrefsModule {
@Provides
@Singleton
@AnalyticsSharedPreferences
fun provideAnalyticsSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("analytics-prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@AppSharedPreferences
fun provideAppSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@CustomEmojiSharedPreferences
fun provideCustomEmojiSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("org.geeksville.emoji.prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@MapSharedPreferences
fun provideMapSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("map_prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@MapConsentSharedPreferences
fun provideMapConsentSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("map_consent_preferences", Context.MODE_PRIVATE)
@Provides
@Singleton
@MapTileProviderSharedPreferences
fun provideMapTileProviderSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("map_tile_provider_prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@MeshSharedPreferences
fun provideMeshSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("mesh-prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@RadioSharedPreferences
fun provideRadioSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@UiSharedPreferences
fun provideUiSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
fun provideAnalyticsPrefs(
@AnalyticsSharedPreferences analyticsPreferences: SharedPreferences,
@AppSharedPreferences appPreferences: SharedPreferences,
): AnalyticsPrefs = AnalyticsPrefsImpl(analyticsPreferences, appPreferences)
@Provides
@Singleton
fun provideCustomEmojiPrefs(@CustomEmojiSharedPreferences sharedPreferences: SharedPreferences): CustomEmojiPrefs =
CustomEmojiPrefsImpl(sharedPreferences)
@Provides
@Singleton
fun provideMapPrefs(@MapSharedPreferences sharedPreferences: SharedPreferences): MapPrefs =
MapPrefsImpl(sharedPreferences)
@Provides
@Singleton
fun provideMapConsentPrefs(@MapConsentSharedPreferences sharedPreferences: SharedPreferences): MapConsentPrefs =
MapConsentPrefsImpl(sharedPreferences)
@Provides
@Singleton
fun provideMapTileProviderPrefs(
@MapTileProviderSharedPreferences sharedPreferences: SharedPreferences,
): MapTileProviderPrefs = MapTileProviderPrefsImpl(sharedPreferences)
@Provides
@Singleton
fun provideMeshPrefs(@MeshSharedPreferences sharedPreferences: SharedPreferences): MeshPrefs =
MeshPrefsImpl(sharedPreferences)
@Provides
@Singleton
fun provideRadioPrefs(@RadioSharedPreferences sharedPreferences: SharedPreferences): RadioPrefs =
RadioPrefsImpl(sharedPreferences)
@Provides
@Singleton
fun provideUiPrefs(@UiSharedPreferences sharedPreferences: SharedPreferences): UiPrefs =
UiPrefsImpl(sharedPreferences)
}

View file

@ -36,7 +36,6 @@ import com.geeksville.mesh.Portnums
import com.geeksville.mesh.Portnums.PortNum
import com.geeksville.mesh.TelemetryProtos.Telemetry
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.prefs.MapPrefs
import com.geeksville.mesh.database.MeshLogRepository
import com.geeksville.mesh.database.entity.FirmwareRelease
import com.geeksville.mesh.database.entity.MeshLog
@ -62,6 +61,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.strings.R
import org.meshtastic.feature.map.model.CustomTileSource
import java.io.BufferedWriter

View file

@ -39,7 +39,6 @@ import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.Position
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.prefs.UiPrefs
import com.geeksville.mesh.channel
import com.geeksville.mesh.channelSet
import com.geeksville.mesh.channelSettings
@ -83,6 +82,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.strings.R
import javax.inject.Inject

View file

@ -27,7 +27,6 @@ import com.geeksville.mesh.android.BinaryLogFile
import com.geeksville.mesh.android.BuildUtils
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.prefs.RadioPrefs
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.network.NetworkRepository
@ -49,6 +48,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.meshtastic.core.prefs.radio.RadioPrefs
import javax.inject.Inject
import javax.inject.Singleton
@ -195,9 +195,7 @@ constructor(
private fun broadcastConnectionChanged(newState: ConnectionState) {
debug("Broadcasting connection state change to $newState")
processLifecycle.coroutineScope.launch(dispatchers.default) {
_connectionState.emit(newState)
}
processLifecycle.coroutineScope.launch(dispatchers.default) { _connectionState.emit(newState) }
}
// Send a packet/command out the radio link, this routine can block if it needs to

View file

@ -58,8 +58,6 @@ import com.geeksville.mesh.analytics.DataPair
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.hasLocationPermission
import com.geeksville.mesh.android.prefs.MeshPrefs
import com.geeksville.mesh.android.prefs.UiPrefs
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.copy
import com.geeksville.mesh.database.MeshLogRepository
@ -103,6 +101,8 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.meshtastic.core.model.getFullTracerouteResponse
import org.meshtastic.core.prefs.mesh.MeshPrefs
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.strings.R
import java.util.Random
import java.util.UUID

View file

@ -18,8 +18,8 @@
package com.geeksville.mesh.ui.common
import androidx.lifecycle.ViewModel
import com.geeksville.mesh.android.prefs.CustomEmojiPrefs
import dagger.hilt.android.lifecycle.HiltViewModel
import org.meshtastic.core.prefs.emoji.CustomEmojiPrefs
import javax.inject.Inject
@HiltViewModel

View file

@ -20,7 +20,6 @@ package com.geeksville.mesh.ui.connections
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import com.geeksville.mesh.android.prefs.UiPrefs
import com.geeksville.mesh.database.NodeRepository
import com.geeksville.mesh.database.entity.MyNodeEntity
import com.geeksville.mesh.model.Node
@ -32,6 +31,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn
import org.meshtastic.core.prefs.ui.UiPrefs
import javax.inject.Inject
@HiltViewModel

View file

@ -19,7 +19,6 @@ package com.geeksville.mesh.ui.map
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.android.prefs.MapPrefs
import com.geeksville.mesh.database.NodeRepository
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.Packet
@ -32,6 +31,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import org.meshtastic.core.prefs.map.MapPrefs
@Suppress("TooManyFunctions")
abstract class BaseMapViewModel(

View file

@ -27,7 +27,6 @@ import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.Portnums
import com.geeksville.mesh.Position
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.prefs.UiPrefs
import com.geeksville.mesh.database.MeshLogRepository
import com.geeksville.mesh.database.NodeRepository
import com.geeksville.mesh.database.entity.MyNodeEntity
@ -48,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.prefs.ui.UiPrefs
import java.io.BufferedWriter
import java.io.FileNotFoundException
import java.io.FileWriter

View file

@ -45,8 +45,6 @@ import com.geeksville.mesh.Position
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.isAnalyticsAvailable
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
import com.geeksville.mesh.android.prefs.MapConsentPrefs
import com.geeksville.mesh.config
import com.geeksville.mesh.database.entity.MyNodeEntity
import com.geeksville.mesh.deviceProfile
@ -77,6 +75,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import org.meshtastic.core.prefs.map.MapConsentPrefs
import org.meshtastic.core.strings.R
import java.io.FileOutputStream
import javax.inject.Inject

View file

@ -17,9 +17,14 @@
plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.hilt)
alias(libs.plugins.kover)
}
android { namespace = "org.meshtastic.core.prefs" }
dependencies {}
dependencies {
implementation(libs.bundles.coroutines)
implementation(libs.appcompat)
googleImplementation(libs.maps.compose)
}

View file

@ -15,37 +15,40 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.di
import android.content.Context
import android.content.SharedPreferences
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import org.meshtastic.core.prefs.map.GoogleMapsPrefs
import org.meshtastic.core.prefs.map.GoogleMapsPrefsImpl
import javax.inject.Qualifier
import javax.inject.Singleton
// Pref store qualifiers are private to prevent prefs stores from being injected directly.
// Pref store qualifiers are internal to prevent prefs stores from being injected directly.
// Consuming code should always inject one of the prefs repositories.
@Qualifier
@Retention(AnnotationRetention.BINARY)
private annotation class GoogleMapsSharedPreferences
internal annotation class GoogleMapsSharedPreferences
@InstallIn(SingletonComponent::class)
@Module
object GoogleMapsModule {
interface GoogleMapsModule {
@Provides
@Singleton
@GoogleMapsSharedPreferences
fun provideGoogleMapsSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("google_maps_prefs", Context.MODE_PRIVATE)
@Binds fun bindGoogleMapsPrefs(googleMapsPrefsImpl: GoogleMapsPrefsImpl): GoogleMapsPrefs
@Provides
@Singleton
fun provideGoogleMapsPrefs(@GoogleMapsSharedPreferences sharedPreferences: SharedPreferences): GoogleMapsPrefs =
GoogleMapsPrefsImpl(sharedPreferences)
companion object {
@Provides
@Singleton
@GoogleMapsSharedPreferences
fun provideGoogleMapsSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("google_maps_prefs", Context.MODE_PRIVATE)
}
}

View file

@ -15,10 +15,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.map
import android.content.SharedPreferences
import com.google.maps.android.compose.MapType
import org.meshtastic.core.prefs.NullableStringPrefDelegate
import org.meshtastic.core.prefs.StringSetPrefDelegate
import org.meshtastic.core.prefs.di.GoogleMapsSharedPreferences
import javax.inject.Inject
import javax.inject.Singleton
/** Interface for prefs specific to Google Maps. For general map prefs, see MapPrefs. */
interface GoogleMapsPrefs {
@ -27,7 +32,8 @@ interface GoogleMapsPrefs {
var hiddenLayerUrls: Set<String>
}
class GoogleMapsPrefsImpl(prefs: SharedPreferences) : GoogleMapsPrefs {
@Singleton
class GoogleMapsPrefsImpl @Inject constructor(@GoogleMapsSharedPreferences prefs: SharedPreferences) : GoogleMapsPrefs {
override var selectedGoogleMapType: String? by
NullableStringPrefDelegate(prefs, "selected_google_map_type", MapType.NORMAL.name)
override var selectedCustomTileUrl: String? by NullableStringPrefDelegate(prefs, "selected_custom_tile_url", null)

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs
import android.content.SharedPreferences
import androidx.core.content.edit
@ -29,7 +29,7 @@ import kotlin.reflect.KProperty
* @param key The key used to store and retrieve the value.
* @param defaultValue The default value to return if no value is found.
*/
class NullableStringPrefDelegate(
internal class NullableStringPrefDelegate(
private val prefs: SharedPreferences,
private val key: String,
private val defaultValue: String?,

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs
import android.content.SharedPreferences
import androidx.core.content.edit
@ -30,8 +30,11 @@ import kotlin.reflect.KProperty
* @param defaultValue The default value to return if no value is found.
* @throws IllegalArgumentException if the type is not supported.
*/
class PrefDelegate<T>(private val prefs: SharedPreferences, private val key: String, private val defaultValue: T) :
ReadWriteProperty<Any?, T> {
internal class PrefDelegate<T>(
private val prefs: SharedPreferences,
private val key: String,
private val defaultValue: T,
) : ReadWriteProperty<Any?, T> {
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T = when (defaultValue) {

View file

@ -15,14 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs
import android.content.SharedPreferences
import androidx.core.content.edit
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class StringSetPrefDelegate(
internal class StringSetPrefDelegate(
private val prefs: SharedPreferences,
private val key: String,
private val defaultValue: Set<String>,

View file

@ -15,10 +15,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.analytics
import android.content.SharedPreferences
import org.meshtastic.core.prefs.NullableStringPrefDelegate
import org.meshtastic.core.prefs.PrefDelegate
import org.meshtastic.core.prefs.di.AnalyticsSharedPreferences
import org.meshtastic.core.prefs.di.AppSharedPreferences
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
interface AnalyticsPrefs {
var analyticsAllowed: Boolean
@ -26,7 +32,13 @@ interface AnalyticsPrefs {
}
// Having an additional app prefs store is maintaining the existing behavior.
class AnalyticsPrefsImpl(analyticsPrefs: SharedPreferences, appPrefs: SharedPreferences) : AnalyticsPrefs {
@Singleton
class AnalyticsPrefsImpl
@Inject
constructor(
@AnalyticsSharedPreferences analyticsPrefs: SharedPreferences,
@AppSharedPreferences appPrefs: SharedPreferences,
) : AnalyticsPrefs {
override var analyticsAllowed: Boolean by PrefDelegate(analyticsPrefs, "allowed", true)
private var _installId: String? by NullableStringPrefDelegate(appPrefs, "appPrefs_install_id", null)

View file

@ -0,0 +1,163 @@
/*
* 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.prefs.di
import android.content.Context
import android.content.SharedPreferences
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import org.meshtastic.core.prefs.analytics.AnalyticsPrefsImpl
import org.meshtastic.core.prefs.emoji.CustomEmojiPrefs
import org.meshtastic.core.prefs.emoji.CustomEmojiPrefsImpl
import org.meshtastic.core.prefs.map.MapConsentPrefs
import org.meshtastic.core.prefs.map.MapConsentPrefsImpl
import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.prefs.map.MapPrefsImpl
import org.meshtastic.core.prefs.map.MapTileProviderPrefs
import org.meshtastic.core.prefs.map.MapTileProviderPrefsImpl
import org.meshtastic.core.prefs.mesh.MeshPrefs
import org.meshtastic.core.prefs.mesh.MeshPrefsImpl
import org.meshtastic.core.prefs.radio.RadioPrefs
import org.meshtastic.core.prefs.radio.RadioPrefsImpl
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.prefs.ui.UiPrefsImpl
import javax.inject.Qualifier
import javax.inject.Singleton
// These pref store qualifiers are internal to prevent prefs stores from being injected directly.
// Consuming code should always inject one of the prefs repositories.
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class AnalyticsSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class AppSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class CustomEmojiSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class MapSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class MapConsentSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class MapTileProviderSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class MeshSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class RadioSharedPreferences
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class UiSharedPreferences
@Suppress("TooManyFunctions")
@InstallIn(SingletonComponent::class)
@Module
interface PrefsModule {
@Binds fun bindAnalyticsPrefs(analyticsPrefsImpl: AnalyticsPrefsImpl): AnalyticsPrefs
@Binds fun bindCustomEmojiPrefs(customEmojiPrefsImpl: CustomEmojiPrefsImpl): CustomEmojiPrefs
@Binds fun bindMapConsentPrefs(mapConsentPrefsImpl: MapConsentPrefsImpl): MapConsentPrefs
@Binds fun bindMapPrefs(mapPrefsImpl: MapPrefsImpl): MapPrefs
@Binds fun bindMapTileProviderPrefs(mapTileProviderPrefsImpl: MapTileProviderPrefsImpl): MapTileProviderPrefs
@Binds fun bindMeshPrefs(meshPrefsImpl: MeshPrefsImpl): MeshPrefs
@Binds fun bindRadioPrefs(radioPrefsImpl: RadioPrefsImpl): RadioPrefs
@Binds fun bindUiPrefs(uiPrefsImpl: UiPrefsImpl): UiPrefs
companion object {
@Provides
@Singleton
@AnalyticsSharedPreferences
fun provideAnalyticsSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("analytics-prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@AppSharedPreferences
fun provideAppSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@CustomEmojiSharedPreferences
fun provideCustomEmojiSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("org.geeksville.emoji.prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@MapSharedPreferences
fun provideMapSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("map_prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@MapConsentSharedPreferences
fun provideMapConsentSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("map_consent_preferences", Context.MODE_PRIVATE)
@Provides
@Singleton
@MapTileProviderSharedPreferences
fun provideMapTileProviderSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("map_tile_provider_prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@MeshSharedPreferences
fun provideMeshSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("mesh-prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@RadioSharedPreferences
fun provideRadioSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE)
@Provides
@Singleton
@UiSharedPreferences
fun provideUiSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
}
}

View file

@ -15,14 +15,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.emoji
import android.content.SharedPreferences
import org.meshtastic.core.prefs.NullableStringPrefDelegate
import org.meshtastic.core.prefs.di.CustomEmojiSharedPreferences
import javax.inject.Inject
import javax.inject.Singleton
interface CustomEmojiPrefs {
var customEmojiFrequency: String?
}
class CustomEmojiPrefsImpl(prefs: SharedPreferences) : CustomEmojiPrefs {
@Singleton
class CustomEmojiPrefsImpl @Inject constructor(@CustomEmojiSharedPreferences prefs: SharedPreferences) :
CustomEmojiPrefs {
override var customEmojiFrequency: String? by NullableStringPrefDelegate(prefs, "pref_key_custom_emoji_freq", null)
}

View file

@ -15,10 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.map
import android.content.SharedPreferences
import androidx.core.content.edit
import org.meshtastic.core.prefs.di.MapConsentSharedPreferences
import javax.inject.Inject
import javax.inject.Singleton
interface MapConsentPrefs {
fun shouldReportLocation(nodeNum: Int?): Boolean
@ -26,7 +29,9 @@ interface MapConsentPrefs {
fun setShouldReportLocation(nodeNum: Int?, value: Boolean)
}
class MapConsentPrefsImpl(private val prefs: SharedPreferences) : MapConsentPrefs {
@Singleton
class MapConsentPrefsImpl @Inject constructor(@MapConsentSharedPreferences private val prefs: SharedPreferences) :
MapConsentPrefs {
override fun shouldReportLocation(nodeNum: Int?) = prefs.getBoolean(nodeNum.toString(), false)
override fun setShouldReportLocation(nodeNum: Int?, value: Boolean) {

View file

@ -15,9 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.map
import android.content.SharedPreferences
import org.meshtastic.core.prefs.PrefDelegate
import org.meshtastic.core.prefs.di.MapSharedPreferences
import javax.inject.Inject
import javax.inject.Singleton
/** Interface for general map prefs. For Google-specific prefs, see GoogleMapsPrefs. */
interface MapPrefs {
@ -27,7 +31,8 @@ interface MapPrefs {
var showPrecisionCircleOnMap: Boolean
}
class MapPrefsImpl(prefs: SharedPreferences) : MapPrefs {
@Singleton
class MapPrefsImpl @Inject constructor(@MapSharedPreferences prefs: SharedPreferences) : MapPrefs {
override var mapStyle: Int by PrefDelegate(prefs, "map_style_id", 0)
override var showOnlyFavorites: Boolean by PrefDelegate(prefs, "show_only_favorites", false)
override var showWaypointsOnMap: Boolean by PrefDelegate(prefs, "show_waypoints", true)

View file

@ -15,14 +15,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.map
import android.content.SharedPreferences
import org.meshtastic.core.prefs.NullableStringPrefDelegate
import org.meshtastic.core.prefs.di.MapTileProviderSharedPreferences
import javax.inject.Inject
import javax.inject.Singleton
interface MapTileProviderPrefs {
var customTileProviders: String?
}
class MapTileProviderPrefsImpl(prefs: SharedPreferences) : MapTileProviderPrefs {
@Singleton
class MapTileProviderPrefsImpl @Inject constructor(@MapTileProviderSharedPreferences prefs: SharedPreferences) :
MapTileProviderPrefs {
override var customTileProviders: String? by NullableStringPrefDelegate(prefs, "custom_tile_providers", null)
}

View file

@ -15,10 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.mesh
import android.content.SharedPreferences
import androidx.core.content.edit
import org.meshtastic.core.prefs.NullableStringPrefDelegate
import org.meshtastic.core.prefs.di.MeshSharedPreferences
import javax.inject.Inject
import javax.inject.Singleton
interface MeshPrefs {
var deviceAddress: String?
@ -28,7 +32,8 @@ interface MeshPrefs {
fun setShouldProvideNodeLocation(nodeNum: Int?, value: Boolean)
}
class MeshPrefsImpl(private val prefs: SharedPreferences) : MeshPrefs {
@Singleton
class MeshPrefsImpl @Inject constructor(@MeshSharedPreferences private val prefs: SharedPreferences) : MeshPrefs {
override var deviceAddress: String? by NullableStringPrefDelegate(prefs, "device_address", null)
override fun shouldProvideNodeLocation(nodeNum: Int?): Boolean =

View file

@ -15,14 +15,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.radio
import android.content.SharedPreferences
import org.meshtastic.core.prefs.NullableStringPrefDelegate
import org.meshtastic.core.prefs.di.RadioSharedPreferences
import javax.inject.Inject
import javax.inject.Singleton
interface RadioPrefs {
var devAddr: String?
}
class RadioPrefsImpl(prefs: SharedPreferences) : RadioPrefs {
@Singleton
class RadioPrefsImpl @Inject constructor(@RadioSharedPreferences prefs: SharedPreferences) : RadioPrefs {
override var devAddr: String? by NullableStringPrefDelegate(prefs, "devAddr2", null)
}

View file

@ -15,17 +15,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android.prefs
package org.meshtastic.core.prefs.ui
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import com.geeksville.mesh.model.NodeSortOption
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
import javax.inject.Inject
import javax.inject.Singleton
interface UiPrefs {
var theme: Int
@ -49,7 +52,8 @@ interface UiPrefs {
const val KEY_THEME = "theme"
const val KEY_APP_INTRO_COMPLETED = "app_intro_completed"
class UiPrefsImpl(private val prefs: SharedPreferences) : UiPrefs {
@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)
@ -83,7 +87,7 @@ class UiPrefsImpl(private val prefs: SharedPreferences) : UiPrefs {
}
override var hasShownNotPairedWarning: Boolean by PrefDelegate(prefs, "has_shown_not_paired_warning", false)
override var nodeSortOption: Int by PrefDelegate(prefs, "node-sort-option", NodeSortOption.VIA_FAVORITE.ordinal)
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)