refactor: migrate preferences to DataStore and decouple core:domain for KMP (#4731)

This commit is contained in:
James Rich 2026-03-05 20:37:35 -06:00 committed by GitHub
parent 87fdaa26ff
commit b9b68d2779
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
113 changed files with 1790 additions and 1320 deletions

View file

@ -0,0 +1,173 @@
/*
* 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/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.flow.StateFlow
/** Reactive interface for analytics-related preferences. */
interface AnalyticsPrefs {
val analyticsAllowed: StateFlow<Boolean>
fun setAnalyticsAllowed(allowed: Boolean)
val installId: StateFlow<String>
}
/** Reactive interface for homoglyph encoding preferences. */
interface HomoglyphPrefs {
val homoglyphEncodingEnabled: StateFlow<Boolean>
fun setHomoglyphEncodingEnabled(enabled: Boolean)
}
/** Reactive interface for message filtering preferences. */
interface FilterPrefs {
val filterEnabled: StateFlow<Boolean>
fun setFilterEnabled(enabled: Boolean)
val filterWords: StateFlow<Set<String>>
fun setFilterWords(words: Set<String>)
}
/** Reactive interface for mesh log preferences. */
interface MeshLogPrefs {
val retentionDays: StateFlow<Int>
fun setRetentionDays(days: Int)
val loggingEnabled: StateFlow<Boolean>
fun setLoggingEnabled(enabled: Boolean)
companion object {
const val DEFAULT_RETENTION_DAYS = 30
const val MIN_RETENTION_DAYS = -1
const val MAX_RETENTION_DAYS = 365
}
}
/** Reactive interface for emoji preferences. */
interface CustomEmojiPrefs {
val customEmojiFrequency: StateFlow<String?>
fun setCustomEmojiFrequency(frequency: String?)
}
/** Reactive interface for general UI preferences. */
interface UiPrefs {
val hasShownNotPairedWarning: StateFlow<Boolean>
fun setHasShownNotPairedWarning(shown: Boolean)
val showQuickChat: StateFlow<Boolean>
fun setShowQuickChat(show: Boolean)
fun shouldProvideNodeLocation(nodeNum: Int): StateFlow<Boolean>
fun setShouldProvideNodeLocation(nodeNum: Int, provide: Boolean)
}
/** Reactive interface for general map preferences. */
interface MapPrefs {
val mapStyle: StateFlow<Int>
fun setMapStyle(style: Int)
val showOnlyFavorites: StateFlow<Boolean>
fun setShowOnlyFavorites(show: Boolean)
val showWaypointsOnMap: StateFlow<Boolean>
fun setShowWaypointsOnMap(show: Boolean)
val showPrecisionCircleOnMap: StateFlow<Boolean>
fun setShowPrecisionCircleOnMap(show: Boolean)
val lastHeardFilter: StateFlow<Long>
fun setLastHeardFilter(seconds: Long)
val lastHeardTrackFilter: StateFlow<Long>
fun setLastHeardTrackFilter(seconds: Long)
}
/** Reactive interface for map consent. */
interface MapConsentPrefs {
fun shouldReportLocation(nodeNum: Int?): StateFlow<Boolean>
fun setShouldReportLocation(nodeNum: Int?, report: Boolean)
}
/** Reactive interface for map tile provider settings. */
interface MapTileProviderPrefs {
val customTileProviders: StateFlow<String?>
fun setCustomTileProviders(providers: String?)
}
/** Reactive interface for radio settings. */
interface RadioPrefs {
val devAddr: StateFlow<String?>
fun setDevAddr(address: String?)
}
fun RadioPrefs.isBle() = devAddr.value?.startsWith("x") == true
fun RadioPrefs.isSerial() = devAddr.value?.startsWith("s") == true
fun RadioPrefs.isMock() = devAddr.value?.startsWith("m") == true
fun RadioPrefs.isTcp() = devAddr.value?.startsWith("t") == true
fun RadioPrefs.isNoop() = devAddr.value?.startsWith("n") == true
/** Reactive interface for mesh connection settings. */
interface MeshPrefs {
val deviceAddress: StateFlow<String?>
fun setDeviceAddress(address: String?)
fun shouldProvideNodeLocation(nodeNum: Int?): StateFlow<Boolean>
fun setShouldProvideNodeLocation(nodeNum: Int?, provide: Boolean)
fun getStoreForwardLastRequest(address: String?): StateFlow<Int>
fun setStoreForwardLastRequest(address: String?, timestamp: Int)
}
/** Consolidated interface for all application preferences. */
interface AppPreferences {
val analytics: AnalyticsPrefs
val homoglyph: HomoglyphPrefs
val filter: FilterPrefs
val meshLog: MeshLogPrefs
val emoji: CustomEmojiPrefs
val ui: UiPrefs
val map: MapPrefs
val mapConsent: MapConsentPrefs
val mapTileProvider: MapTileProviderPrefs
val radio: RadioPrefs
val mesh: MeshPrefs
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (c) 2025-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/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.flow.StateFlow
/** Interface for managing database instances and cache limits. */
interface DatabaseManager {
/** Reactive stream of the current database cache limit. */
val cacheLimit: StateFlow<Int>
/** Returns the current database cache limit from storage. */
fun getCurrentCacheLimit(): Int
/** Sets the database cache limit. */
fun setCacheLimit(limit: Int)
/** Switches the active database to the one associated with the given [address]. */
suspend fun switchActiveDatabase(address: String?)
}

View file

@ -1,21 +0,0 @@
/*
* Copyright (c) 2025-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/>.
*/
package org.meshtastic.core.repository
interface HomoglyphPrefs {
val homoglyphEncodingEnabled: Boolean
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2025-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/>.
*/
package org.meshtastic.core.repository
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.database.entity.MeshLog
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.MyNodeInfo
import org.meshtastic.proto.PortNum
import org.meshtastic.proto.Telemetry
/**
* Repository interface for managing and retrieving logs from the database.
*
* This component provides access to the application's message log, telemetry history, and debug records. It supports
* reactive queries for packets, telemetry data, and node-specific logs.
*
* This interface is shared across platforms via Kotlin Multiplatform (KMP).
*/
@Suppress("TooManyFunctions")
interface MeshLogRepository {
/** Retrieves all [MeshLog]s in the database, up to [maxItem]. */
fun getAllLogs(maxItem: Int = DEFAULT_MAX_LOGS): Flow<List<MeshLog>>
/** Retrieves all [MeshLog]s in the database in the order they were received. */
fun getAllLogsInReceiveOrder(maxItem: Int = DEFAULT_MAX_LOGS): Flow<List<MeshLog>>
/** Retrieves all [MeshLog]s in the database without any limit. */
fun getAllLogsUnbounded(): Flow<List<MeshLog>>
/** Retrieves all [MeshLog]s associated with a specific [nodeNum] and [portNum]. */
fun getLogsFrom(nodeNum: Int, portNum: Int): Flow<List<MeshLog>>
/** Retrieves all [MeshLog]s containing [MeshPacket]s for a specific [nodeNum]. */
fun getMeshPacketsFrom(nodeNum: Int, portNum: Int = -1): Flow<List<MeshPacket>>
/** Retrieves telemetry history for a specific node, automatically handling local node redirection. */
fun getTelemetryFrom(nodeNum: Int): Flow<List<Telemetry>>
/**
* Retrieves all outgoing request logs for a specific [targetNodeNum] and [portNum].
*
* A request log is defined as an outgoing packet where `want_response` is true.
*/
fun getRequestLogs(targetNodeNum: Int, portNum: PortNum): Flow<List<MeshLog>>
/** Returns the cached [MyNodeInfo] from the system logs. */
fun getMyNodeInfo(): Flow<MyNodeInfo?>
/** Persists a new log entry to the database. */
suspend fun insert(log: MeshLog)
/** Clears all logs from the database. */
suspend fun deleteAll()
/** Deletes a specific log entry by its [uuid]. */
suspend fun deleteLog(uuid: String)
/** Deletes all logs associated with a specific [nodeNum] and [portNum]. */
suspend fun deleteLogs(nodeNum: Int, portNum: Int)
/** Prunes the log database based on the configured [retentionDays]. */
suspend fun deleteLogsOlderThan(retentionDays: Int)
companion object {
const val DEFAULT_MAX_LOGS = 5000
}
}

View file

@ -89,7 +89,7 @@ class SendMessageUseCase(
// Apply homoglyph encoding
val finalMessageText =
if (homoglyphEncodingPrefs.homoglyphEncodingEnabled) {
if (homoglyphEncodingPrefs.homoglyphEncodingEnabled.value) {
HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(text)
} else {
text