mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: introduce Desktop target and expand Kotlin Multiplatform (KMP) architecture (#4761)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
f4364cff9a
commit
ac6bb5479b
386 changed files with 17089 additions and 4590 deletions
|
|
@ -42,10 +42,11 @@ import java.io.File
|
|||
import org.meshtastic.core.common.database.DatabaseManager as SharedDatabaseManager
|
||||
|
||||
/** Manages per-device Room database instances for node data, with LRU eviction. */
|
||||
@Single
|
||||
@Single(binds = [DatabaseProvider::class, SharedDatabaseManager::class])
|
||||
@Suppress("TooManyFunctions")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
open class DatabaseManager(private val app: Application, private val dispatchers: CoroutineDispatchers) :
|
||||
DatabaseProvider,
|
||||
SharedDatabaseManager {
|
||||
val prefs: SharedPreferences = app.getSharedPreferences("db-manager-prefs", Context.MODE_PRIVATE)
|
||||
private val managerScope = CoroutineScope(SupervisorJob() + dispatchers.default)
|
||||
|
|
@ -69,7 +70,7 @@ open class DatabaseManager(private val app: Application, private val dispatchers
|
|||
}
|
||||
|
||||
private val _currentDb = MutableStateFlow<MeshtasticDatabase?>(null)
|
||||
val currentDb: StateFlow<MeshtasticDatabase> =
|
||||
override val currentDb: StateFlow<MeshtasticDatabase> =
|
||||
_currentDb.filterNotNull().stateIn(managerScope, SharingStarted.Eagerly, buildRoomDb(app, defaultDbName()))
|
||||
|
||||
private val _currentAddress = MutableStateFlow<String?>(null)
|
||||
|
|
@ -119,7 +120,7 @@ open class DatabaseManager(private val app: Application, private val dispatchers
|
|||
private val limitedIo = dispatchers.io.limitedParallelism(4)
|
||||
|
||||
/** Execute [block] with the current DB instance. */
|
||||
suspend fun <T> withDb(block: suspend (MeshtasticDatabase) -> T): T? = withContext(limitedIo) {
|
||||
override suspend fun <T> withDb(block: suspend (MeshtasticDatabase) -> T): T? = withContext(limitedIo) {
|
||||
val db = _currentDb.value ?: return@withContext null
|
||||
val active = buildDbName(_currentAddress.value)
|
||||
markLastUsed(active)
|
||||
|
|
@ -127,7 +128,7 @@ open class DatabaseManager(private val app: Application, private val dispatchers
|
|||
}
|
||||
|
||||
/** Returns true if a database exists for the given device address. */
|
||||
fun hasDatabaseFor(address: String?): Boolean {
|
||||
override fun hasDatabaseFor(address: String?): Boolean {
|
||||
if (address.isNullOrBlank() || address == "n") return false
|
||||
val dbName = buildDbName(address)
|
||||
return getDbFile(app, dbName) != null
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.database
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Provides multiplatform access to the current [MeshtasticDatabase] and a safe transactional helper. Platform
|
||||
* implementations manage the concrete lifecycle (Room on Android, etc.).
|
||||
*/
|
||||
interface DatabaseProvider {
|
||||
/** Reactive stream of the currently active database instance. */
|
||||
val currentDb: StateFlow<MeshtasticDatabase>
|
||||
|
||||
/** Execute [block] against the current database, returning `null` if no database is available. */
|
||||
suspend fun <T> withDb(block: suspend (MeshtasticDatabase) -> T): T?
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ interface NodeInfoDao {
|
|||
val incomingKey = incomingNode.publicKey
|
||||
|
||||
val incomingHasKey = (incomingKey?.size ?: 0) == KEY_SIZE
|
||||
val existingHasKey = (existingKey?.size ?: 0) == KEY_SIZE && existingKey != NodeEntity.ERROR_BYTE_STRING
|
||||
val existingHasKey = existingKey.size == KEY_SIZE && existingKey != NodeEntity.ERROR_BYTE_STRING
|
||||
|
||||
return when {
|
||||
incomingHasKey -> {
|
||||
|
|
@ -143,7 +143,7 @@ interface NodeInfoDao {
|
|||
|
||||
val isPlaceholder = incomingNode.user.hw_model == HardwareModel.UNSET
|
||||
val hasExistingUser = existingNode.user.hw_model != HardwareModel.UNSET
|
||||
val isDefaultName = incomingNode.user.long_name?.matches(Regex("^Meshtastic [0-9a-fA-F]{4}$")) == true
|
||||
val isDefaultName = incomingNode.user.long_name.matches(Regex("^Meshtastic [0-9a-fA-F]{4}$"))
|
||||
|
||||
if (hasExistingUser && isPlaceholder && isDefaultName) {
|
||||
return incomingNode.copy(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.meshtastic.proto.MeshPacket
|
|||
import org.meshtastic.proto.MyNodeInfo
|
||||
import org.meshtastic.proto.NodeInfo
|
||||
import org.meshtastic.proto.Position
|
||||
import org.meshtastic.core.model.MeshLog as ExternalMeshLog
|
||||
|
||||
/**
|
||||
* Represents a log entry in the database.
|
||||
|
|
@ -83,3 +84,23 @@ data class MeshLog(
|
|||
const val NODE_NUM_LOCAL = 0
|
||||
}
|
||||
}
|
||||
|
||||
fun MeshLog.asExternalModel() = ExternalMeshLog(
|
||||
uuid = uuid,
|
||||
message_type = message_type,
|
||||
received_date = received_date,
|
||||
raw_message = raw_message,
|
||||
fromNum = fromNum,
|
||||
portNum = portNum,
|
||||
fromRadio = fromRadio,
|
||||
)
|
||||
|
||||
fun ExternalMeshLog.asEntity() = MeshLog(
|
||||
uuid = uuid,
|
||||
message_type = message_type,
|
||||
received_date = received_date,
|
||||
raw_message = raw_message,
|
||||
fromNum = fromNum,
|
||||
portNum = portNum,
|
||||
fromRadio = fromRadio,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ data class NodeEntity(
|
|||
get() = user.hw_model == HardwareModel.UNSET
|
||||
|
||||
val hasPKC
|
||||
get() = (publicKey ?: user.public_key)?.size?.let { it > 0 } == true
|
||||
get() = (publicKey ?: user.public_key).size > 0
|
||||
|
||||
fun setPosition(p: WirePosition, defaultTime: Int = currentTime()) {
|
||||
position = p.copy(time = if (p.time != 0) p.time else defaultTime)
|
||||
|
|
@ -216,8 +216,8 @@ data class NodeEntity(
|
|||
user =
|
||||
MeshUser(
|
||||
id = user.id,
|
||||
longName = user.long_name ?: "",
|
||||
shortName = user.short_name ?: "",
|
||||
longName = user.long_name,
|
||||
shortName = user.short_name,
|
||||
hwModel = user.hw_model,
|
||||
role = user.role.value,
|
||||
)
|
||||
|
|
@ -228,10 +228,10 @@ data class NodeEntity(
|
|||
longitude = longitude,
|
||||
altitude = position.altitude ?: 0,
|
||||
time = position.time,
|
||||
satellitesInView = position.sats_in_view ?: 0,
|
||||
satellitesInView = position.sats_in_view,
|
||||
groundSpeed = position.ground_speed ?: 0,
|
||||
groundTrack = position.ground_track ?: 0,
|
||||
precisionBits = position.precision_bits ?: 0,
|
||||
precisionBits = position.precision_bits,
|
||||
)
|
||||
.takeIf { it.isValid() },
|
||||
snr = snr,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue