mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: modern APIs — Koin 4.2, CMP 1.11, Ktor resilience, Room @Upsert, injected dispatchers (#5119)
This commit is contained in:
parent
99378c9291
commit
9acdf5309f
32 changed files with 453 additions and 278 deletions
|
|
@ -17,18 +17,15 @@
|
|||
package org.meshtastic.core.database.dao
|
||||
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Insert
|
||||
import androidx.room3.OnConflictStrategy
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Upsert
|
||||
import org.meshtastic.core.database.entity.DeviceHardwareEntity
|
||||
|
||||
@Dao
|
||||
interface DeviceHardwareDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(deviceHardware: DeviceHardwareEntity)
|
||||
@Upsert suspend fun insert(deviceHardware: DeviceHardwareEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAll(deviceHardware: List<DeviceHardwareEntity>)
|
||||
@Upsert suspend fun insertAll(deviceHardware: List<DeviceHardwareEntity>)
|
||||
|
||||
@Query("SELECT * FROM device_hardware WHERE hwModel = :hwModel")
|
||||
suspend fun getByHwModel(hwModel: Int): List<DeviceHardwareEntity>
|
||||
|
|
|
|||
|
|
@ -17,16 +17,14 @@
|
|||
package org.meshtastic.core.database.dao
|
||||
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Insert
|
||||
import androidx.room3.OnConflictStrategy
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Upsert
|
||||
import org.meshtastic.core.database.entity.FirmwareReleaseEntity
|
||||
import org.meshtastic.core.database.entity.FirmwareReleaseType
|
||||
|
||||
@Dao
|
||||
interface FirmwareReleaseDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(firmwareReleaseEntity: FirmwareReleaseEntity)
|
||||
@Upsert suspend fun insert(firmwareReleaseEntity: FirmwareReleaseEntity)
|
||||
|
||||
@Query("DELETE FROM firmware_release")
|
||||
suspend fun deleteAll()
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@
|
|||
package org.meshtastic.core.database.dao
|
||||
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Insert
|
||||
import androidx.room3.MapColumn
|
||||
import androidx.room3.OnConflictStrategy
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Transaction
|
||||
import androidx.room3.Upsert
|
||||
|
|
@ -168,8 +166,7 @@ interface NodeInfoDao {
|
|||
@Query("SELECT * FROM my_node")
|
||||
fun getMyNodeInfo(): Flow<MyNodeEntity?>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun setMyNodeInfo(myInfo: MyNodeEntity)
|
||||
@Upsert suspend fun setMyNodeInfo(myInfo: MyNodeEntity)
|
||||
|
||||
@Query("DELETE FROM my_node")
|
||||
suspend fun clearMyNodeInfo()
|
||||
|
|
@ -295,8 +292,7 @@ interface NodeInfoDao {
|
|||
doUpsert(verifiedNode)
|
||||
}
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun putAll(nodes: List<NodeEntity>)
|
||||
@Upsert suspend fun putAll(nodes: List<NodeEntity>)
|
||||
|
||||
@Query("UPDATE nodes SET notes = :notes WHERE num = :num")
|
||||
suspend fun setNodeNotes(num: Int, notes: String)
|
||||
|
|
|
|||
|
|
@ -17,9 +17,8 @@
|
|||
package org.meshtastic.core.database.dao
|
||||
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Insert
|
||||
import androidx.room3.OnConflictStrategy
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.meshtastic.core.database.entity.TracerouteNodePositionEntity
|
||||
|
||||
|
|
@ -32,6 +31,5 @@ interface TracerouteNodePositionDao {
|
|||
@Query("DELETE FROM traceroute_node_position WHERE log_uuid = :logUuid")
|
||||
suspend fun deleteByLogUuid(logUuid: String)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAll(entities: List<TracerouteNodePositionEntity>)
|
||||
@Upsert suspend fun insertAll(entities: List<TracerouteNodePositionEntity>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class PreferencesDataStoreModule {
|
|||
@Named("CorePreferencesDataStore")
|
||||
fun providePreferencesDataStore(
|
||||
context: Context,
|
||||
@Named("DataStoreScope") scope: CoroutineScope,
|
||||
@Named(DATASTORE_SCOPE) scope: CoroutineScope,
|
||||
): DataStore<Preferences> = PreferenceDataStoreFactory.create(
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }),
|
||||
migrations =
|
||||
|
|
@ -66,7 +66,7 @@ class LocalConfigDataStoreModule {
|
|||
@Named("CoreLocalConfigDataStore")
|
||||
fun provideLocalConfigDataStore(
|
||||
context: Context,
|
||||
@Named("DataStoreScope") scope: CoroutineScope,
|
||||
@Named(DATASTORE_SCOPE) scope: CoroutineScope,
|
||||
): DataStore<LocalConfig> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
|
|
@ -85,7 +85,7 @@ class ModuleConfigDataStoreModule {
|
|||
@Named("CoreModuleConfigDataStore")
|
||||
fun provideModuleConfigDataStore(
|
||||
context: Context,
|
||||
@Named("DataStoreScope") scope: CoroutineScope,
|
||||
@Named(DATASTORE_SCOPE) scope: CoroutineScope,
|
||||
): DataStore<LocalModuleConfig> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
|
|
@ -104,7 +104,7 @@ class ChannelSetDataStoreModule {
|
|||
@Named("CoreChannelSetDataStore")
|
||||
fun provideChannelSetDataStore(
|
||||
context: Context,
|
||||
@Named("DataStoreScope") scope: CoroutineScope,
|
||||
@Named(DATASTORE_SCOPE) scope: CoroutineScope,
|
||||
): DataStore<ChannelSet> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
|
|
@ -123,7 +123,7 @@ class LocalStatsDataStoreModule {
|
|||
@Named("CoreLocalStatsDataStore")
|
||||
fun provideLocalStatsDataStore(
|
||||
context: Context,
|
||||
@Named("DataStoreScope") scope: CoroutineScope,
|
||||
@Named(DATASTORE_SCOPE) scope: CoroutineScope,
|
||||
): DataStore<LocalStats> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
|
|
|
|||
|
|
@ -24,10 +24,17 @@ import org.koin.core.annotation.Named
|
|||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.ioDispatcher
|
||||
|
||||
/**
|
||||
* Koin qualifier for the application-scoped [CoroutineScope] shared by all [DataStore] instances.
|
||||
*
|
||||
* Used with `@Named(DATASTORE_SCOPE)` in Koin annotations and `named(DATASTORE_SCOPE)` in manual DSL modules.
|
||||
*/
|
||||
const val DATASTORE_SCOPE = "DataStoreScope"
|
||||
|
||||
@Module
|
||||
@ComponentScan("org.meshtastic.core.datastore")
|
||||
class CoreDatastoreModule {
|
||||
@Single
|
||||
@Named("DataStoreScope")
|
||||
@Named(DATASTORE_SCOPE)
|
||||
fun provideDataStoreScope(): CoroutineScope = CoroutineScope(ioDispatcher + SupervisorJob())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.network
|
||||
|
||||
/**
|
||||
* Shared HTTP client configuration constants used by both Android and Desktop Ktor `HttpClient` setups.
|
||||
*
|
||||
* These values are consumed by the platform-specific Koin modules (`NetworkModule` on Android, `DesktopKoinModule` on
|
||||
* Desktop) when installing [io.ktor.client.plugins.HttpTimeout] and [io.ktor.client.plugins.HttpRequestRetry].
|
||||
*/
|
||||
object HttpClientDefaults {
|
||||
/** Timeout in milliseconds for connect, request, and socket operations. */
|
||||
const val TIMEOUT_MS = 30_000L
|
||||
|
||||
/** Maximum number of automatic retries on server errors (5xx). */
|
||||
const val MAX_RETRIES = 3
|
||||
}
|
||||
|
|
@ -20,12 +20,12 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
|
||||
|
|
@ -38,7 +38,9 @@ class MarkAsReadReceiver :
|
|||
|
||||
private val serviceNotifications: MeshServiceNotifications by inject()
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val dispatchers: CoroutineDispatchers by inject()
|
||||
|
||||
private val scope by lazy { CoroutineScope(dispatchers.io + SupervisorJob()) }
|
||||
|
||||
companion object {
|
||||
const val MARK_AS_READ_ACTION = "com.geeksville.mesh.MARK_AS_READ"
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ import android.os.IBinder
|
|||
import androidx.core.app.ServiceCompat
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.meshtastic.core.common.hasLocationPermission
|
||||
import org.meshtastic.core.common.util.toRemoteExceptions
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.model.MeshUser
|
||||
|
|
@ -84,8 +84,10 @@ class MeshService : Service() {
|
|||
|
||||
private val router: MeshRouter by inject()
|
||||
|
||||
private val dispatchers: CoroutineDispatchers by inject()
|
||||
|
||||
private val serviceJob = Job()
|
||||
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||
private val serviceScope by lazy { CoroutineScope(dispatchers.io + serviceJob) }
|
||||
|
||||
private var isServiceInitialized = false
|
||||
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
|
||||
|
|
@ -41,7 +41,9 @@ class ReactionReceiver :
|
|||
|
||||
private val serviceRepository: ServiceRepository by inject()
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private val dispatchers: CoroutineDispatchers by inject()
|
||||
|
||||
private val scope by lazy { CoroutineScope(SupervisorJob() + dispatchers.io) }
|
||||
|
||||
@Suppress("TooGenericExceptionCaught", "ReturnCount")
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import androidx.core.app.RemoteInput
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
|
|
@ -44,7 +44,9 @@ class ReplyReceiver :
|
|||
|
||||
private val meshServiceNotifications: MeshServiceNotifications by inject()
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val dispatchers: CoroutineDispatchers by inject()
|
||||
|
||||
private val scope by lazy { CoroutineScope(dispatchers.io + SupervisorJob()) }
|
||||
|
||||
companion object {
|
||||
const val REPLY_ACTION = "org.meshtastic.app.REPLY_ACTION"
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ kotlin {
|
|||
implementation(libs.jetbrains.compose.material3.adaptive.layout)
|
||||
implementation(libs.jetbrains.compose.material3.adaptive.navigation)
|
||||
implementation(libs.jetbrains.compose.material3.adaptive.navigation.suite)
|
||||
implementation(libs.jetbrains.navigationevent.compose)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
implementation(libs.jetbrains.compose.material3.adaptive.navigation3)
|
||||
implementation(libs.jetbrains.lifecycle.viewmodel.navigation3)
|
||||
|
|
|
|||
|
|
@ -27,17 +27,18 @@ import androidx.compose.runtime.mutableLongStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
|
||||
@Composable
|
||||
actual fun rememberTimeTickWithLifecycle(): Long {
|
||||
val context = LocalContext.current
|
||||
var value by remember { mutableLongStateOf(System.currentTimeMillis()) }
|
||||
var value by remember { mutableLongStateOf(nowMillis) }
|
||||
|
||||
DisposableEffect(context) {
|
||||
val receiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
value = System.currentTimeMillis()
|
||||
value = nowMillis
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.meshtastic.core.ui.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
|
||||
/** JVM implementation — returns System.currentTimeMillis() (no lifecycle-based updates on Desktop). */
|
||||
@Composable actual fun rememberTimeTickWithLifecycle(): Long = System.currentTimeMillis()
|
||||
/** JVM implementation — returns the current epoch millis (no lifecycle-based updates on Desktop). */
|
||||
@Composable actual fun rememberTimeTickWithLifecycle(): Long = nowMillis
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue