mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: migrate from Hilt to Koin and expand KMP common modules (#4746)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
a5390a80e7
commit
875cf1cff2
440 changed files with 3738 additions and 3508 deletions
|
|
@ -17,32 +17,21 @@
|
|||
package org.meshtastic.app.filter
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.koin.test.KoinTest
|
||||
import org.koin.test.inject
|
||||
import org.meshtastic.core.repository.FilterPrefs
|
||||
import org.meshtastic.core.repository.MessageFilter
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MessageFilterIntegrationTest {
|
||||
class MessageFilterIntegrationTest : KoinTest {
|
||||
|
||||
@get:Rule var hiltRule = HiltAndroidRule(this)
|
||||
private val filterPrefs: FilterPrefs by inject()
|
||||
|
||||
@Inject lateinit var filterPrefs: FilterPrefs
|
||||
|
||||
@Inject lateinit var filterService: MessageFilter
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
hiltRule.inject()
|
||||
}
|
||||
private val filterService: MessageFilter by inject()
|
||||
|
||||
@Test
|
||||
fun filterPrefsIntegration() = runTest {
|
||||
|
|
|
|||
|
|
@ -18,16 +18,17 @@ package org.meshtastic.app.analytics
|
|||
|
||||
import co.touchlab.kermit.Logger
|
||||
import co.touchlab.kermit.Severity
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.BuildConfig
|
||||
import org.meshtastic.core.repository.DataPair
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* F-Droid specific implementation of [PlatformAnalytics]. This provides no-op implementations for analytics and other
|
||||
* platform services.
|
||||
*/
|
||||
class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics {
|
||||
@Single
|
||||
class FdroidPlatformAnalytics : PlatformAnalytics {
|
||||
init {
|
||||
// For F-Droid builds we don't initialize external analytics services.
|
||||
// In debug builds we attach a DebugTree for convenient local logging, but
|
||||
|
|
|
|||
|
|
@ -16,24 +16,19 @@
|
|||
*/
|
||||
package org.meshtastic.app.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.koin.core.annotation.Module
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.model.NetworkFirmwareReleases
|
||||
import org.meshtastic.core.network.service.ApiService
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
class FDroidNetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Single
|
||||
fun provideOkHttpClient(buildConfigProvider: BuildConfigProvider): OkHttpClient = OkHttpClient.Builder()
|
||||
.addInterceptor(
|
||||
interceptor =
|
||||
|
|
@ -45,8 +40,7 @@ class FDroidNetworkModule {
|
|||
)
|
||||
.build()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Single
|
||||
fun provideApiService(): ApiService = object : ApiService {
|
||||
override suspend fun getDeviceHardware(): List<NetworkDeviceHardware> =
|
||||
throw NotImplementedError("API calls to getDeviceHardware are not supported on Fdroid builds.")
|
||||
|
|
|
|||
|
|
@ -1,35 +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.app.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.app.analytics.FdroidPlatformAnalytics
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** Hilt module to provide the [FdroidPlatformAnalytics] for the fdroid flavor. */
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class FdroidPlatformAnalyticsModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindPlatformHelper(fdroidPlatformAnalytics: FdroidPlatformAnalytics): PlatformAnalytics
|
||||
}
|
||||
|
|
@ -14,18 +14,9 @@
|
|||
* 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.app.messaging.di
|
||||
package org.meshtastic.app.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.app.messaging.domain.worker.WorkManagerMessageQueue
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import org.koin.core.annotation.Module
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class MessagingModule {
|
||||
|
||||
@Binds abstract fun bindMessageQueue(impl: WorkManagerMessageQueue): MessageQueue
|
||||
}
|
||||
@Module(includes = [FDroidNetworkModule::class])
|
||||
class FlavorModule
|
||||
|
|
@ -18,9 +18,11 @@ package org.meshtastic.app.map
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.ui.util.MapViewProvider
|
||||
|
||||
@Single
|
||||
class FdroidMapViewProvider : MapViewProvider {
|
||||
@Composable
|
||||
override fun MapView(
|
||||
|
|
@ -33,7 +35,7 @@ class FdroidMapViewProvider : MapViewProvider {
|
|||
tracerouteNodePositions: Map<Int, Any>,
|
||||
onTracerouteMappableCountChanged: (Int, Int) -> Unit,
|
||||
) {
|
||||
val mapViewModel: MapViewModel = hiltViewModel()
|
||||
val mapViewModel: MapViewModel = koinViewModel()
|
||||
org.meshtastic.app.map.MapView(
|
||||
modifier = modifier,
|
||||
mapViewModel = mapViewModel,
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
|
|
@ -83,6 +82,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.R
|
||||
import org.meshtastic.app.map.cluster.RadiusMarkerClusterer
|
||||
import org.meshtastic.app.map.component.CacheLayout
|
||||
|
|
@ -235,7 +235,7 @@ private fun cacheManagerCallback(onTaskComplete: () -> Unit, onTaskFailed: (Int)
|
|||
@Composable
|
||||
fun MapView(
|
||||
modifier: Modifier = Modifier,
|
||||
mapViewModel: MapViewModel = hiltViewModel(),
|
||||
mapViewModel: MapViewModel = koinViewModel(),
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
focusedNodeNum: Int? = null,
|
||||
nodeTracks: List<Position>? = null,
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ package org.meshtastic.app.map
|
|||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.toRoute
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.RadioController
|
||||
|
|
@ -33,13 +33,10 @@ import org.meshtastic.core.repository.RadioConfigRepository
|
|||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.feature.map.BaseMapViewModel
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
class MapViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@KoinViewModel
|
||||
class MapViewModel(
|
||||
mapPrefs: MapPrefs,
|
||||
packetRepository: PacketRepository,
|
||||
override val nodeRepository: NodeRepository,
|
||||
|
|
|
|||
|
|
@ -86,22 +86,6 @@ open class NOAAWmsTileSource(
|
|||
if (time != null) this.time = time
|
||||
}
|
||||
|
||||
// fun createFrom(endpoint: WMSEndpoint, layer: WMSLayer): WMSTileSource? {
|
||||
// var srs: String? = "EPSG:900913"
|
||||
// if (layer.srs.isNotEmpty()) {
|
||||
// srs = layer.srs[0]
|
||||
// }
|
||||
// return if (layer.styles.isEmpty()) {
|
||||
// WMSTileSource(
|
||||
// layer.name, arrayOf(endpoint.baseurl), layer.name,
|
||||
// endpoint.wmsVersion, srs, null, layer.pixelSize
|
||||
// )
|
||||
// } else WMSTileSource(
|
||||
// layer.name, arrayOf(endpoint.baseurl), layer.name,
|
||||
// endpoint.wmsVersion, srs, layer.styles[0], layer.pixelSize
|
||||
// )
|
||||
// }
|
||||
|
||||
private fun tile2lon(x: Int, z: Int): Double = x / 2.0.pow(z.toDouble()) * 360.0 - 180
|
||||
|
||||
private fun tile2lat(y: Int, z: Int): Double {
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@
|
|||
* 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.app.repository.radio
|
||||
package org.meshtastic.app.node.component
|
||||
|
||||
import dagger.MapKey
|
||||
import org.meshtastic.core.model.InterfaceId
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import org.meshtastic.core.model.Node
|
||||
|
||||
/** Dagger `MapKey` implementation allowing for `InterfaceId` to be used as a map key. */
|
||||
@MapKey
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class InterfaceMapKey(val value: InterfaceId)
|
||||
@Composable
|
||||
fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
// No-op for F-Droid builds
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.app.node.metrics
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.ui.util.TracerouteMapOverlayInsets
|
||||
|
||||
fun getTracerouteMapOverlayInsets(): TracerouteMapOverlayInsets = TracerouteMapOverlayInsets(
|
||||
overlayAlignment = Alignment.BottomEnd,
|
||||
overlayPadding = PaddingValues(end = 16.dp, bottom = 16.dp),
|
||||
contentHorizontalAlignment = Alignment.End,
|
||||
)
|
||||
|
|
@ -46,16 +46,15 @@ import com.google.firebase.analytics.analytics
|
|||
import com.google.firebase.crashlytics.crashlytics
|
||||
import com.google.firebase.crashlytics.setCustomKeys
|
||||
import com.google.firebase.initialize
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.opentelemetry.api.GlobalOpenTelemetry
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.BuildConfig
|
||||
import org.meshtastic.core.repository.AnalyticsPrefs
|
||||
import org.meshtastic.core.repository.DataPair
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import javax.inject.Inject
|
||||
import co.touchlab.kermit.Logger as KermitLogger
|
||||
|
||||
/**
|
||||
|
|
@ -65,12 +64,9 @@ import co.touchlab.kermit.Logger as KermitLogger
|
|||
* This implementation delays initialization of SDKs until user consent is granted to reduce tracking "noise" and
|
||||
* respect privacy-focused environments.
|
||||
*/
|
||||
class GooglePlatformAnalytics
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val analyticsPrefs: AnalyticsPrefs,
|
||||
) : PlatformAnalytics {
|
||||
@Single
|
||||
class GooglePlatformAnalytics(private val context: Context, private val analyticsPrefs: AnalyticsPrefs) :
|
||||
PlatformAnalytics {
|
||||
|
||||
private val sampleRate = 100f.takeIf { BuildConfig.DEBUG } ?: 10f // For Datadog remote sample rate
|
||||
|
||||
|
|
|
|||
23
app/src/google/kotlin/org/meshtastic/app/di/FlavorModule.kt
Normal file
23
app/src/google/kotlin/org/meshtastic/app/di/FlavorModule.kt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.app.di
|
||||
|
||||
import org.koin.core.annotation.Module
|
||||
import org.meshtastic.app.map.prefs.di.GoogleMapsKoinModule
|
||||
|
||||
@Module(includes = [GoogleNetworkModule::class, GoogleMapsKoinModule::class])
|
||||
class FlavorModule
|
||||
|
|
@ -19,35 +19,24 @@ package org.meshtastic.app.di
|
|||
import android.content.Context
|
||||
import com.datadog.android.okhttp.DatadogEventListener
|
||||
import com.datadog.android.okhttp.DatadogInterceptor
|
||||
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 okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.koin.core.annotation.Module
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.network.service.ApiService
|
||||
import org.meshtastic.core.network.service.ApiServiceImpl
|
||||
import java.io.File
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface GoogleNetworkModule {
|
||||
class GoogleNetworkModule {
|
||||
|
||||
@Binds @Singleton
|
||||
fun bindApiService(apiServiceImpl: ApiServiceImpl): ApiService
|
||||
@Single fun bindApiService(apiServiceImpl: ApiServiceImpl): ApiService = apiServiceImpl
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttpClient(
|
||||
@ApplicationContext context: Context,
|
||||
buildConfigProvider: BuildConfigProvider,
|
||||
): OkHttpClient = OkHttpClient.Builder()
|
||||
@Single
|
||||
fun provideOkHttpClient(context: Context, buildConfigProvider: BuildConfigProvider): OkHttpClient =
|
||||
OkHttpClient.Builder()
|
||||
.cache(
|
||||
cache =
|
||||
Cache(
|
||||
|
|
@ -63,10 +52,7 @@ interface GoogleNetworkModule {
|
|||
}
|
||||
},
|
||||
)
|
||||
.addInterceptor(
|
||||
interceptor = DatadogInterceptor.Builder(tracedHosts = listOf("meshtastic.org")).build(),
|
||||
)
|
||||
.addInterceptor(interceptor = DatadogInterceptor.Builder(tracedHosts = listOf("meshtastic.org")).build())
|
||||
.eventListenerFactory(eventListenerFactory = DatadogEventListener.Factory())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.app.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.app.analytics.GooglePlatformAnalytics
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** Hilt module to provide the [GooglePlatformAnalytics] for the google flavor. */
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class GooglePlatformAnalyticsModule {
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindPlatformHelper(googlePlatformHelper: GooglePlatformAnalytics): PlatformAnalytics
|
||||
}
|
||||
|
|
@ -18,9 +18,11 @@ package org.meshtastic.app.map
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.ui.util.MapViewProvider
|
||||
|
||||
@Single
|
||||
class GoogleMapViewProvider : MapViewProvider {
|
||||
@Composable
|
||||
override fun MapView(
|
||||
|
|
@ -33,7 +35,7 @@ class GoogleMapViewProvider : MapViewProvider {
|
|||
tracerouteNodePositions: Map<Int, Any>,
|
||||
onTracerouteMappableCountChanged: (Int, Int) -> Unit,
|
||||
) {
|
||||
val mapViewModel: MapViewModel = hiltViewModel()
|
||||
val mapViewModel: MapViewModel = koinViewModel()
|
||||
org.meshtastic.app.map.MapView(
|
||||
modifier = modifier,
|
||||
mapViewModel = mapViewModel,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
|
|
@ -95,6 +94,7 @@ import com.google.maps.android.data.kml.KmlLayer
|
|||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.json.JSONObject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.map.component.ClusterItemsListDialog
|
||||
import org.meshtastic.app.map.component.CustomMapLayersSheet
|
||||
import org.meshtastic.app.map.component.CustomTileProviderManagerSheet
|
||||
|
|
@ -149,7 +149,7 @@ private const val TRACEROUTE_BOUNDS_PADDING_PX = 120
|
|||
@Composable
|
||||
fun MapView(
|
||||
modifier: Modifier = Modifier,
|
||||
mapViewModel: MapViewModel = hiltViewModel(),
|
||||
mapViewModel: MapViewModel = koinViewModel(),
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
focusedNodeNum: Int? = null,
|
||||
nodeTracks: List<Position>? = null,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import com.google.android.gms.maps.model.TileProvider
|
|||
import com.google.android.gms.maps.model.UrlTileProvider
|
||||
import com.google.maps.android.compose.CameraPositionState
|
||||
import com.google.maps.android.compose.MapType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -43,6 +42,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.app.map.model.CustomTileProviderConfig
|
||||
import org.meshtastic.app.map.prefs.map.GoogleMapsPrefs
|
||||
import org.meshtastic.app.map.repository.CustomTileProviderRepository
|
||||
|
|
@ -62,7 +62,6 @@ import java.io.IOException
|
|||
import java.io.InputStream
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
private const val TILE_SIZE = 256
|
||||
|
|
@ -77,10 +76,8 @@ data class MapCameraPosition(
|
|||
)
|
||||
|
||||
@Suppress("TooManyFunctions", "LongParameterList")
|
||||
@HiltViewModel
|
||||
class MapViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@KoinViewModel
|
||||
class MapViewModel(
|
||||
private val application: Application,
|
||||
mapPrefs: MapPrefs,
|
||||
private val googleMapsPrefs: GoogleMapsPrefs,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.app.map.prefs.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Module
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
|
||||
@Module
|
||||
@ComponentScan("org.meshtastic.app.map")
|
||||
class GoogleMapsKoinModule {
|
||||
|
||||
@Single
|
||||
@Named("GoogleMapsDataStore")
|
||||
fun provideGoogleMapsDataStore(context: Context): DataStore<Preferences> = PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "google_maps_prefs")),
|
||||
scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
|
||||
produceFile = { context.preferencesDataStoreFile("google_maps_ds") },
|
||||
)
|
||||
}
|
||||
|
|
@ -1,68 +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.app.map.prefs.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import org.meshtastic.app.map.prefs.map.GoogleMapsPrefs
|
||||
import org.meshtastic.app.map.prefs.map.GoogleMapsPrefsImpl
|
||||
import org.meshtastic.app.map.repository.CustomTileProviderRepository
|
||||
import org.meshtastic.app.map.repository.CustomTileProviderRepositoryImpl
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class GoogleMapsDataStore
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface GoogleMapsModule {
|
||||
|
||||
@Binds fun bindGoogleMapsPrefs(googleMapsPrefsImpl: GoogleMapsPrefsImpl): GoogleMapsPrefs
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCustomTileProviderRepository(impl: CustomTileProviderRepositoryImpl): CustomTileProviderRepository
|
||||
|
||||
companion object {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@GoogleMapsDataStore
|
||||
fun provideGoogleMapsDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "google_maps_prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("google_maps_ds") },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,10 +31,9 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.app.map.prefs.di.GoogleMapsDataStore
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** Interface for prefs specific to Google Maps. For general map prefs, see MapPrefs. */
|
||||
interface GoogleMapsPrefs {
|
||||
|
|
@ -75,11 +74,9 @@ interface GoogleMapsPrefs {
|
|||
fun setNetworkMapLayers(value: Set<String>)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class GoogleMapsPrefsImpl
|
||||
@Inject
|
||||
constructor(
|
||||
@GoogleMapsDataStore private val dataStore: DataStore<Preferences>,
|
||||
@Single
|
||||
class GoogleMapsPrefsImpl(
|
||||
@Named("GoogleMapsDataStore") private val dataStore: DataStore<Preferences>,
|
||||
dispatchers: CoroutineDispatchers,
|
||||
) : GoogleMapsPrefs {
|
||||
private val scope = CoroutineScope(SupervisorJob() + dispatchers.default)
|
||||
|
|
|
|||
|
|
@ -23,11 +23,10 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.map.model.CustomTileProviderConfig
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.repository.MapTileProviderPrefs
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
interface CustomTileProviderRepository {
|
||||
fun getCustomTileProviders(): Flow<List<CustomTileProviderConfig>>
|
||||
|
|
@ -41,10 +40,8 @@ interface CustomTileProviderRepository {
|
|||
suspend fun getCustomTileProviderById(configId: String): CustomTileProviderConfig?
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class CustomTileProviderRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
@Single
|
||||
class CustomTileProviderRepositoryImpl(
|
||||
private val json: Json,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val mapTileProviderPrefs: MapTileProviderPrefs,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.app.node.component
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.google.android.gms.maps.model.CameraPosition
|
||||
import com.google.android.gms.maps.model.LatLng
|
||||
import com.google.maps.android.compose.Circle
|
||||
import com.google.maps.android.compose.ComposeMapColorScheme
|
||||
import com.google.maps.android.compose.GoogleMap
|
||||
import com.google.maps.android.compose.MapUiSettings
|
||||
import com.google.maps.android.compose.MapsComposeExperimentalApi
|
||||
import com.google.maps.android.compose.MarkerComposable
|
||||
import com.google.maps.android.compose.rememberCameraPositionState
|
||||
import com.google.maps.android.compose.rememberUpdatedMarkerState
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.ui.component.NodeChip
|
||||
import org.meshtastic.core.ui.component.precisionBitsToMeters
|
||||
|
||||
private const val DEFAULT_ZOOM = 15f
|
||||
|
||||
@OptIn(MapsComposeExperimentalApi::class)
|
||||
@Composable
|
||||
fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val mapColorScheme =
|
||||
when (dark) {
|
||||
true -> ComposeMapColorScheme.DARK
|
||||
else -> ComposeMapColorScheme.LIGHT
|
||||
}
|
||||
key(node.num) {
|
||||
val location = LatLng(node.latitude, node.longitude)
|
||||
val cameraState = rememberCameraPositionState {
|
||||
position = CameraPosition.fromLatLngZoom(location, DEFAULT_ZOOM)
|
||||
}
|
||||
|
||||
GoogleMap(
|
||||
mapColorScheme = mapColorScheme,
|
||||
modifier = modifier,
|
||||
uiSettings =
|
||||
MapUiSettings(
|
||||
zoomControlsEnabled = true,
|
||||
mapToolbarEnabled = false,
|
||||
compassEnabled = false,
|
||||
myLocationButtonEnabled = false,
|
||||
rotationGesturesEnabled = false,
|
||||
scrollGesturesEnabled = false,
|
||||
tiltGesturesEnabled = false,
|
||||
zoomGesturesEnabled = false,
|
||||
),
|
||||
cameraPositionState = cameraState,
|
||||
) {
|
||||
val precisionMeters = precisionBitsToMeters(node.position.precision_bits ?: 0)
|
||||
val latLng = LatLng(node.latitude, node.longitude)
|
||||
if (precisionMeters > 0) {
|
||||
Circle(
|
||||
center = latLng,
|
||||
radius = precisionMeters,
|
||||
fillColor = Color(node.colors.second).copy(alpha = 0.2f),
|
||||
strokeColor = Color(node.colors.second),
|
||||
strokeWidth = 2f,
|
||||
)
|
||||
}
|
||||
MarkerComposable(state = rememberUpdatedMarkerState(position = latLng)) { NodeChip(node = node) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.app.node.metrics
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.ui.util.TracerouteMapOverlayInsets
|
||||
|
||||
fun getTracerouteMapOverlayInsets(): TracerouteMapOverlayInsets = TracerouteMapOverlayInsets(
|
||||
overlayAlignment = Alignment.BottomCenter,
|
||||
overlayPadding = PaddingValues(bottom = 16.dp),
|
||||
contentHorizontalAlignment = Alignment.CenterHorizontally,
|
||||
)
|
||||
|
|
@ -1,78 +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.app
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.app.repository.radio.AndroidRadioInterfaceService
|
||||
import org.meshtastic.app.service.AndroidAppWidgetUpdater
|
||||
import org.meshtastic.app.service.AndroidMeshLocationManager
|
||||
import org.meshtastic.app.service.AndroidMeshWorkerManager
|
||||
import org.meshtastic.app.service.MeshServiceNotificationsImpl
|
||||
import org.meshtastic.app.service.ServiceBroadcasts
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.di.ProcessLifecycle
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface ApplicationModule {
|
||||
|
||||
@Binds fun bindMeshServiceNotifications(impl: MeshServiceNotificationsImpl): MeshServiceNotifications
|
||||
|
||||
@Binds
|
||||
fun bindMeshLocationManager(impl: AndroidMeshLocationManager): org.meshtastic.core.repository.MeshLocationManager
|
||||
|
||||
@Binds fun bindMeshWorkerManager(impl: AndroidMeshWorkerManager): org.meshtastic.core.repository.MeshWorkerManager
|
||||
|
||||
@Binds fun bindAppWidgetUpdater(impl: AndroidAppWidgetUpdater): org.meshtastic.core.repository.AppWidgetUpdater
|
||||
|
||||
@Binds
|
||||
fun bindRadioInterfaceService(
|
||||
impl: AndroidRadioInterfaceService,
|
||||
): org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
@Binds fun bindServiceBroadcasts(impl: ServiceBroadcasts): org.meshtastic.core.repository.ServiceBroadcasts
|
||||
|
||||
companion object {
|
||||
@Provides @ProcessLifecycle
|
||||
fun provideProcessLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get()
|
||||
|
||||
@Provides
|
||||
@ProcessLifecycle
|
||||
fun provideProcessLifecycle(@ProcessLifecycle processLifecycleOwner: LifecycleOwner): Lifecycle =
|
||||
processLifecycleOwner.lifecycle
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideBuildConfigProvider(): BuildConfigProvider = object : BuildConfigProvider {
|
||||
override val isDebug: Boolean = BuildConfig.DEBUG
|
||||
override val applicationId: String = BuildConfig.APPLICATION_ID
|
||||
override val versionCode: Int = BuildConfig.VERSION_CODE
|
||||
override val versionName: String = BuildConfig.VERSION_NAME
|
||||
override val absoluteMinFwVersion: String = BuildConfig.ABS_MIN_FW_VERSION
|
||||
override val minFwVersion: String = BuildConfig.MIN_FW_VERSION
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,6 @@ import androidx.activity.SystemBarStyle
|
|||
import androidx.activity.compose.ReportDrawnWhen
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
|
|
@ -40,18 +39,22 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.kotlin.ble.core.android.AndroidEnvironment
|
||||
import no.nordicsemi.kotlin.ble.environment.android.compose.LocalEnvironmentOwner
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.meshtastic.app.intro.AnalyticsIntro
|
||||
import org.meshtastic.app.intro.AndroidIntroViewModel
|
||||
import org.meshtastic.app.map.getMapViewProvider
|
||||
import org.meshtastic.app.model.UIViewModel
|
||||
import org.meshtastic.app.node.component.InlineMap
|
||||
import org.meshtastic.app.node.metrics.getTracerouteMapOverlayInsets
|
||||
import org.meshtastic.app.ui.MainScreen
|
||||
import org.meshtastic.core.barcode.rememberBarcodeScanner
|
||||
import org.meshtastic.core.model.util.dispatchMeshtasticUri
|
||||
|
|
@ -63,27 +66,30 @@ import org.meshtastic.core.ui.theme.AppTheme
|
|||
import org.meshtastic.core.ui.theme.MODE_DYNAMIC
|
||||
import org.meshtastic.core.ui.util.LocalAnalyticsIntroProvider
|
||||
import org.meshtastic.core.ui.util.LocalBarcodeScannerProvider
|
||||
import org.meshtastic.core.ui.util.LocalInlineMapProvider
|
||||
import org.meshtastic.core.ui.util.LocalMapViewProvider
|
||||
import org.meshtastic.core.ui.util.LocalNfcScannerProvider
|
||||
import org.meshtastic.core.ui.util.LocalTracerouteMapOverlayInsetsProvider
|
||||
import org.meshtastic.core.ui.util.showToast
|
||||
import org.meshtastic.feature.intro.AppIntroductionScreen
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val model: UIViewModel by viewModels()
|
||||
private val model: UIViewModel by viewModel()
|
||||
|
||||
/**
|
||||
* Activity-lifecycle-aware client that binds to the mesh service. Note: This is used implicitly as it registers
|
||||
* itself as a LifecycleObserver in its init block.
|
||||
*/
|
||||
@Inject internal lateinit var meshServiceClient: MeshServiceClient
|
||||
internal val meshServiceClient: MeshServiceClient by inject { parametersOf(this) }
|
||||
|
||||
@Inject internal lateinit var androidEnvironment: AndroidEnvironment
|
||||
internal val androidEnvironment: AndroidEnvironment by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
|
||||
// Eagerly evaluate lazy Koin dependency so it registers its LifecycleObserver
|
||||
meshServiceClient.hashCode()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
|
@ -124,6 +130,8 @@ class MainActivity : ComponentActivity() {
|
|||
LocalNfcScannerProvider provides { onResult, onDisabled -> NfcScannerEffect(onResult, onDisabled) },
|
||||
LocalAnalyticsIntroProvider provides { AnalyticsIntro() },
|
||||
LocalMapViewProvider provides getMapViewProvider(),
|
||||
LocalInlineMapProvider provides { node, modifier -> InlineMap(node, modifier) },
|
||||
LocalTracerouteMapOverlayInsetsProvider provides getTracerouteMapOverlayInsets(),
|
||||
) {
|
||||
AppTheme(dynamicColor = dynamic, darkTheme = dark) {
|
||||
val appIntroCompleted by model.appIntroCompleted.collectAsStateWithLifecycle()
|
||||
|
|
@ -135,7 +143,7 @@ class MainActivity : ComponentActivity() {
|
|||
if (appIntroCompleted) {
|
||||
MainScreen(uIViewModel = model)
|
||||
} else {
|
||||
val introViewModel = hiltViewModel<AndroidIntroViewModel>()
|
||||
val introViewModel = koinViewModel<AndroidIntroViewModel>()
|
||||
AppIntroductionScreen(onDone = { model.onAppIntroCompleted() }, viewModel = introViewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,9 @@
|
|||
*/
|
||||
package org.meshtastic.app
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.test.runner.AndroidJUnitRunner
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Module
|
||||
|
||||
@Suppress("unused")
|
||||
class TestRunner : AndroidJUnitRunner() {
|
||||
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application =
|
||||
super.newApplication(cl, HiltTestApplication::class.java.name, context)
|
||||
}
|
||||
@Module
|
||||
@ComponentScan("org.meshtastic.app")
|
||||
class MainKoinModule
|
||||
|
|
@ -23,9 +23,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.meshtastic.app.service.MeshService
|
||||
import org.meshtastic.app.service.startService
|
||||
import org.meshtastic.core.common.util.SequentialJob
|
||||
|
|
@ -33,14 +32,11 @@ import org.meshtastic.core.service.AndroidServiceRepository
|
|||
import org.meshtastic.core.service.BindFailedException
|
||||
import org.meshtastic.core.service.IMeshService
|
||||
import org.meshtastic.core.service.ServiceClient
|
||||
import javax.inject.Inject
|
||||
|
||||
/** A Activity-lifecycle-aware [ServiceClient] that binds [MeshService] once the Activity is started. */
|
||||
@ActivityScoped
|
||||
class MeshServiceClient
|
||||
@Inject
|
||||
constructor(
|
||||
@ActivityContext private val context: Context,
|
||||
@Factory
|
||||
class MeshServiceClient(
|
||||
private val context: Context,
|
||||
private val serviceRepository: AndroidServiceRepository,
|
||||
private val serviceSetupJob: SequentialJob,
|
||||
) : ServiceClient<IMeshService>(IMeshService.Stub::asInterface),
|
||||
|
|
|
|||
|
|
@ -21,17 +21,11 @@ import android.appwidget.AppWidgetProviderInfo
|
|||
import android.os.Build
|
||||
import androidx.collection.intSetOf
|
||||
import androidx.glance.appwidget.GlanceAppWidgetManager
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
|
|
@ -40,13 +34,17 @@ import kotlinx.coroutines.flow.first
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import no.nordicsemi.kotlin.ble.core.android.AndroidEnvironment
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.workmanager.koin.workManagerFactory
|
||||
import org.koin.core.context.startKoin
|
||||
import org.meshtastic.app.di.AppKoinModule
|
||||
import org.meshtastic.app.di.module
|
||||
import org.meshtastic.app.widget.LocalStatsWidgetReceiver
|
||||
import org.meshtastic.app.worker.MeshLogCleanupWorker
|
||||
import org.meshtastic.core.common.ContextServices
|
||||
import org.meshtastic.core.database.DatabaseManager
|
||||
import org.meshtastic.core.repository.MeshLogPrefs
|
||||
import org.meshtastic.core.repository.MeshPrefs
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.toJavaDuration
|
||||
|
|
@ -54,15 +52,11 @@ import kotlin.time.toJavaDuration
|
|||
/**
|
||||
* The main application class for Meshtastic.
|
||||
*
|
||||
* This class is annotated with [HiltAndroidApp] to enable Hilt for dependency injection. It initializes core
|
||||
* application components, including analytics and platform-specific helpers, and manages analytics consent based on
|
||||
* user preferences.
|
||||
* This class initializes core application components using Koin for dependency injection.
|
||||
*/
|
||||
@HiltAndroidApp
|
||||
open class MeshUtilApplication :
|
||||
Application(),
|
||||
Configuration.Provider {
|
||||
@Inject lateinit var workerFactory: HiltWorkerFactory
|
||||
|
||||
private val applicationScope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
|
|
@ -70,6 +64,12 @@ open class MeshUtilApplication :
|
|||
super.onCreate()
|
||||
ContextServices.app = this
|
||||
|
||||
startKoin {
|
||||
androidContext(this@MeshUtilApplication)
|
||||
workManagerFactory()
|
||||
modules(AppKoinModule().module())
|
||||
}
|
||||
|
||||
// Schedule periodic MeshLog cleanup
|
||||
scheduleMeshLogCleanup()
|
||||
|
||||
|
|
@ -93,15 +93,11 @@ open class MeshUtilApplication :
|
|||
|
||||
pushPreview()
|
||||
|
||||
val entryPoint =
|
||||
EntryPointAccessors.fromApplication(
|
||||
this@MeshUtilApplication,
|
||||
org.meshtastic.app.widget.LocalStatsWidget.LocalStatsWidgetEntryPoint::class.java,
|
||||
)
|
||||
val widgetStateProvider: org.meshtastic.app.widget.LocalStatsWidgetStateProvider = get()
|
||||
try {
|
||||
// Wait for real data for up to 30 seconds before pushing an updated preview
|
||||
withTimeout(30.seconds) {
|
||||
entryPoint.widgetStateProvider().state.first { it.showContent && it.nodeShortName != null }
|
||||
widgetStateProvider.state.first { it.showContent && it.nodeShortName != null }
|
||||
}
|
||||
|
||||
Logger.i { "Real node data acquired. Pushing updated widget preview." }
|
||||
|
|
@ -113,17 +109,20 @@ open class MeshUtilApplication :
|
|||
}
|
||||
|
||||
// Initialize DatabaseManager asynchronously with current device address so DAO consumers have an active DB
|
||||
val entryPoint = EntryPointAccessors.fromApplication(this, AppEntryPoint::class.java)
|
||||
applicationScope.launch { entryPoint.databaseManager().init(entryPoint.meshPrefs().deviceAddress.value) }
|
||||
applicationScope.launch {
|
||||
val dbManager: DatabaseManager = get()
|
||||
val meshPrefs: MeshPrefs = get()
|
||||
dbManager.init(meshPrefs.deviceAddress.value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
// Shutdown managers (useful for Robolectric tests)
|
||||
val entryPoint = EntryPointAccessors.fromApplication(this, AppEntryPoint::class.java)
|
||||
entryPoint.databaseManager().close()
|
||||
entryPoint.androidEnvironment().close()
|
||||
get<DatabaseManager>().close()
|
||||
get<AndroidEnvironment>().close()
|
||||
applicationScope.cancel()
|
||||
super.onTerminate()
|
||||
org.koin.core.context.stopKoin()
|
||||
}
|
||||
|
||||
private fun scheduleMeshLogCleanup() {
|
||||
|
|
@ -139,19 +138,7 @@ open class MeshUtilApplication :
|
|||
}
|
||||
|
||||
override val workManagerConfiguration: Configuration
|
||||
get() = Configuration.Builder().setWorkerFactory(workerFactory).build()
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface AppEntryPoint {
|
||||
fun databaseManager(): DatabaseManager
|
||||
|
||||
fun meshPrefs(): MeshPrefs
|
||||
|
||||
fun meshLogPrefs(): MeshLogPrefs
|
||||
|
||||
fun androidEnvironment(): AndroidEnvironment
|
||||
get() = Configuration.Builder().setWorkerFactory(get()).build()
|
||||
}
|
||||
|
||||
fun logAssert(executeReliableWrite: Boolean) {
|
||||
|
|
|
|||
111
app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt
Normal file
111
app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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.app.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.hardware.usb.UsbManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.work.WorkManager
|
||||
import com.hoho.android.usbserial.driver.ProbeTable
|
||||
import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||
import org.koin.core.annotation.Module
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.repository.usb.ProbeTableProvider
|
||||
import org.meshtastic.core.ble.di.CoreBleAndroidModule
|
||||
import org.meshtastic.core.ble.di.CoreBleModule
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.common.di.CoreCommonModule
|
||||
import org.meshtastic.core.data.di.CoreDataAndroidModule
|
||||
import org.meshtastic.core.data.di.CoreDataModule
|
||||
import org.meshtastic.core.database.di.CoreDatabaseAndroidModule
|
||||
import org.meshtastic.core.database.di.CoreDatabaseModule
|
||||
import org.meshtastic.core.datastore.di.CoreDatastoreAndroidModule
|
||||
import org.meshtastic.core.datastore.di.CoreDatastoreModule
|
||||
import org.meshtastic.core.di.di.CoreDiModule
|
||||
import org.meshtastic.core.network.di.CoreNetworkModule
|
||||
import org.meshtastic.core.prefs.di.CorePrefsAndroidModule
|
||||
import org.meshtastic.core.prefs.di.CorePrefsModule
|
||||
import org.meshtastic.core.service.di.CoreServiceAndroidModule
|
||||
import org.meshtastic.core.service.di.CoreServiceModule
|
||||
import org.meshtastic.core.ui.di.CoreUiModule
|
||||
import org.meshtastic.feature.firmware.di.FeatureFirmwareModule
|
||||
import org.meshtastic.feature.intro.di.FeatureIntroModule
|
||||
import org.meshtastic.feature.map.di.FeatureMapModule
|
||||
import org.meshtastic.feature.messaging.di.FeatureMessagingModule
|
||||
import org.meshtastic.feature.node.di.FeatureNodeModule
|
||||
import org.meshtastic.feature.settings.di.FeatureSettingsModule
|
||||
|
||||
@Module(
|
||||
includes =
|
||||
[
|
||||
org.meshtastic.app.MainKoinModule::class,
|
||||
CoreDiModule::class,
|
||||
CoreCommonModule::class,
|
||||
CoreBleModule::class,
|
||||
CoreBleAndroidModule::class,
|
||||
CoreDataModule::class,
|
||||
CoreDataAndroidModule::class,
|
||||
org.meshtastic.core.domain.di.CoreDomainModule::class,
|
||||
CoreDatabaseModule::class,
|
||||
CoreDatabaseAndroidModule::class,
|
||||
org.meshtastic.core.repository.di.CoreRepositoryModule::class,
|
||||
CoreDatastoreModule::class,
|
||||
CoreDatastoreAndroidModule::class,
|
||||
CorePrefsModule::class,
|
||||
CorePrefsAndroidModule::class,
|
||||
CoreServiceModule::class,
|
||||
CoreServiceAndroidModule::class,
|
||||
CoreNetworkModule::class,
|
||||
CoreUiModule::class,
|
||||
FeatureNodeModule::class,
|
||||
FeatureMessagingModule::class,
|
||||
FeatureMapModule::class,
|
||||
FeatureSettingsModule::class,
|
||||
FeatureFirmwareModule::class,
|
||||
FeatureIntroModule::class,
|
||||
NetworkModule::class,
|
||||
FlavorModule::class,
|
||||
],
|
||||
)
|
||||
class AppKoinModule {
|
||||
@Single
|
||||
@Named("ProcessLifecycle")
|
||||
fun provideProcessLifecycle(): Lifecycle = ProcessLifecycleOwner.get().lifecycle
|
||||
|
||||
@Single
|
||||
fun provideBuildConfigProvider(): BuildConfigProvider = object : BuildConfigProvider {
|
||||
override val isDebug: Boolean = org.meshtastic.app.BuildConfig.DEBUG
|
||||
override val applicationId: String = org.meshtastic.app.BuildConfig.APPLICATION_ID
|
||||
override val versionCode: Int = org.meshtastic.app.BuildConfig.VERSION_CODE
|
||||
override val versionName: String = org.meshtastic.app.BuildConfig.VERSION_NAME
|
||||
override val absoluteMinFwVersion: String = org.meshtastic.app.BuildConfig.ABS_MIN_FW_VERSION
|
||||
override val minFwVersion: String = org.meshtastic.app.BuildConfig.MIN_FW_VERSION
|
||||
}
|
||||
|
||||
@Single fun provideWorkManager(context: Application): WorkManager = WorkManager.getInstance(context)
|
||||
|
||||
@Single
|
||||
fun provideUsbManager(application: Application): UsbManager? =
|
||||
application.getSystemService(Context.USB_SERVICE) as UsbManager?
|
||||
|
||||
@Single fun provideProbeTable(provider: ProbeTableProvider): ProbeTable = provider.get()
|
||||
|
||||
@Single fun provideUsbSerialProber(probeTable: ProbeTable): UsbSerialProber = UsbSerialProber(probeTable)
|
||||
}
|
||||
|
|
@ -1,75 +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.app.di
|
||||
|
||||
import android.content.Context
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import no.nordicsemi.kotlin.ble.client.android.CentralManager
|
||||
import no.nordicsemi.kotlin.ble.client.android.native
|
||||
import no.nordicsemi.kotlin.ble.core.android.AndroidEnvironment
|
||||
import no.nordicsemi.kotlin.ble.environment.android.NativeAndroidEnvironment
|
||||
import org.meshtastic.core.ble.AndroidBleConnectionFactory
|
||||
import org.meshtastic.core.ble.AndroidBleScanner
|
||||
import org.meshtastic.core.ble.AndroidBluetoothRepository
|
||||
import org.meshtastic.core.ble.BleConnection
|
||||
import org.meshtastic.core.ble.BleConnectionFactory
|
||||
import org.meshtastic.core.ble.BleScanner
|
||||
import org.meshtastic.core.ble.BluetoothRepository
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class BleModule {
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindBleScanner(impl: AndroidBleScanner): BleScanner
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindBluetoothRepository(impl: AndroidBluetoothRepository): BluetoothRepository
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindBleConnectionFactory(impl: AndroidBleConnectionFactory): BleConnectionFactory
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAndroidEnvironment(@ApplicationContext context: Context): AndroidEnvironment =
|
||||
NativeAndroidEnvironment.getInstance(context, isNeverForLocationFlagSet = true)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBleSingletonCoroutineScope(dispatchers: CoroutineDispatchers): CoroutineScope =
|
||||
CoroutineScope(SupervisorJob() + dispatchers.default)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCentralManager(environment: AndroidEnvironment, coroutineScope: CoroutineScope): CentralManager =
|
||||
CentralManager.native(environment as NativeAndroidEnvironment, coroutineScope)
|
||||
|
||||
@Provides
|
||||
fun provideBleConnection(factory: BleConnectionFactory, coroutineScope: CoroutineScope): BleConnection =
|
||||
factory.create(coroutineScope, "BLE")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +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.app.di
|
||||
|
||||
import android.content.Context
|
||||
import android.location.LocationManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideLocationManager(@ApplicationContext context: Context): LocationManager =
|
||||
context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
}
|
||||
|
|
@ -1,47 +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.app.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.data.datasource.BootloaderOtaQuirksJsonDataSource
|
||||
import org.meshtastic.core.data.datasource.BootloaderOtaQuirksJsonDataSourceImpl
|
||||
import org.meshtastic.core.data.datasource.DeviceHardwareJsonDataSource
|
||||
import org.meshtastic.core.data.datasource.DeviceHardwareJsonDataSourceImpl
|
||||
import org.meshtastic.core.data.datasource.FirmwareReleaseJsonDataSource
|
||||
import org.meshtastic.core.data.datasource.FirmwareReleaseJsonDataSourceImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface DataSourceModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindDeviceHardwareJsonDataSource(impl: DeviceHardwareJsonDataSourceImpl): DeviceHardwareJsonDataSource
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindFirmwareReleaseJsonDataSource(impl: FirmwareReleaseJsonDataSourceImpl): FirmwareReleaseJsonDataSource
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindBootloaderOtaQuirksJsonDataSource(
|
||||
impl: BootloaderOtaQuirksJsonDataSourceImpl,
|
||||
): BootloaderOtaQuirksJsonDataSource
|
||||
}
|
||||
|
|
@ -1,165 +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.app.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.core.okio.OkioStorage
|
||||
import androidx.datastore.dataStoreFile
|
||||
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import okio.FileSystem
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import org.meshtastic.core.datastore.KEY_APP_INTRO_COMPLETED
|
||||
import org.meshtastic.core.datastore.KEY_INCLUDE_UNKNOWN
|
||||
import org.meshtastic.core.datastore.KEY_NODE_SORT
|
||||
import org.meshtastic.core.datastore.KEY_ONLY_DIRECT
|
||||
import org.meshtastic.core.datastore.KEY_ONLY_ONLINE
|
||||
import org.meshtastic.core.datastore.KEY_SHOW_IGNORED
|
||||
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.LocalStatsSerializer
|
||||
import org.meshtastic.core.datastore.serializer.ModuleConfigSerializer
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import org.meshtastic.proto.LocalModuleConfig
|
||||
import org.meshtastic.proto.LocalStats
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val USER_PREFERENCES_NAME = "user_preferences"
|
||||
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Qualifier
|
||||
annotation class DataStoreScope
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object DataStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@DataStoreScope
|
||||
fun provideDataStoreScope(): CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providePreferencesDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<Preferences> = PreferenceDataStoreFactory.create(
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }),
|
||||
migrations =
|
||||
listOf(
|
||||
SharedPreferencesMigration(context = appContext, sharedPreferencesName = USER_PREFERENCES_NAME),
|
||||
SharedPreferencesMigration(
|
||||
context = appContext,
|
||||
sharedPreferencesName = "ui-prefs",
|
||||
keysToMigrate =
|
||||
setOf(
|
||||
KEY_APP_INTRO_COMPLETED,
|
||||
KEY_THEME,
|
||||
KEY_NODE_SORT,
|
||||
KEY_INCLUDE_UNKNOWN,
|
||||
KEY_ONLY_ONLINE,
|
||||
KEY_ONLY_DIRECT,
|
||||
KEY_SHOW_IGNORED,
|
||||
),
|
||||
),
|
||||
),
|
||||
scope = scope,
|
||||
produceFile = { appContext.preferencesDataStoreFile(USER_PREFERENCES_NAME) },
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLocalConfigDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<LocalConfig> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
fileSystem = FileSystem.SYSTEM,
|
||||
serializer = LocalConfigSerializer,
|
||||
producePath = { appContext.dataStoreFile("local_config.pb").toOkioPath() },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { LocalConfig() }),
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideModuleConfigDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<LocalModuleConfig> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
fileSystem = FileSystem.SYSTEM,
|
||||
serializer = ModuleConfigSerializer,
|
||||
producePath = { appContext.dataStoreFile("module_config.pb").toOkioPath() },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { LocalModuleConfig() }),
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideChannelSetDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<ChannelSet> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
fileSystem = FileSystem.SYSTEM,
|
||||
serializer = ChannelSetSerializer,
|
||||
producePath = { appContext.dataStoreFile("channel_set.pb").toOkioPath() },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { ChannelSet() }),
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLocalStatsDataStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
@DataStoreScope scope: CoroutineScope,
|
||||
): DataStore<LocalStats> = DataStoreFactory.create(
|
||||
storage =
|
||||
OkioStorage(
|
||||
fileSystem = FileSystem.SYSTEM,
|
||||
serializer = LocalStatsSerializer,
|
||||
producePath = { appContext.dataStoreFile("local_stats.pb").toOkioPath() },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { LocalStats() }),
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
|
@ -16,7 +16,10 @@
|
|||
*/
|
||||
package org.meshtastic.app.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.nsd.NsdManager
|
||||
import coil3.ImageLoader
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.memory.MemoryCache
|
||||
|
|
@ -25,72 +28,66 @@ import coil3.request.crossfade
|
|||
import coil3.svg.SvgDecoder
|
||||
import coil3.util.DebugLogger
|
||||
import coil3.util.Logger
|
||||
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 io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koin.core.annotation.Module
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val DISK_CACHE_PERCENT = 0.02
|
||||
private const val MEMORY_CACHE_PERCENT = 0.25
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface NetworkModule {
|
||||
class NetworkModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
@Single
|
||||
fun provideConnectivityManager(application: Application): ConnectivityManager =
|
||||
application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
@Single
|
||||
fun provideNsdManager(application: Application): NsdManager =
|
||||
application.getSystemService(Context.NSD_SERVICE) as NsdManager
|
||||
|
||||
@Single
|
||||
fun bindMqttRepository(
|
||||
impl: org.meshtastic.core.network.repository.MQTTRepositoryImpl,
|
||||
): org.meshtastic.core.network.repository.MQTTRepository
|
||||
): org.meshtastic.core.network.repository.MQTTRepository = impl
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideImageLoader(
|
||||
okHttpClient: OkHttpClient,
|
||||
@ApplicationContext application: Context,
|
||||
buildConfigProvider: BuildConfigProvider,
|
||||
): ImageLoader {
|
||||
val sharedOkHttp = okHttpClient.newBuilder().build()
|
||||
return ImageLoader.Builder(context = application)
|
||||
.components {
|
||||
add(OkHttpNetworkFetcherFactory(callFactory = { sharedOkHttp }))
|
||||
add(SvgDecoder.Factory(scaleToDensity = true))
|
||||
}
|
||||
.memoryCache {
|
||||
MemoryCache.Builder().maxSizePercent(context = application, percent = MEMORY_CACHE_PERCENT).build()
|
||||
}
|
||||
.diskCache { DiskCache.Builder().maxSizePercent(percent = DISK_CACHE_PERCENT).build() }
|
||||
.logger(
|
||||
logger = if (buildConfigProvider.isDebug) DebugLogger(minLevel = Logger.Level.Verbose) else null,
|
||||
)
|
||||
.crossfade(enable = true)
|
||||
.build()
|
||||
}
|
||||
@Single
|
||||
fun provideImageLoader(
|
||||
okHttpClient: OkHttpClient,
|
||||
application: Context,
|
||||
buildConfigProvider: BuildConfigProvider,
|
||||
): ImageLoader {
|
||||
val sharedOkHttp = okHttpClient.newBuilder().build()
|
||||
return ImageLoader.Builder(context = application)
|
||||
.components {
|
||||
add(OkHttpNetworkFetcherFactory(callFactory = { sharedOkHttp }))
|
||||
add(SvgDecoder.Factory(scaleToDensity = true))
|
||||
}
|
||||
.memoryCache {
|
||||
MemoryCache.Builder().maxSizePercent(context = application, percent = MEMORY_CACHE_PERCENT).build()
|
||||
}
|
||||
.diskCache { DiskCache.Builder().maxSizePercent(percent = DISK_CACHE_PERCENT).build() }
|
||||
.logger(logger = if (buildConfigProvider.isDebug) DebugLogger(minLevel = Logger.Level.Verbose) else null)
|
||||
.crossfade(enable = true)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideJson(): Json = Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
@Single
|
||||
fun provideJson(): Json = Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideHttpClient(okHttpClient: OkHttpClient, json: Json): HttpClient = HttpClient(engineFactory = OkHttp) {
|
||||
engine { preconfigured = okHttpClient }
|
||||
@Single
|
||||
fun provideHttpClient(okHttpClient: OkHttpClient, json: Json): HttpClient = HttpClient(engineFactory = OkHttp) {
|
||||
engine { preconfigured = okHttpClient }
|
||||
|
||||
install(plugin = ContentNegotiation) { json(json) }
|
||||
}
|
||||
install(plugin = ContentNegotiation) { json(json) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +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.app.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.data.datasource.NodeInfoReadDataSource
|
||||
import org.meshtastic.core.data.datasource.NodeInfoWriteDataSource
|
||||
import org.meshtastic.core.data.datasource.SwitchingNodeInfoReadDataSource
|
||||
import org.meshtastic.core.data.datasource.SwitchingNodeInfoWriteDataSource
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface NodeDataSourceModule {
|
||||
@Binds @Singleton
|
||||
fun bindNodeInfoReadDataSource(impl: SwitchingNodeInfoReadDataSource): NodeInfoReadDataSource
|
||||
|
||||
@Binds @Singleton
|
||||
fun bindNodeInfoWriteDataSource(impl: SwitchingNodeInfoWriteDataSource): NodeInfoWriteDataSource
|
||||
}
|
||||
|
|
@ -1,269 +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.app.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import org.meshtastic.core.prefs.analytics.AnalyticsPrefsImpl
|
||||
import org.meshtastic.core.prefs.di.AnalyticsDataStore
|
||||
import org.meshtastic.core.prefs.di.AppDataStore
|
||||
import org.meshtastic.core.prefs.di.CustomEmojiDataStore
|
||||
import org.meshtastic.core.prefs.di.FilterDataStore
|
||||
import org.meshtastic.core.prefs.di.HomoglyphEncodingDataStore
|
||||
import org.meshtastic.core.prefs.di.MapConsentDataStore
|
||||
import org.meshtastic.core.prefs.di.MapDataStore
|
||||
import org.meshtastic.core.prefs.di.MapTileProviderDataStore
|
||||
import org.meshtastic.core.prefs.di.MeshDataStore
|
||||
import org.meshtastic.core.prefs.di.MeshLogDataStore
|
||||
import org.meshtastic.core.prefs.di.RadioDataStore
|
||||
import org.meshtastic.core.prefs.di.UiDataStore
|
||||
import org.meshtastic.core.prefs.emoji.CustomEmojiPrefsImpl
|
||||
import org.meshtastic.core.prefs.filter.FilterPrefsImpl
|
||||
import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefsImpl
|
||||
import org.meshtastic.core.prefs.map.MapConsentPrefsImpl
|
||||
import org.meshtastic.core.prefs.map.MapPrefsImpl
|
||||
import org.meshtastic.core.prefs.map.MapTileProviderPrefsImpl
|
||||
import org.meshtastic.core.prefs.mesh.MeshPrefsImpl
|
||||
import org.meshtastic.core.prefs.meshlog.MeshLogPrefsImpl
|
||||
import org.meshtastic.core.prefs.radio.RadioPrefsImpl
|
||||
import org.meshtastic.core.prefs.ui.UiPrefsImpl
|
||||
import org.meshtastic.core.repository.AnalyticsPrefs
|
||||
import org.meshtastic.core.repository.CustomEmojiPrefs
|
||||
import org.meshtastic.core.repository.FilterPrefs
|
||||
import org.meshtastic.core.repository.HomoglyphPrefs
|
||||
import org.meshtastic.core.repository.MapConsentPrefs
|
||||
import org.meshtastic.core.repository.MapPrefs
|
||||
import org.meshtastic.core.repository.MapTileProviderPrefs
|
||||
import org.meshtastic.core.repository.MeshLogPrefs
|
||||
import org.meshtastic.core.repository.MeshPrefs
|
||||
import org.meshtastic.core.repository.RadioPrefs
|
||||
import org.meshtastic.core.repository.UiPrefs
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class AnalyticsDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class HomoglyphEncodingDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class AppDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class CustomEmojiDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class MapDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class MapConsentDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class MapTileProviderDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class MeshDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class RadioDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class UiDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class MeshLogDataStore
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
internal annotation class FilterDataStore
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface PrefsModule {
|
||||
|
||||
@Binds fun bindAnalyticsPrefs(analyticsPrefsImpl: AnalyticsPrefsImpl): AnalyticsPrefs
|
||||
|
||||
@Binds fun bindHomoglyphEncodingPrefs(homoglyphEncodingPrefsImpl: HomoglyphPrefsImpl): HomoglyphPrefs
|
||||
|
||||
@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 bindMeshLogPrefs(meshLogPrefsImpl: MeshLogPrefsImpl): MeshLogPrefs
|
||||
|
||||
@Binds fun bindRadioPrefs(radioPrefsImpl: RadioPrefsImpl): RadioPrefs
|
||||
|
||||
@Binds fun bindUiPrefs(uiPrefsImpl: UiPrefsImpl): UiPrefs
|
||||
|
||||
@Binds fun bindFilterPrefs(filterPrefsImpl: FilterPrefsImpl): FilterPrefs
|
||||
|
||||
companion object {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@AnalyticsDataStore
|
||||
fun provideAnalyticsDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "analytics-prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("analytics_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@HomoglyphEncodingDataStore
|
||||
fun provideHomoglyphEncodingDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "homoglyph-encoding-prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("homoglyph_encoding_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@AppDataStore
|
||||
fun provideAppDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("app_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@CustomEmojiDataStore
|
||||
fun provideCustomEmojiDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "org.geeksville.emoji.prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("custom_emoji_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@MapDataStore
|
||||
fun provideMapDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "map_prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("map_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@MapConsentDataStore
|
||||
fun provideMapConsentDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "map_consent_preferences")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("map_consent_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@MapTileProviderDataStore
|
||||
fun provideMapTileProviderDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "map_tile_provider_prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("map_tile_provider_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@MeshDataStore
|
||||
fun provideMeshDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "mesh-prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("mesh_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@RadioDataStore
|
||||
fun provideRadioDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "radio-prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("radio_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@UiDataStore
|
||||
fun provideUiDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "ui-prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("ui_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@MeshLogDataStore
|
||||
fun provideMeshLogDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "meshlog-prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("meshlog_ds") },
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@FilterDataStore
|
||||
fun provideFilterDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context, "filter-prefs")),
|
||||
scope = scope,
|
||||
produceFile = { context.preferencesDataStoreFile("filter_ds") },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,163 +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.app.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.data.manager.CommandSenderImpl
|
||||
import org.meshtastic.core.data.manager.FromRadioPacketHandlerImpl
|
||||
import org.meshtastic.core.data.manager.HistoryManagerImpl
|
||||
import org.meshtastic.core.data.manager.MeshActionHandlerImpl
|
||||
import org.meshtastic.core.data.manager.MeshConfigFlowManagerImpl
|
||||
import org.meshtastic.core.data.manager.MeshConfigHandlerImpl
|
||||
import org.meshtastic.core.data.manager.MeshConnectionManagerImpl
|
||||
import org.meshtastic.core.data.manager.MeshDataHandlerImpl
|
||||
import org.meshtastic.core.data.manager.MeshMessageProcessorImpl
|
||||
import org.meshtastic.core.data.manager.MeshRouterImpl
|
||||
import org.meshtastic.core.data.manager.MessageFilterImpl
|
||||
import org.meshtastic.core.data.manager.MqttManagerImpl
|
||||
import org.meshtastic.core.data.manager.NeighborInfoHandlerImpl
|
||||
import org.meshtastic.core.data.manager.NodeManagerImpl
|
||||
import org.meshtastic.core.data.manager.PacketHandlerImpl
|
||||
import org.meshtastic.core.data.manager.TracerouteHandlerImpl
|
||||
import org.meshtastic.core.data.repository.DeviceHardwareRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.LocationRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.MeshLogRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.NodeRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.PacketRepositoryImpl
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepositoryImpl
|
||||
import org.meshtastic.core.model.util.MeshDataMapper
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.DeviceHardwareRepository
|
||||
import org.meshtastic.core.repository.FromRadioPacketHandler
|
||||
import org.meshtastic.core.repository.HistoryManager
|
||||
import org.meshtastic.core.repository.LocationRepository
|
||||
import org.meshtastic.core.repository.MeshActionHandler
|
||||
import org.meshtastic.core.repository.MeshConfigFlowManager
|
||||
import org.meshtastic.core.repository.MeshConfigHandler
|
||||
import org.meshtastic.core.repository.MeshConnectionManager
|
||||
import org.meshtastic.core.repository.MeshDataHandler
|
||||
import org.meshtastic.core.repository.MeshLogRepository
|
||||
import org.meshtastic.core.repository.MeshMessageProcessor
|
||||
import org.meshtastic.core.repository.MeshRouter
|
||||
import org.meshtastic.core.repository.MessageFilter
|
||||
import org.meshtastic.core.repository.MqttManager
|
||||
import org.meshtastic.core.repository.NeighborInfoHandler
|
||||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.TracerouteHandler
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class RepositoryModule {
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindNodeRepository(nodeRepositoryImpl: NodeRepositoryImpl): NodeRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindRadioConfigRepository(radioConfigRepositoryImpl: RadioConfigRepositoryImpl): RadioConfigRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindLocationRepository(locationRepositoryImpl: LocationRepositoryImpl): LocationRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindDeviceHardwareRepository(
|
||||
deviceHardwareRepositoryImpl: DeviceHardwareRepositoryImpl,
|
||||
): DeviceHardwareRepository
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindPacketRepository(packetRepositoryImpl: PacketRepositoryImpl): PacketRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshLogRepository(meshLogRepositoryImpl: MeshLogRepositoryImpl): MeshLogRepository
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindNodeManager(nodeManagerImpl: NodeManagerImpl): NodeManager
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindCommandSender(commandSenderImpl: CommandSenderImpl): CommandSender
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindHistoryManager(historyManagerImpl: HistoryManagerImpl): HistoryManager
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindTracerouteHandler(tracerouteHandlerImpl: TracerouteHandlerImpl): TracerouteHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindNeighborInfoHandler(neighborInfoHandlerImpl: NeighborInfoHandlerImpl): NeighborInfoHandler
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindMqttManager(mqttManagerImpl: MqttManagerImpl): MqttManager
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindPacketHandler(packetHandlerImpl: PacketHandlerImpl): PacketHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshConnectionManager(meshConnectionManagerImpl: MeshConnectionManagerImpl): MeshConnectionManager
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindMeshDataHandler(meshDataHandlerImpl: MeshDataHandlerImpl): MeshDataHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshActionHandler(meshActionHandlerImpl: MeshActionHandlerImpl): MeshActionHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshMessageProcessor(meshMessageProcessorImpl: MeshMessageProcessorImpl): MeshMessageProcessor
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindMeshRouter(meshRouterImpl: MeshRouterImpl): MeshRouter
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindFromRadioPacketHandler(
|
||||
fromRadioPacketHandlerImpl: FromRadioPacketHandlerImpl,
|
||||
): FromRadioPacketHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshConfigHandler(meshConfigHandlerImpl: MeshConfigHandlerImpl): MeshConfigHandler
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindMeshConfigFlowManager(meshConfigFlowManagerImpl: MeshConfigFlowManagerImpl): MeshConfigFlowManager
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindMessageFilter(messageFilterImpl: MessageFilterImpl): MessageFilter
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMeshDataMapper(nodeManager: NodeManager): MeshDataMapper = MeshDataMapper(nodeManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* 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.app.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.service.AndroidRadioControllerImpl
|
||||
import org.meshtastic.core.service.AndroidServiceRepository
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class ServiceModule {
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindRadioController(impl: AndroidRadioControllerImpl): RadioController
|
||||
|
||||
@Binds @Singleton
|
||||
abstract fun bindServiceRepository(impl: AndroidServiceRepository): ServiceRepository
|
||||
}
|
||||
|
|
@ -1,45 +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.app.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.HomoglyphPrefs
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.usecase.SendMessageUseCase
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object UseCaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendMessageUseCase(
|
||||
nodeRepository: NodeRepository,
|
||||
packetRepository: PacketRepository,
|
||||
radioController: RadioController,
|
||||
homoglyphEncodingPrefs: HomoglyphPrefs,
|
||||
messageQueue: MessageQueue,
|
||||
): SendMessageUseCase =
|
||||
SendMessageUseCase(nodeRepository, packetRepository, radioController, homoglyphEncodingPrefs, messageQueue)
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.model.DeviceListEntry
|
||||
import org.meshtastic.app.model.getMeshtasticShortName
|
||||
import org.meshtastic.app.repository.network.NetworkRepository
|
||||
|
|
@ -37,7 +38,6 @@ import org.meshtastic.core.repository.RadioInterfaceService
|
|||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.meshtastic
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
data class DiscoveredDevices(
|
||||
val bleDevices: List<DeviceListEntry>,
|
||||
|
|
@ -47,9 +47,8 @@ data class DiscoveredDevices(
|
|||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class GetDiscoveredDevicesUseCase
|
||||
@Inject
|
||||
constructor(
|
||||
@Single
|
||||
class GetDiscoveredDevicesUseCase(
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
private val networkRepository: NetworkRepository,
|
||||
private val recentAddressesDataSource: RecentAddressesDataSource,
|
||||
|
|
@ -57,7 +56,7 @@ constructor(
|
|||
private val databaseManager: DatabaseManager,
|
||||
private val usbRepository: UsbRepository,
|
||||
private val radioInterfaceService: RadioInterfaceService,
|
||||
private val usbManagerLazy: dagger.Lazy<UsbManager>,
|
||||
private val usbManagerLazy: Lazy<UsbManager>,
|
||||
) {
|
||||
private val suffixLength = 4
|
||||
|
||||
|
|
@ -94,7 +93,7 @@ constructor(
|
|||
|
||||
val usbDevicesFlow =
|
||||
usbRepository.serialDevices.map { usb ->
|
||||
usb.map { (_, d) -> DeviceListEntry.Usb(radioInterfaceService, usbManagerLazy.get(), d) }
|
||||
usb.map { (_, d) -> DeviceListEntry.Usb(radioInterfaceService, usbManagerLazy.value, d) }
|
||||
}
|
||||
|
||||
return combine(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.app.firmware
|
||||
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.data.repository.FirmwareReleaseRepository
|
||||
import org.meshtastic.core.datastore.BootloaderWarningDataSource
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.DeviceHardwareRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioPrefs
|
||||
import org.meshtastic.feature.firmware.FirmwareFileHandler
|
||||
import org.meshtastic.feature.firmware.FirmwareUpdateManager
|
||||
import org.meshtastic.feature.firmware.FirmwareUpdateViewModel
|
||||
import org.meshtastic.feature.firmware.FirmwareUsbManager
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@KoinViewModel
|
||||
class AndroidFirmwareUpdateViewModel(
|
||||
firmwareReleaseRepository: FirmwareReleaseRepository,
|
||||
deviceHardwareRepository: DeviceHardwareRepository,
|
||||
nodeRepository: NodeRepository,
|
||||
radioController: RadioController,
|
||||
radioPrefs: RadioPrefs,
|
||||
bootloaderWarningDataSource: BootloaderWarningDataSource,
|
||||
firmwareUpdateManager: FirmwareUpdateManager,
|
||||
usbManager: FirmwareUsbManager,
|
||||
fileHandler: FirmwareFileHandler,
|
||||
) : FirmwareUpdateViewModel(
|
||||
firmwareReleaseRepository,
|
||||
deviceHardwareRepository,
|
||||
nodeRepository,
|
||||
radioController,
|
||||
radioPrefs,
|
||||
bootloaderWarningDataSource,
|
||||
firmwareUpdateManager,
|
||||
usbManager,
|
||||
fileHandler,
|
||||
)
|
||||
|
|
@ -16,9 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.app.intro
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.feature.intro.IntroViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Android-specific Hilt wrapper for IntroViewModel. */
|
||||
@HiltViewModel class AndroidIntroViewModel @Inject constructor() : IntroViewModel()
|
||||
/** Android-specific Koin wrapper for IntroViewModel. */
|
||||
@KoinViewModel class AndroidIntroViewModel : IntroViewModel()
|
||||
|
|
|
|||
|
|
@ -16,18 +16,15 @@
|
|||
*/
|
||||
package org.meshtastic.app.map
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.MapPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.feature.map.SharedMapViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AndroidSharedMapViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@KoinViewModel
|
||||
class AndroidSharedMapViewModel(
|
||||
mapPrefs: MapPrefs,
|
||||
nodeRepository: NodeRepository,
|
||||
packetRepository: PacketRepository,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package org.meshtastic.app.map.node
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.navigation.toRoute
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
|
@ -27,6 +26,7 @@ import kotlinx.coroutines.flow.flatMapLatest
|
|||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.database.entity.MeshLog
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
|
|
@ -37,12 +37,9 @@ import org.meshtastic.core.ui.util.toPosition
|
|||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.PortNum
|
||||
import org.meshtastic.proto.Position
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class NodeMapViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@KoinViewModel
|
||||
class NodeMapViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
nodeRepository: NodeRepository,
|
||||
meshLogRepository: MeshLogRepository,
|
||||
|
|
|
|||
|
|
@ -16,18 +16,15 @@
|
|||
*/
|
||||
package org.meshtastic.app.messaging
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.feature.messaging.ui.contact.ContactsViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AndroidContactsViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@KoinViewModel
|
||||
class AndroidContactsViewModel(
|
||||
nodeRepository: NodeRepository,
|
||||
packetRepository: PacketRepository,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
package org.meshtastic.app.messaging
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.data.repository.QuickChatActionRepository
|
||||
import org.meshtastic.core.repository.CustomEmojiPrefs
|
||||
import org.meshtastic.core.repository.HomoglyphPrefs
|
||||
|
|
@ -29,13 +29,10 @@ import org.meshtastic.core.repository.ServiceRepository
|
|||
import org.meshtastic.core.repository.UiPrefs
|
||||
import org.meshtastic.core.repository.usecase.SendMessageUseCase
|
||||
import org.meshtastic.feature.messaging.MessageViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
class AndroidMessageViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@KoinViewModel
|
||||
class AndroidMessageViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
nodeRepository: NodeRepository,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
|
|
|
|||
|
|
@ -16,11 +16,10 @@
|
|||
*/
|
||||
package org.meshtastic.app.messaging
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.data.repository.QuickChatActionRepository
|
||||
import org.meshtastic.feature.messaging.QuickChatViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AndroidQuickChatViewModel @Inject constructor(quickChatActionRepository: QuickChatActionRepository) :
|
||||
@KoinViewModel
|
||||
class AndroidQuickChatViewModel(quickChatActionRepository: QuickChatActionRepository) :
|
||||
QuickChatViewModel(quickChatActionRepository)
|
||||
|
|
|
|||
|
|
@ -17,22 +17,18 @@
|
|||
package org.meshtastic.app.messaging.domain.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.koin.android.annotation.KoinWorker
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
|
||||
@HiltWorker
|
||||
class SendMessageWorker
|
||||
@AssistedInject
|
||||
constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted params: WorkerParameters,
|
||||
@KoinWorker
|
||||
class SendMessageWorker(
|
||||
context: Context,
|
||||
params: WorkerParameters,
|
||||
private val packetRepository: PacketRepository,
|
||||
private val radioController: RadioController,
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
|
|
|||
|
|
@ -20,13 +20,12 @@ import androidx.work.ExistingWorkPolicy
|
|||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** Android implementation of [MessageQueue] that uses [WorkManager] for reliable background transmission. */
|
||||
@Singleton
|
||||
class WorkManagerMessageQueue @Inject constructor(private val workManager: WorkManager) : MessageQueue {
|
||||
@Single
|
||||
class WorkManagerMessageQueue(private val workManager: WorkManager) : MessageQueue {
|
||||
|
||||
override suspend fun enqueue(packetId: Int) {
|
||||
val workRequest =
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import android.net.Uri
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -35,6 +34,7 @@ import kotlinx.coroutines.flow.mapNotNull
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.data.repository.FirmwareReleaseRepository
|
||||
import org.meshtastic.core.database.entity.asDeviceVersion
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
|
|
@ -62,13 +62,10 @@ import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
|||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.ClientNotification
|
||||
import org.meshtastic.proto.SharedContact
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@KoinViewModel
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
class UIViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
class UIViewModel(
|
||||
private val nodeDB: NodeRepository,
|
||||
private val serviceRepository: AndroidServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@
|
|||
package org.meshtastic.app.navigation
|
||||
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import androidx.navigation.navigation
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.settings.AndroidRadioConfigViewModel
|
||||
import org.meshtastic.app.ui.sharing.ChannelScreen
|
||||
import org.meshtastic.core.navigation.ChannelsRoutes
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
|
|
@ -38,7 +39,7 @@ fun NavGraphBuilder.channelsGraph(navController: NavHostController) {
|
|||
) { backStackEntry ->
|
||||
val parentEntry = remember(backStackEntry) { navController.getBackStackEntry(ChannelsRoutes.ChannelsGraph) }
|
||||
ChannelScreen(
|
||||
radioConfigViewModel = hiltViewModel(parentEntry),
|
||||
radioConfigViewModel = koinViewModel<AndroidRadioConfigViewModel>(viewModelStoreOwner = parentEntry),
|
||||
onNavigate = { route -> navController.navigate(route) },
|
||||
onNavigateUp = { navController.navigateUp() },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@
|
|||
package org.meshtastic.app.navigation
|
||||
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import androidx.navigation.navigation
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.settings.AndroidRadioConfigViewModel
|
||||
import org.meshtastic.app.ui.connections.ConnectionsScreen
|
||||
import org.meshtastic.core.navigation.ConnectionsRoutes
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
|
|
@ -42,7 +43,7 @@ fun NavGraphBuilder.connectionsGraph(navController: NavHostController) {
|
|||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(ConnectionsRoutes.ConnectionsGraph) }
|
||||
ConnectionsScreen(
|
||||
radioConfigViewModel = hiltViewModel(parentEntry),
|
||||
radioConfigViewModel = koinViewModel<AndroidRadioConfigViewModel>(viewModelStoreOwner = parentEntry),
|
||||
onClickNodeChip = {
|
||||
navController.navigate(NodesRoutes.NodeDetailGraph(it)) {
|
||||
launchSingleTop = true
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package org.meshtastic.app.navigation
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
|
|
@ -26,6 +25,7 @@ import androidx.navigation.navDeepLink
|
|||
import androidx.navigation.navigation
|
||||
import androidx.navigation.toRoute
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.messaging.AndroidContactsViewModel
|
||||
import org.meshtastic.app.messaging.AndroidMessageViewModel
|
||||
import org.meshtastic.app.messaging.AndroidQuickChatViewModel
|
||||
|
|
@ -43,11 +43,11 @@ fun NavGraphBuilder.contactsGraph(navController: NavHostController, scrollToTopE
|
|||
composable<ContactsRoutes.Contacts>(
|
||||
deepLinks = listOf(navDeepLink<ContactsRoutes.Contacts>(basePath = "$DEEP_LINK_BASE_URI/contacts")),
|
||||
) {
|
||||
val uiViewModel: UIViewModel = hiltViewModel()
|
||||
val uiViewModel: UIViewModel = koinViewModel()
|
||||
val sharedContactRequested by uiViewModel.sharedContactRequested.collectAsStateWithLifecycle()
|
||||
val requestChannelSet by uiViewModel.requestChannelSet.collectAsStateWithLifecycle()
|
||||
val contactsViewModel = hiltViewModel<AndroidContactsViewModel>()
|
||||
val messageViewModel = hiltViewModel<AndroidMessageViewModel>()
|
||||
val contactsViewModel = koinViewModel<AndroidContactsViewModel>()
|
||||
val messageViewModel = koinViewModel<AndroidMessageViewModel>()
|
||||
|
||||
AdaptiveContactsScreen(
|
||||
navController = navController,
|
||||
|
|
@ -71,11 +71,11 @@ fun NavGraphBuilder.contactsGraph(navController: NavHostController, scrollToTopE
|
|||
),
|
||||
) { backStackEntry ->
|
||||
val args = backStackEntry.toRoute<ContactsRoutes.Messages>()
|
||||
val uiViewModel: UIViewModel = hiltViewModel()
|
||||
val uiViewModel: UIViewModel = koinViewModel()
|
||||
val sharedContactRequested by uiViewModel.sharedContactRequested.collectAsStateWithLifecycle()
|
||||
val requestChannelSet by uiViewModel.requestChannelSet.collectAsStateWithLifecycle()
|
||||
val contactsViewModel = hiltViewModel<AndroidContactsViewModel>()
|
||||
val messageViewModel = hiltViewModel<AndroidMessageViewModel>()
|
||||
val contactsViewModel = koinViewModel<AndroidContactsViewModel>()
|
||||
val messageViewModel = koinViewModel<AndroidMessageViewModel>()
|
||||
|
||||
AdaptiveContactsScreen(
|
||||
navController = navController,
|
||||
|
|
@ -101,7 +101,7 @@ fun NavGraphBuilder.contactsGraph(navController: NavHostController, scrollToTopE
|
|||
),
|
||||
) { backStackEntry ->
|
||||
val message = backStackEntry.toRoute<ContactsRoutes.Share>().message
|
||||
val viewModel = hiltViewModel<AndroidContactsViewModel>()
|
||||
val viewModel = koinViewModel<AndroidContactsViewModel>()
|
||||
ShareScreen(
|
||||
viewModel = viewModel,
|
||||
onConfirm = {
|
||||
|
|
@ -115,7 +115,7 @@ fun NavGraphBuilder.contactsGraph(navController: NavHostController, scrollToTopE
|
|||
composable<ContactsRoutes.QuickChat>(
|
||||
deepLinks = listOf(navDeepLink<ContactsRoutes.QuickChat>(basePath = "$DEEP_LINK_BASE_URI/quick_chat")),
|
||||
) {
|
||||
val viewModel = hiltViewModel<AndroidQuickChatViewModel>()
|
||||
val viewModel = koinViewModel<AndroidQuickChatViewModel>()
|
||||
QuickChatScreen(viewModel = viewModel, onNavigateUp = navController::navigateUp)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,17 @@ package org.meshtastic.app.navigation
|
|||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navigation
|
||||
import androidx.navigation.compose.navigation
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.firmware.AndroidFirmwareUpdateViewModel
|
||||
import org.meshtastic.core.navigation.FirmwareRoutes
|
||||
import org.meshtastic.feature.firmware.FirmwareUpdateScreen
|
||||
|
||||
fun NavGraphBuilder.firmwareGraph(navController: NavController) {
|
||||
navigation<FirmwareRoutes.FirmwareGraph>(startDestination = FirmwareRoutes.FirmwareUpdate) {
|
||||
composable<FirmwareRoutes.FirmwareUpdate> { FirmwareUpdateScreen(navController) }
|
||||
composable<FirmwareRoutes.FirmwareUpdate> {
|
||||
val viewModel = koinViewModel<AndroidFirmwareUpdateViewModel>()
|
||||
FirmwareUpdateScreen(onNavigateUp = { navController.navigateUp() }, viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
*/
|
||||
package org.meshtastic.app.navigation
|
||||
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.map.AndroidSharedMapViewModel
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.navigation.MapRoutes
|
||||
|
|
@ -29,7 +29,7 @@ import org.meshtastic.feature.map.MapScreen
|
|||
|
||||
fun NavGraphBuilder.mapGraph(navController: NavHostController) {
|
||||
composable<MapRoutes.Map>(deepLinks = listOf(navDeepLink<MapRoutes.Map>(basePath = "$DEEP_LINK_BASE_URI/map"))) {
|
||||
val viewModel = hiltViewModel<AndroidSharedMapViewModel>()
|
||||
val viewModel = koinViewModel<AndroidSharedMapViewModel>()
|
||||
MapScreen(
|
||||
viewModel = viewModel,
|
||||
onClickNodeChip = {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import androidx.compose.material.icons.rounded.Router
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
|
|
@ -40,8 +39,10 @@ import androidx.navigation.navDeepLink
|
|||
import androidx.navigation.toRoute
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.map.node.NodeMapScreen
|
||||
import org.meshtastic.app.map.node.NodeMapViewModel
|
||||
import org.meshtastic.app.node.AndroidMetricsViewModel
|
||||
import org.meshtastic.app.ui.node.AdaptiveNodeListScreen
|
||||
import org.meshtastic.core.navigation.ContactsRoutes
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
|
|
@ -120,7 +121,7 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, scrollToTo
|
|||
) { backStackEntry ->
|
||||
val parentGraphBackStackEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes.NodeDetailGraph::class) }
|
||||
val vm = hiltViewModel<NodeMapViewModel>(parentGraphBackStackEntry)
|
||||
val vm = koinViewModel<NodeMapViewModel>(viewModelStoreOwner = parentGraphBackStackEntry)
|
||||
NodeMapScreen(vm, onNavigateUp = navController::navigateUp)
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +136,8 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, scrollToTo
|
|||
) { backStackEntry ->
|
||||
val parentGraphBackStackEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes.NodeDetailGraph::class) }
|
||||
val metricsViewModel = hiltViewModel<MetricsViewModel>(parentGraphBackStackEntry)
|
||||
val metricsViewModel =
|
||||
koinViewModel<AndroidMetricsViewModel>(viewModelStoreOwner = parentGraphBackStackEntry)
|
||||
|
||||
val args = backStackEntry.toRoute<NodeDetailRoutes.TracerouteLog>()
|
||||
metricsViewModel.setNodeId(args.destNum)
|
||||
|
|
@ -166,7 +168,8 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, scrollToTo
|
|||
) { backStackEntry ->
|
||||
val parentGraphBackStackEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes.NodeDetailGraph::class) }
|
||||
val metricsViewModel = hiltViewModel<MetricsViewModel>(parentGraphBackStackEntry)
|
||||
val metricsViewModel =
|
||||
koinViewModel<AndroidMetricsViewModel>(viewModelStoreOwner = parentGraphBackStackEntry)
|
||||
|
||||
val args = backStackEntry.toRoute<NodeDetailRoutes.TracerouteMap>()
|
||||
metricsViewModel.setNodeId(args.destNum)
|
||||
|
|
@ -277,7 +280,7 @@ private inline fun <reified R : Route> NavGraphBuilder.addNodeDetailScreenCompos
|
|||
) { backStackEntry ->
|
||||
val parentGraphBackStackEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes.NodeDetailGraph::class) }
|
||||
val metricsViewModel = hiltViewModel<MetricsViewModel>(parentGraphBackStackEntry)
|
||||
val metricsViewModel = koinViewModel<AndroidMetricsViewModel>(viewModelStoreOwner = parentGraphBackStackEntry)
|
||||
|
||||
val args = backStackEntry.toRoute<R>()
|
||||
val destNum = getDestNum(args)
|
||||
|
|
|
|||
|
|
@ -22,13 +22,18 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import androidx.navigation.navigation
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.app.settings.AndroidCleanNodeDatabaseViewModel
|
||||
import org.meshtastic.app.settings.AndroidDebugViewModel
|
||||
import org.meshtastic.app.settings.AndroidFilterSettingsViewModel
|
||||
import org.meshtastic.app.settings.AndroidRadioConfigViewModel
|
||||
import org.meshtastic.app.settings.AndroidSettingsViewModel
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.navigation.Graph
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
|
|
@ -39,13 +44,11 @@ import org.meshtastic.feature.settings.AdministrationScreen
|
|||
import org.meshtastic.feature.settings.DeviceConfigurationScreen
|
||||
import org.meshtastic.feature.settings.ModuleConfigurationScreen
|
||||
import org.meshtastic.feature.settings.SettingsScreen
|
||||
import org.meshtastic.feature.settings.SettingsViewModel
|
||||
import org.meshtastic.feature.settings.debugging.DebugScreen
|
||||
import org.meshtastic.feature.settings.filter.FilterSettingsScreen
|
||||
import org.meshtastic.feature.settings.navigation.ConfigRoute
|
||||
import org.meshtastic.feature.settings.navigation.ModuleRoute
|
||||
import org.meshtastic.feature.settings.radio.CleanNodeDatabaseScreen
|
||||
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.feature.settings.radio.channel.ChannelConfigScreen
|
||||
import org.meshtastic.feature.settings.radio.component.AmbientLightingConfigScreen
|
||||
import org.meshtastic.feature.settings.radio.component.AudioConfigScreen
|
||||
|
|
@ -83,8 +86,8 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
|
|||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
SettingsScreen(
|
||||
settingsViewModel = hiltViewModel(parentEntry),
|
||||
viewModel = hiltViewModel(parentEntry),
|
||||
settingsViewModel = koinViewModel<AndroidSettingsViewModel>(viewModelStoreOwner = parentEntry),
|
||||
viewModel = koinViewModel<AndroidRadioConfigViewModel>(viewModelStoreOwner = parentEntry),
|
||||
onClickNodeChip = {
|
||||
navController.navigate(NodesRoutes.NodeDetailGraph(it)) {
|
||||
launchSingleTop = true
|
||||
|
|
@ -100,7 +103,7 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
|
|||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
DeviceConfigurationScreen(
|
||||
viewModel = hiltViewModel(parentEntry),
|
||||
viewModel = koinViewModel<AndroidRadioConfigViewModel>(viewModelStoreOwner = parentEntry),
|
||||
onBack = navController::popBackStack,
|
||||
onNavigate = { route -> navController.navigate(route) },
|
||||
)
|
||||
|
|
@ -109,10 +112,10 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
|
|||
composable<SettingsRoutes.ModuleConfiguration> { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel(parentEntry)
|
||||
val settingsViewModel: AndroidSettingsViewModel = koinViewModel(viewModelStoreOwner = parentEntry)
|
||||
val excludedModulesUnlocked by settingsViewModel.excludedModulesUnlocked.collectAsStateWithLifecycle()
|
||||
ModuleConfigurationScreen(
|
||||
viewModel = hiltViewModel(parentEntry),
|
||||
viewModel = koinViewModel<AndroidRadioConfigViewModel>(viewModelStoreOwner = parentEntry),
|
||||
excludedModulesUnlocked = excludedModulesUnlocked,
|
||||
onBack = navController::popBackStack,
|
||||
onNavigate = { route -> navController.navigate(route) },
|
||||
|
|
@ -122,7 +125,10 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
|
|||
composable<SettingsRoutes.Administration> { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
AdministrationScreen(viewModel = hiltViewModel(parentEntry), onBack = navController::popBackStack)
|
||||
AdministrationScreen(
|
||||
viewModel = koinViewModel<AndroidRadioConfigViewModel>(viewModelStoreOwner = parentEntry),
|
||||
onBack = navController::popBackStack,
|
||||
)
|
||||
}
|
||||
|
||||
composable<SettingsRoutes.CleanNodeDb>(
|
||||
|
|
@ -133,7 +139,8 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
|
|||
),
|
||||
),
|
||||
) {
|
||||
CleanNodeDatabaseScreen()
|
||||
val viewModel: AndroidCleanNodeDatabaseViewModel = koinViewModel()
|
||||
CleanNodeDatabaseScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
ConfigRoute.entries.forEach { entry ->
|
||||
|
|
@ -221,18 +228,22 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
|
|||
deepLinks =
|
||||
listOf(navDeepLink<SettingsRoutes.DebugPanel>(basePath = "$DEEP_LINK_BASE_URI/settings/debug_panel")),
|
||||
) {
|
||||
DebugScreen(onNavigateUp = navController::navigateUp)
|
||||
val viewModel: AndroidDebugViewModel = koinViewModel()
|
||||
DebugScreen(viewModel = viewModel, onNavigateUp = navController::navigateUp)
|
||||
}
|
||||
|
||||
composable<SettingsRoutes.About> { AboutScreen(onNavigateUp = navController::navigateUp) }
|
||||
|
||||
composable<SettingsRoutes.FilterSettings> { FilterSettingsScreen(onBack = navController::navigateUp) }
|
||||
composable<SettingsRoutes.FilterSettings> {
|
||||
val viewModel: AndroidFilterSettingsViewModel = koinViewModel()
|
||||
FilterSettingsScreen(viewModel = viewModel, onBack = navController::navigateUp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context(_: NavGraphBuilder)
|
||||
inline fun <reified R : Route, reified G : Graph> NavHostController.configComposable(
|
||||
noinline content: @Composable (RadioConfigViewModel) -> Unit,
|
||||
noinline content: @Composable (AndroidRadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
configComposable(route = R::class, parentGraphRoute = G::class, content = content)
|
||||
}
|
||||
|
|
@ -241,10 +252,10 @@ context(navGraphBuilder: NavGraphBuilder)
|
|||
fun <R : Route, G : Graph> NavHostController.configComposable(
|
||||
route: KClass<R>,
|
||||
parentGraphRoute: KClass<G>,
|
||||
content: @Composable (RadioConfigViewModel) -> Unit,
|
||||
content: @Composable (AndroidRadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
navGraphBuilder.composable(route = route) { backStackEntry ->
|
||||
val parentEntry = remember(backStackEntry) { getBackStackEntry(parentGraphRoute) }
|
||||
content(hiltViewModel(parentEntry))
|
||||
content(koinViewModel<AndroidRadioConfigViewModel>(viewModelStoreOwner = parentEntry))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,28 +14,19 @@
|
|||
* 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.app.di
|
||||
package org.meshtastic.app.node
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.WorkManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import javax.inject.Singleton
|
||||
import org.meshtastic.feature.node.compass.CompassHeadingProvider
|
||||
import org.meshtastic.feature.node.compass.CompassViewModel
|
||||
import org.meshtastic.feature.node.compass.MagneticFieldProvider
|
||||
import org.meshtastic.feature.node.compass.PhoneLocationProvider
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AppModule {
|
||||
|
||||
@Provides
|
||||
fun provideCoroutineDispatchers(): CoroutineDispatchers =
|
||||
CoroutineDispatchers(io = Dispatchers.IO, main = Dispatchers.Main, default = Dispatchers.Default)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWorkManager(@ApplicationContext context: Context): WorkManager = WorkManager.getInstance(context)
|
||||
}
|
||||
@KoinViewModel
|
||||
class AndroidCompassViewModel(
|
||||
headingProvider: CompassHeadingProvider,
|
||||
locationProvider: PhoneLocationProvider,
|
||||
magneticFieldProvider: MagneticFieldProvider,
|
||||
dispatchers: CoroutineDispatchers,
|
||||
) : CompassViewModel(headingProvider, locationProvider, magneticFieldProvider, dispatchers)
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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.app.node
|
||||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.toRoute
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.common.util.toDate
|
||||
import org.meshtastic.core.common.util.toInstant
|
||||
import org.meshtastic.core.data.repository.TracerouteSnapshotRepository
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.repository.MeshLogRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
import org.meshtastic.feature.node.detail.NodeRequestActions
|
||||
import org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
|
||||
import org.meshtastic.feature.node.metrics.MetricsViewModel
|
||||
import java.io.BufferedWriter
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@KoinViewModel
|
||||
class AndroidMetricsViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val app: Application,
|
||||
dispatchers: CoroutineDispatchers,
|
||||
meshLogRepository: MeshLogRepository,
|
||||
serviceRepository: ServiceRepository,
|
||||
nodeRepository: NodeRepository,
|
||||
tracerouteSnapshotRepository: TracerouteSnapshotRepository,
|
||||
nodeRequestActions: NodeRequestActions,
|
||||
alertManager: AlertManager,
|
||||
getNodeDetailsUseCase: GetNodeDetailsUseCase,
|
||||
) : MetricsViewModel(
|
||||
savedStateHandle.toRoute<NodesRoutes.NodeDetailGraph>().destNum ?: 0,
|
||||
dispatchers,
|
||||
meshLogRepository,
|
||||
serviceRepository,
|
||||
nodeRepository,
|
||||
tracerouteSnapshotRepository,
|
||||
nodeRequestActions,
|
||||
alertManager,
|
||||
getNodeDetailsUseCase,
|
||||
) {
|
||||
override fun savePositionCSV(uri: Any) {
|
||||
if (uri is Uri) {
|
||||
savePositionCSVAndroid(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun savePositionCSVAndroid(uri: Uri) = viewModelScope.launch(dispatchers.main) {
|
||||
val positions = state.value.positionLogs
|
||||
writeToUri(uri) { writer ->
|
||||
writer.appendLine(
|
||||
"\"date\",\"time\",\"latitude\",\"longitude\",\"altitude\",\"satsInView\",\"speed\",\"heading\"",
|
||||
)
|
||||
|
||||
val dateFormat = SimpleDateFormat("\"yyyy-MM-dd\",\"HH:mm:ss\"", Locale.getDefault())
|
||||
|
||||
positions.forEach { position ->
|
||||
val rxDateTime = dateFormat.format((position.time.toLong() * 1000L).toInstant().toDate())
|
||||
val latitude = (position.latitude_i ?: 0) * 1e-7
|
||||
val longitude = (position.longitude_i ?: 0) * 1e-7
|
||||
val altitude = position.altitude
|
||||
val satsInView = position.sats_in_view
|
||||
val speed = position.ground_speed
|
||||
val heading = "%.2f".format((position.ground_track ?: 0) * 1e-5)
|
||||
|
||||
writer.appendLine(
|
||||
"$rxDateTime,\"$latitude\",\"$longitude\",\"$altitude\",\"$satsInView\",\"$speed\",\"$heading\"",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun writeToUri(uri: Uri, crossinline block: suspend (BufferedWriter) -> Unit) =
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
app.contentResolver.openFileDescriptor(uri, "wt")?.use { parcelFileDescriptor ->
|
||||
FileWriter(parcelFileDescriptor.fileDescriptor).use { fileWriter ->
|
||||
BufferedWriter(fileWriter).use { writer -> block.invoke(writer) }
|
||||
}
|
||||
}
|
||||
} catch (ex: FileNotFoundException) {
|
||||
Logger.e(ex) { "Can't write file error" }
|
||||
}
|
||||
}
|
||||
|
||||
override fun decodeBase64(base64: String): ByteArray =
|
||||
android.util.Base64.decode(base64, android.util.Base64.DEFAULT)
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.app.node
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.feature.node.detail.NodeDetailViewModel
|
||||
import org.meshtastic.feature.node.detail.NodeManagementActions
|
||||
import org.meshtastic.feature.node.detail.NodeRequestActions
|
||||
import org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
|
||||
|
||||
@KoinViewModel
|
||||
class AndroidNodeDetailViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
nodeManagementActions: NodeManagementActions,
|
||||
nodeRequestActions: NodeRequestActions,
|
||||
serviceRepository: ServiceRepository,
|
||||
getNodeDetailsUseCase: GetNodeDetailsUseCase,
|
||||
) : NodeDetailViewModel(
|
||||
savedStateHandle,
|
||||
nodeManagementActions,
|
||||
nodeRequestActions,
|
||||
serviceRepository,
|
||||
getNodeDetailsUseCase,
|
||||
)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.app.node
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.feature.node.detail.NodeManagementActions
|
||||
import org.meshtastic.feature.node.domain.usecase.GetFilteredNodesUseCase
|
||||
import org.meshtastic.feature.node.list.NodeFilterPreferences
|
||||
import org.meshtastic.feature.node.list.NodeListViewModel
|
||||
|
||||
@KoinViewModel
|
||||
class AndroidNodeListViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
nodeRepository: NodeRepository,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
serviceRepository: ServiceRepository,
|
||||
radioController: RadioController,
|
||||
nodeManagementActions: NodeManagementActions,
|
||||
getFilteredNodesUseCase: GetFilteredNodesUseCase,
|
||||
nodeFilterPreferences: NodeFilterPreferences,
|
||||
) : NodeListViewModel(
|
||||
savedStateHandle,
|
||||
nodeRepository,
|
||||
radioConfigRepository,
|
||||
serviceRepository,
|
||||
radioController,
|
||||
nodeManagementActions,
|
||||
getFilteredNodesUseCase,
|
||||
nodeFilterPreferences,
|
||||
)
|
||||
|
|
@ -27,24 +27,20 @@ import kotlinx.coroutines.flow.conflate
|
|||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.di.ProcessLifecycle
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NetworkRepository
|
||||
@Inject
|
||||
constructor(
|
||||
private val nsdManagerLazy: dagger.Lazy<NsdManager>,
|
||||
private val connectivityManager: dagger.Lazy<ConnectivityManager>,
|
||||
@Single
|
||||
class NetworkRepository(
|
||||
private val nsdManager: NsdManager,
|
||||
private val connectivityManager: ConnectivityManager,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
@ProcessLifecycle private val processLifecycle: Lifecycle,
|
||||
@Named("ProcessLifecycle") private val processLifecycle: Lifecycle,
|
||||
) {
|
||||
|
||||
val networkAvailable: Flow<Boolean> by lazy {
|
||||
connectivityManager
|
||||
.get()
|
||||
.networkAvailable()
|
||||
.flowOn(dispatchers.io)
|
||||
.conflate()
|
||||
|
|
@ -57,8 +53,7 @@ constructor(
|
|||
}
|
||||
|
||||
val resolvedList: Flow<List<NsdServiceInfo>> by lazy {
|
||||
nsdManagerLazy
|
||||
.get()
|
||||
nsdManager
|
||||
.serviceList(SERVICE_TYPE)
|
||||
.flowOn(dispatchers.io)
|
||||
.conflate()
|
||||
|
|
|
|||
|
|
@ -1,40 +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.app.repository.network
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.nsd.NsdManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class NetworkRepositoryModule {
|
||||
companion object {
|
||||
@Provides
|
||||
fun provideConnectivityManager(application: Application): ConnectivityManager =
|
||||
application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
@Provides
|
||||
fun provideNsdManager(application: Application): NsdManager =
|
||||
application.getSystemService(Context.NSD_SERVICE) as NsdManager
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,8 @@ import kotlinx.coroutines.flow.catch
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.BuildConfig
|
||||
import org.meshtastic.app.repository.network.NetworkRepository
|
||||
import org.meshtastic.core.ble.BluetoothRepository
|
||||
|
|
@ -44,7 +46,6 @@ import org.meshtastic.core.common.util.ignoreException
|
|||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.common.util.toRemoteExceptions
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.di.ProcessLifecycle
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.InterfaceId
|
||||
import org.meshtastic.core.model.MeshActivity
|
||||
|
|
@ -54,8 +55,6 @@ import org.meshtastic.core.repository.RadioInterfaceService
|
|||
import org.meshtastic.core.repository.RadioPrefs
|
||||
import org.meshtastic.proto.Heartbeat
|
||||
import org.meshtastic.proto.ToRadio
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Handles the bluetooth link with a mesh radio device. Does not cache any device state, just does bluetooth comms
|
||||
|
|
@ -67,17 +66,15 @@ import javax.inject.Singleton
|
|||
* can be stubbed out with a simulated version as needed.
|
||||
*/
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@Singleton
|
||||
class AndroidRadioInterfaceService
|
||||
@Inject
|
||||
constructor(
|
||||
@Single
|
||||
class AndroidRadioInterfaceService(
|
||||
private val context: Application,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
private val networkRepository: NetworkRepository,
|
||||
@ProcessLifecycle private val processLifecycle: Lifecycle,
|
||||
@Named("ProcessLifecycle") private val processLifecycle: Lifecycle,
|
||||
private val radioPrefs: RadioPrefs,
|
||||
private val interfaceFactory: InterfaceFactory,
|
||||
private val interfaceFactory: Lazy<InterfaceFactory>,
|
||||
private val analytics: PlatformAnalytics,
|
||||
) : RadioInterfaceService {
|
||||
|
||||
|
|
@ -179,7 +176,7 @@ constructor(
|
|||
|
||||
/** Constructs a full radio address for the specific interface type. */
|
||||
override fun toInterfaceAddress(interfaceId: InterfaceId, rest: String): String =
|
||||
interfaceFactory.toInterfaceAddress(interfaceId, rest)
|
||||
interfaceFactory.value.toInterfaceAddress(interfaceId, rest)
|
||||
|
||||
override fun isMockInterface(): Boolean =
|
||||
BuildConfig.DEBUG || Settings.System.getString(context.contentResolver, "firebase.test.lab") == "true"
|
||||
|
|
@ -200,7 +197,7 @@ constructor(
|
|||
fun getBondedDeviceAddress(): String? {
|
||||
// If the user has unpaired our device, treat things as if we don't have one
|
||||
val address = getDeviceAddress()
|
||||
return if (interfaceFactory.addressValid(address)) {
|
||||
return if (interfaceFactory.value.addressValid(address)) {
|
||||
address
|
||||
} else {
|
||||
null
|
||||
|
|
@ -259,24 +256,32 @@ constructor(
|
|||
if (radioIf !is NopInterface) {
|
||||
// Already running
|
||||
return
|
||||
}
|
||||
|
||||
val isTestLab = Settings.System.getString(context.contentResolver, "firebase.test.lab") == "true"
|
||||
val address =
|
||||
getBondedDeviceAddress()
|
||||
?: if (isTestLab) {
|
||||
mockInterfaceAddress
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (address == null) {
|
||||
Logger.w { "No bonded mesh radio, can't start interface" }
|
||||
} else {
|
||||
val address = getBondedDeviceAddress()
|
||||
if (address == null) {
|
||||
Logger.w { "No bonded mesh radio, can't start interface" }
|
||||
} else {
|
||||
Logger.i { "Starting radio ${address.anonymize}" }
|
||||
isStarted = true
|
||||
Logger.i { "Starting radio ${address.anonymize}" }
|
||||
isStarted = true
|
||||
|
||||
if (logSends) {
|
||||
sentPacketsLog = BinaryLogFile(context, "sent_log.pb")
|
||||
}
|
||||
if (logReceives) {
|
||||
receivedPacketsLog = BinaryLogFile(context, "receive_log.pb")
|
||||
}
|
||||
|
||||
radioIf = interfaceFactory.createInterface(address)
|
||||
startHeartbeat()
|
||||
if (logSends) {
|
||||
sentPacketsLog = BinaryLogFile(context, "sent_log.pb")
|
||||
}
|
||||
if (logReceives) {
|
||||
receivedPacketsLog = BinaryLogFile(context, "receive_log.pb")
|
||||
}
|
||||
|
||||
radioIf = interfaceFactory.value.createInterface(address, this)
|
||||
startHeartbeat()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +302,7 @@ constructor(
|
|||
val r = radioIf
|
||||
Logger.i { "stopping interface $r" }
|
||||
isStarted = false
|
||||
radioIf = interfaceFactory.nopInterface
|
||||
radioIf = interfaceFactory.value.nopInterface
|
||||
r.close()
|
||||
|
||||
// cancel any old jobs and get ready for the new ones
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.model.InterfaceId
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/**
|
||||
* Entry point for create radio backend instances given a specific address.
|
||||
|
|
@ -26,19 +26,31 @@ import javax.inject.Provider
|
|||
* This class is responsible for building and dissecting radio addresses based upon their interface type and the "rest"
|
||||
* of the address (which varies per implementation).
|
||||
*/
|
||||
class InterfaceFactory
|
||||
@Inject
|
||||
constructor(
|
||||
@Single
|
||||
class InterfaceFactory(
|
||||
private val nopInterfaceFactory: NopInterfaceFactory,
|
||||
private val specMap: Map<InterfaceId, @JvmSuppressWildcards Provider<InterfaceSpec<*>>>,
|
||||
private val bluetoothSpec: Lazy<NordicBleInterfaceSpec>,
|
||||
private val mockSpec: Lazy<MockInterfaceSpec>,
|
||||
private val serialSpec: Lazy<SerialInterfaceSpec>,
|
||||
private val tcpSpec: Lazy<TCPInterfaceSpec>,
|
||||
) {
|
||||
internal val nopInterface by lazy { nopInterfaceFactory.create("") }
|
||||
|
||||
private val specMap: Map<InterfaceId, InterfaceSpec<*>>
|
||||
get() =
|
||||
mapOf(
|
||||
InterfaceId.BLUETOOTH to bluetoothSpec.value,
|
||||
InterfaceId.MOCK to mockSpec.value,
|
||||
InterfaceId.NOP to NopInterfaceSpec(nopInterfaceFactory),
|
||||
InterfaceId.SERIAL to serialSpec.value,
|
||||
InterfaceId.TCP to tcpSpec.value,
|
||||
)
|
||||
|
||||
fun toInterfaceAddress(interfaceId: InterfaceId, rest: String): String = "${interfaceId.id}$rest"
|
||||
|
||||
fun createInterface(address: String): IRadioInterface {
|
||||
fun createInterface(address: String, service: RadioInterfaceService): IRadioInterface {
|
||||
val (spec, rest) = splitAddress(address)
|
||||
return spec?.createInterface(rest) ?: nopInterface
|
||||
return spec?.createInterface(rest, service) ?: nopInterface
|
||||
}
|
||||
|
||||
fun addressValid(address: String?): Boolean = address?.let {
|
||||
|
|
@ -47,7 +59,7 @@ constructor(
|
|||
} ?: false
|
||||
|
||||
private fun splitAddress(address: String): Pair<InterfaceSpec<*>?, String> {
|
||||
val c = address[0].let { InterfaceId.forIdChar(it) }?.let { specMap[it]?.get() }
|
||||
val c = address[0].let { InterfaceId.forIdChar(it) }?.let { specMap[it] }
|
||||
val rest = address.substring(1)
|
||||
return Pair(c, rest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** This interface defines the contract that all radio backend implementations must adhere to. */
|
||||
interface InterfaceSpec<T : IRadioInterface> {
|
||||
fun createInterface(rest: String): T
|
||||
fun createInterface(rest: String, service: RadioInterfaceService): T
|
||||
|
||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||
fun addressValid(rest: String): Boolean = true
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.delay
|
||||
import okio.ByteString.Companion.encodeUtf8
|
||||
import okio.ByteString.Companion.toByteString
|
||||
|
|
@ -58,12 +56,7 @@ private val defaultChannel = ProtoChannel(settings = Channel.default.settings, r
|
|||
|
||||
/** A simulated interface that is used for testing in the simulator */
|
||||
@Suppress("detekt:TooManyFunctions", "detekt:MagicNumber")
|
||||
class MockInterface
|
||||
@AssistedInject
|
||||
constructor(
|
||||
private val service: RadioInterfaceService,
|
||||
@Assisted val address: String,
|
||||
) : IRadioInterface {
|
||||
class MockInterface(private val service: RadioInterfaceService, val address: String) : IRadioInterface {
|
||||
|
||||
companion object {
|
||||
private const val MY_NODE = 0x42424242
|
||||
|
|
|
|||
|
|
@ -16,10 +16,11 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import dagger.assisted.AssistedFactory
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** Factory for creating `MockInterface` instances. */
|
||||
@AssistedFactory
|
||||
interface MockInterfaceFactory {
|
||||
fun create(rest: String): MockInterface
|
||||
@Single
|
||||
class MockInterfaceFactory {
|
||||
fun create(rest: String, service: RadioInterfaceService): MockInterface = MockInterface(service, rest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import javax.inject.Inject
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** Mock interface backend implementation. */
|
||||
class MockInterfaceSpec @Inject constructor(private val factory: MockInterfaceFactory) : InterfaceSpec<MockInterface> {
|
||||
override fun createInterface(rest: String): MockInterface = factory.create(rest)
|
||||
@Single
|
||||
class MockInterfaceSpec(private val factory: MockInterfaceFactory) : InterfaceSpec<MockInterface> {
|
||||
override fun createInterface(rest: String, service: RadioInterfaceService): MockInterface =
|
||||
factory.create(rest, service)
|
||||
|
||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||
override fun addressValid(rest: String): Boolean = true
|
||||
|
|
|
|||
|
|
@ -16,10 +16,7 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
class NopInterface @AssistedInject constructor(@Assisted val address: String) : IRadioInterface {
|
||||
class NopInterface(val address: String) : IRadioInterface {
|
||||
override fun handleSendToRadio(p: ByteArray) {
|
||||
// No-op
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import dagger.assisted.AssistedFactory
|
||||
import org.koin.core.annotation.Single
|
||||
|
||||
/** Factory for creating `NopInterface` instances. */
|
||||
@AssistedFactory
|
||||
interface NopInterfaceFactory {
|
||||
fun create(rest: String): NopInterface
|
||||
@Single
|
||||
class NopInterfaceFactory {
|
||||
fun create(rest: String): NopInterface = NopInterface(rest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import javax.inject.Inject
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** No-op interface backend implementation. */
|
||||
class NopInterfaceSpec @Inject constructor(private val factory: NopInterfaceFactory) : InterfaceSpec<NopInterface> {
|
||||
override fun createInterface(rest: String): NopInterface = factory.create(rest)
|
||||
@Single
|
||||
class NopInterfaceSpec(private val factory: NopInterfaceFactory) : InterfaceSpec<NopInterface> {
|
||||
override fun createInterface(rest: String, service: RadioInterfaceService): NopInterface = factory.create(rest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ package org.meshtastic.app.repository.radio
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
|
@ -72,15 +70,13 @@ private val SCAN_TIMEOUT = 5.seconds
|
|||
* @param address The BLE address of the device to connect to.
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
class NordicBleInterface
|
||||
@AssistedInject
|
||||
constructor(
|
||||
class NordicBleInterface(
|
||||
private val serviceScope: CoroutineScope,
|
||||
private val scanner: BleScanner,
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
private val connectionFactory: BleConnectionFactory,
|
||||
private val service: RadioInterfaceService,
|
||||
@Assisted val address: String,
|
||||
val address: String,
|
||||
) : IRadioInterface {
|
||||
|
||||
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||
|
|
|
|||
|
|
@ -16,10 +16,25 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import dagger.assisted.AssistedFactory
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.ble.BleConnectionFactory
|
||||
import org.meshtastic.core.ble.BleScanner
|
||||
import org.meshtastic.core.ble.BluetoothRepository
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** Factory for creating `NordicBleInterface` instances. */
|
||||
@AssistedFactory
|
||||
interface NordicBleInterfaceFactory {
|
||||
fun create(rest: String): NordicBleInterface
|
||||
@Single
|
||||
class NordicBleInterfaceFactory(
|
||||
private val scanner: BleScanner,
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
private val connectionFactory: BleConnectionFactory,
|
||||
) {
|
||||
fun create(rest: String, service: RadioInterfaceService): NordicBleInterface = NordicBleInterface(
|
||||
serviceScope = service.serviceScope,
|
||||
scanner = scanner,
|
||||
bluetoothRepository = bluetoothRepository,
|
||||
connectionFactory = connectionFactory,
|
||||
service = service,
|
||||
address = rest,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,18 +17,19 @@
|
|||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.ble.BluetoothRepository
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
import javax.inject.Inject
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** Bluetooth backend implementation. */
|
||||
class NordicBleInterfaceSpec
|
||||
@Inject
|
||||
constructor(
|
||||
@Single
|
||||
class NordicBleInterfaceSpec(
|
||||
private val factory: NordicBleInterfaceFactory,
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
) : InterfaceSpec<NordicBleInterface> {
|
||||
override fun createInterface(rest: String): NordicBleInterface = factory.create(rest)
|
||||
override fun createInterface(rest: String, service: RadioInterfaceService): NordicBleInterface =
|
||||
factory.create(rest, service)
|
||||
|
||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||
override fun addressValid(rest: String): Boolean = if (!bluetoothRepository.isBonded(rest)) {
|
||||
|
|
|
|||
|
|
@ -1,48 +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.app.repository.radio
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoMap
|
||||
import dagger.multibindings.Multibinds
|
||||
import org.meshtastic.core.model.InterfaceId
|
||||
|
||||
@Suppress("unused") // Used by hilt
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class RadioRepositoryModule {
|
||||
|
||||
@Multibinds abstract fun interfaceMap(): Map<InterfaceId, @JvmSuppressWildcards InterfaceSpec<*>>
|
||||
|
||||
@[Binds IntoMap InterfaceMapKey(InterfaceId.BLUETOOTH)]
|
||||
abstract fun bindBluetoothInterfaceSpec(spec: NordicBleInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||
|
||||
@[Binds IntoMap InterfaceMapKey(InterfaceId.MOCK)]
|
||||
abstract fun bindMockInterfaceSpec(spec: MockInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||
|
||||
@[Binds IntoMap InterfaceMapKey(InterfaceId.NOP)]
|
||||
abstract fun bindNopInterfaceSpec(spec: NopInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||
|
||||
@[Binds IntoMap InterfaceMapKey(InterfaceId.SERIAL)]
|
||||
abstract fun bindSerialInterfaceSpec(spec: SerialInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||
|
||||
@[Binds IntoMap InterfaceMapKey(InterfaceId.TCP)]
|
||||
abstract fun bindTCPInterfaceSpec(spec: TCPInterfaceSpec): @JvmSuppressWildcards InterfaceSpec<*>
|
||||
}
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.meshtastic.app.repository.usb.SerialConnection
|
||||
import org.meshtastic.app.repository.usb.SerialConnectionListener
|
||||
import org.meshtastic.app.repository.usb.UsbRepository
|
||||
|
|
@ -27,13 +25,10 @@ import org.meshtastic.core.repository.RadioInterfaceService
|
|||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/** An interface that assumes we are talking to a meshtastic device via USB serial */
|
||||
class SerialInterface
|
||||
@AssistedInject
|
||||
constructor(
|
||||
class SerialInterface(
|
||||
service: RadioInterfaceService,
|
||||
private val serialInterfaceSpec: SerialInterfaceSpec,
|
||||
private val usbRepository: UsbRepository,
|
||||
@Assisted private val address: String,
|
||||
private val address: String,
|
||||
) : StreamInterface(service) {
|
||||
private var connRef = AtomicReference<SerialConnection?>()
|
||||
|
||||
|
|
@ -47,7 +42,13 @@ constructor(
|
|||
}
|
||||
|
||||
override fun connect() {
|
||||
val device = serialInterfaceSpec.findSerial(address)
|
||||
val deviceMap = usbRepository.serialDevices.value
|
||||
val device =
|
||||
if (deviceMap.containsKey(address)) {
|
||||
deviceMap[address]!!
|
||||
} else {
|
||||
deviceMap.map { (_, driver) -> driver }.firstOrNull()
|
||||
}
|
||||
if (device == null) {
|
||||
Logger.e { "[$address] Serial device not found at address" }
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -16,10 +16,13 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import dagger.assisted.AssistedFactory
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.repository.usb.UsbRepository
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** Factory for creating `SerialInterface` instances. */
|
||||
@AssistedFactory
|
||||
interface SerialInterfaceFactory {
|
||||
fun create(rest: String): SerialInterface
|
||||
@Single
|
||||
class SerialInterfaceFactory(private val usbRepository: UsbRepository) {
|
||||
fun create(rest: String, service: RadioInterfaceService): SerialInterface =
|
||||
SerialInterface(service, usbRepository, rest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,23 +18,24 @@ package org.meshtastic.app.repository.radio
|
|||
|
||||
import android.hardware.usb.UsbManager
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.repository.usb.UsbRepository
|
||||
import javax.inject.Inject
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** Serial/USB interface backend implementation. */
|
||||
class SerialInterfaceSpec
|
||||
@Inject
|
||||
constructor(
|
||||
@Single
|
||||
class SerialInterfaceSpec(
|
||||
private val factory: SerialInterfaceFactory,
|
||||
private val usbManager: dagger.Lazy<UsbManager>,
|
||||
private val usbManager: UsbManager,
|
||||
private val usbRepository: UsbRepository,
|
||||
) : InterfaceSpec<SerialInterface> {
|
||||
override fun createInterface(rest: String): SerialInterface = factory.create(rest)
|
||||
override fun createInterface(rest: String, service: RadioInterfaceService): SerialInterface =
|
||||
factory.create(rest, service)
|
||||
|
||||
override fun addressValid(rest: String): Boolean {
|
||||
usbRepository.serialDevices.value.filterValues { usbManager.get().hasPermission(it.device) }
|
||||
usbRepository.serialDevices.value.filterValues { usbManager.hasPermission(it.device) }
|
||||
findSerial(rest)?.let { d ->
|
||||
return usbManager.get().hasPermission(d.device)
|
||||
return usbManager.hasPermission(d.device)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.app.repository.network.NetworkRepository
|
||||
|
|
@ -37,12 +35,10 @@ import java.net.InetAddress
|
|||
import java.net.Socket
|
||||
import java.net.SocketTimeoutException
|
||||
|
||||
open class TCPInterface
|
||||
@AssistedInject
|
||||
constructor(
|
||||
open class TCPInterface(
|
||||
service: RadioInterfaceService,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
@Assisted private val address: String,
|
||||
private val address: String,
|
||||
) : StreamInterface(service) {
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import dagger.assisted.AssistedFactory
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** Factory for creating `TCPInterface` instances. */
|
||||
@AssistedFactory
|
||||
interface TCPInterfaceFactory {
|
||||
fun create(rest: String): TCPInterface
|
||||
@Single
|
||||
class TCPInterfaceFactory(private val dispatchers: CoroutineDispatchers) {
|
||||
fun create(rest: String, service: RadioInterfaceService): TCPInterface = TCPInterface(service, dispatchers, rest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,12 @@
|
|||
*/
|
||||
package org.meshtastic.app.repository.radio
|
||||
|
||||
import javax.inject.Inject
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
|
||||
/** TCP interface backend implementation. */
|
||||
class TCPInterfaceSpec @Inject constructor(private val factory: TCPInterfaceFactory) : InterfaceSpec<TCPInterface> {
|
||||
override fun createInterface(rest: String): TCPInterface = factory.create(rest)
|
||||
@Single
|
||||
class TCPInterfaceSpec(private val factory: TCPInterfaceFactory) : InterfaceSpec<TCPInterface> {
|
||||
override fun createInterface(rest: String, service: RadioInterfaceService): TCPInterface =
|
||||
factory.create(rest, service)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,17 +19,15 @@ package org.meshtastic.app.repository.usb
|
|||
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver
|
||||
import com.hoho.android.usbserial.driver.ProbeTable
|
||||
import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||
import dagger.Reusable
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import org.koin.core.annotation.Single
|
||||
|
||||
/**
|
||||
* Creates a probe table for the USB driver. This augments the default device-to-driver mappings with additional known
|
||||
* working configurations. See this package's README for more info.
|
||||
*/
|
||||
@Reusable
|
||||
class ProbeTableProvider @Inject constructor() : Provider<ProbeTable> {
|
||||
override fun get(): ProbeTable = UsbSerialProber.getDefaultProbeTable().apply {
|
||||
@Single
|
||||
class ProbeTableProvider {
|
||||
fun get(): ProbeTable = UsbSerialProber.getDefaultProbeTable().apply {
|
||||
// RAK 4631:
|
||||
addProduct(9114, 32809, CdcAcmSerialDriver::class.java)
|
||||
// LilyGo TBeam v1.1:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
internal class SerialConnectionImpl(
|
||||
private val usbManagerLazy: dagger.Lazy<UsbManager?>,
|
||||
private val usbManagerLazy: Lazy<UsbManager?>,
|
||||
private val device: UsbSerialDriver,
|
||||
private val listener: SerialConnectionListener,
|
||||
) : SerialConnection {
|
||||
|
|
@ -74,7 +74,7 @@ internal class SerialConnectionImpl(
|
|||
|
||||
override fun connect() {
|
||||
// We shouldn't be able to get this far without a USB subsystem so explode if that isn't true
|
||||
val usbManager = usbManagerLazy.get()!!
|
||||
val usbManager = usbManagerLazy.value!!
|
||||
|
||||
val usbDeviceConnection = usbManager.openDevice(device.device)
|
||||
if (usbDeviceConnection == null) {
|
||||
|
|
|
|||
|
|
@ -23,12 +23,13 @@ import android.content.IntentFilter
|
|||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbManager
|
||||
import co.touchlab.kermit.Logger
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.exceptionReporter
|
||||
import org.meshtastic.core.common.util.getParcelableExtraCompat
|
||||
import javax.inject.Inject
|
||||
|
||||
/** A helper class to call onChanged when bluetooth is enabled or disabled or when permissions are changed. */
|
||||
class UsbBroadcastReceiver @Inject constructor(private val usbRepository: UsbRepository) : BroadcastReceiver() {
|
||||
@Single
|
||||
class UsbBroadcastReceiver(private val usbRepository: UsbRepository) : BroadcastReceiver() {
|
||||
// Can be used for registering
|
||||
internal val intentFilter
|
||||
get() =
|
||||
|
|
|
|||
|
|
@ -32,31 +32,28 @@ import kotlinx.coroutines.flow.mapLatest
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.registerReceiverCompat
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.di.ProcessLifecycle
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** Repository responsible for maintaining and updating the state of USB connectivity. */
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Singleton
|
||||
class UsbRepository
|
||||
@Inject
|
||||
constructor(
|
||||
@Single
|
||||
class UsbRepository(
|
||||
private val application: Application,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
@ProcessLifecycle private val processLifecycle: Lifecycle,
|
||||
private val usbBroadcastReceiverLazy: dagger.Lazy<UsbBroadcastReceiver>,
|
||||
private val usbManagerLazy: dagger.Lazy<UsbManager?>,
|
||||
private val usbSerialProberLazy: dagger.Lazy<UsbSerialProber>,
|
||||
@Named("ProcessLifecycle") private val processLifecycle: Lifecycle,
|
||||
private val usbBroadcastReceiverLazy: Lazy<UsbBroadcastReceiver>,
|
||||
private val usbManagerLazy: Lazy<UsbManager?>,
|
||||
private val usbSerialProberLazy: Lazy<UsbSerialProber>,
|
||||
) {
|
||||
private val _serialDevices = MutableStateFlow(emptyMap<String, UsbDevice>())
|
||||
|
||||
val serialDevices =
|
||||
_serialDevices
|
||||
.mapLatest { serialDevices ->
|
||||
val serialProber = usbSerialProberLazy.get()
|
||||
val serialProber = usbSerialProberLazy.value
|
||||
buildMap {
|
||||
serialDevices.forEach { (k, v) -> serialProber.probeDevice(v)?.let { driver -> put(k, driver) } }
|
||||
}
|
||||
|
|
@ -66,7 +63,7 @@ constructor(
|
|||
init {
|
||||
processLifecycle.coroutineScope.launch(dispatchers.default) {
|
||||
refreshStateInternal()
|
||||
usbBroadcastReceiverLazy.get().let { receiver ->
|
||||
usbBroadcastReceiverLazy.value.let { receiver ->
|
||||
application.registerReceiverCompat(receiver, receiver.intentFilter)
|
||||
}
|
||||
}
|
||||
|
|
@ -80,12 +77,12 @@ constructor(
|
|||
SerialConnectionImpl(usbManagerLazy, device, listener)
|
||||
|
||||
fun requestPermission(device: UsbDevice): Flow<Boolean> =
|
||||
usbManagerLazy.get()?.requestPermission(application, device) ?: emptyFlow()
|
||||
usbManagerLazy.value?.requestPermission(application, device) ?: emptyFlow()
|
||||
|
||||
fun refreshState() {
|
||||
processLifecycle.coroutineScope.launch(dispatchers.default) { refreshStateInternal() }
|
||||
}
|
||||
|
||||
private suspend fun refreshStateInternal() =
|
||||
withContext(dispatchers.default) { _serialDevices.emit(usbManagerLazy.get()?.deviceList ?: emptyMap()) }
|
||||
withContext(dispatchers.default) { _serialDevices.emit(usbManagerLazy.value?.deviceList ?: emptyMap()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +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.app.repository.usb
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.hardware.usb.UsbManager
|
||||
import com.hoho.android.usbserial.driver.ProbeTable
|
||||
import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface UsbRepositoryModule {
|
||||
companion object {
|
||||
@Provides
|
||||
fun provideUsbManager(application: Application): UsbManager? =
|
||||
application.getSystemService(Context.USB_SERVICE) as UsbManager?
|
||||
|
||||
@Provides fun provideProbeTable(provider: ProbeTableProvider): ProbeTable = provider.get()
|
||||
|
||||
@Provides fun provideUsbSerialProber(probeTable: ProbeTable): UsbSerialProber = UsbSerialProber(probeTable)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,14 +18,12 @@ package org.meshtastic.app.service
|
|||
|
||||
import android.content.Context
|
||||
import androidx.glance.appwidget.updateAll
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.widget.LocalStatsWidget
|
||||
import org.meshtastic.core.repository.AppWidgetUpdater
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AndroidAppWidgetUpdater @Inject constructor(@ApplicationContext private val context: Context) : AppWidgetUpdater {
|
||||
@Single
|
||||
class AndroidAppWidgetUpdater(private val context: Context) : AppWidgetUpdater {
|
||||
override suspend fun updateAll() {
|
||||
// Kickstart the widget composition.
|
||||
// The widget internally uses collectAsState() and its own sampled StateFlow
|
||||
|
|
|
|||
|
|
@ -26,22 +26,17 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.hasLocationPermission
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.repository.LocationRepository
|
||||
import org.meshtastic.core.repository.MeshLocationManager
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import org.meshtastic.proto.Position as ProtoPosition
|
||||
|
||||
@Singleton
|
||||
class AndroidMeshLocationManager
|
||||
@Inject
|
||||
constructor(
|
||||
private val context: Application,
|
||||
private val locationRepository: LocationRepository,
|
||||
) : MeshLocationManager {
|
||||
@Single
|
||||
class AndroidMeshLocationManager(private val context: Application, private val locationRepository: LocationRepository) :
|
||||
MeshLocationManager {
|
||||
private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private var locationFlow: Job? = null
|
||||
|
||||
|
|
|
|||
|
|
@ -20,13 +20,12 @@ import androidx.work.ExistingWorkPolicy
|
|||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.messaging.domain.worker.SendMessageWorker
|
||||
import org.meshtastic.core.repository.MeshWorkerManager
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AndroidMeshWorkerManager @Inject constructor(private val workManager: WorkManager) : MeshWorkerManager {
|
||||
@Single
|
||||
class AndroidMeshWorkerManager(private val workManager: WorkManager) : MeshWorkerManager {
|
||||
override fun enqueueSendMessage(packetId: Int) {
|
||||
val workRequest =
|
||||
OneTimeWorkRequestBuilder<SendMessageWorker>()
|
||||
|
|
|
|||
|
|
@ -19,23 +19,24 @@ package org.meshtastic.app.service
|
|||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
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.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
/** A [BroadcastReceiver] that handles "Mark as read" actions from notifications. */
|
||||
@AndroidEntryPoint
|
||||
class MarkAsReadReceiver : BroadcastReceiver() {
|
||||
class MarkAsReadReceiver :
|
||||
BroadcastReceiver(),
|
||||
KoinComponent {
|
||||
|
||||
@Inject lateinit var packetRepository: PacketRepository
|
||||
private val packetRepository: PacketRepository by inject()
|
||||
|
||||
@Inject lateinit var serviceNotifications: MeshServiceNotifications
|
||||
private val serviceNotifications: MeshServiceNotifications by inject()
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ import android.os.Build
|
|||
import android.os.IBinder
|
||||
import androidx.core.app.ServiceCompat
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.meshtastic.app.BuildConfig
|
||||
import org.meshtastic.app.ui.connections.NO_DEVICE_SELECTED
|
||||
import org.meshtastic.core.common.hasLocationPermission
|
||||
|
|
@ -50,42 +50,37 @@ import org.meshtastic.core.repository.MeshRouter
|
|||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.SERVICE_NOTIFY_ID
|
||||
import org.meshtastic.core.repository.ServiceBroadcasts
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.service.IMeshService
|
||||
import org.meshtastic.proto.PortNum
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@Suppress("TooManyFunctions", "LargeClass")
|
||||
class MeshService : Service() {
|
||||
|
||||
@Inject lateinit var radioInterfaceService: RadioInterfaceService
|
||||
private val radioInterfaceService: RadioInterfaceService by inject()
|
||||
|
||||
@Inject lateinit var serviceRepository: ServiceRepository
|
||||
private val serviceRepository: ServiceRepository by inject()
|
||||
|
||||
@Inject lateinit var packetHandler: PacketHandler
|
||||
private val packetHandler: PacketHandler by inject()
|
||||
|
||||
@Inject lateinit var serviceBroadcasts: ServiceBroadcasts
|
||||
private val serviceBroadcasts: ServiceBroadcasts by inject()
|
||||
|
||||
@Inject lateinit var nodeManager: NodeManager
|
||||
private val nodeManager: NodeManager by inject()
|
||||
|
||||
@Inject lateinit var messageProcessor: MeshMessageProcessor
|
||||
private val messageProcessor: MeshMessageProcessor by inject()
|
||||
|
||||
@Inject lateinit var commandSender: CommandSender
|
||||
private val commandSender: CommandSender by inject()
|
||||
|
||||
@Inject lateinit var locationManager: MeshLocationManager
|
||||
private val locationManager: MeshLocationManager by inject()
|
||||
|
||||
@Inject lateinit var connectionManager: MeshConnectionManager
|
||||
private val connectionManager: MeshConnectionManager by inject()
|
||||
|
||||
@Inject lateinit var serviceNotifications: MeshServiceNotifications
|
||||
private val serviceNotifications: MeshServiceNotifications by inject()
|
||||
|
||||
@Inject lateinit var radioConfigRepository: RadioConfigRepository
|
||||
|
||||
@Inject lateinit var router: MeshRouter
|
||||
private val router: MeshRouter by inject()
|
||||
|
||||
private val serviceJob = Job()
|
||||
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||
|
|
|
|||
|
|
@ -36,11 +36,10 @@ import androidx.core.content.getSystemService
|
|||
import androidx.core.graphics.createBitmap
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.net.toUri
|
||||
import dagger.Lazy
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.app.MainActivity
|
||||
import org.meshtastic.app.R.raw
|
||||
import org.meshtastic.app.service.MarkAsReadReceiver.Companion.MARK_AS_READ_ACTION
|
||||
|
|
@ -92,8 +91,6 @@ import org.meshtastic.proto.ClientNotification
|
|||
import org.meshtastic.proto.DeviceMetrics
|
||||
import org.meshtastic.proto.LocalStats
|
||||
import org.meshtastic.proto.Telemetry
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
/**
|
||||
|
|
@ -103,11 +100,9 @@ import kotlin.time.Duration.Companion.minutes
|
|||
* notifications for various events like new messages, alerts, and service status changes.
|
||||
*/
|
||||
@Suppress("TooManyFunctions", "LongParameterList")
|
||||
@Singleton
|
||||
class MeshServiceNotificationsImpl
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
@Single
|
||||
class MeshServiceNotificationsImpl(
|
||||
private val context: Context,
|
||||
private val packetRepository: Lazy<PacketRepository>,
|
||||
private val nodeRepository: Lazy<NodeRepository>,
|
||||
) : MeshServiceNotifications {
|
||||
|
|
@ -304,7 +299,7 @@ constructor(
|
|||
|
||||
// Seeding from database if caches are still null (e.g. on restart or reconnection)
|
||||
if (cachedLocalStats == null || cachedDeviceMetrics == null) {
|
||||
val repo = nodeRepository.get()
|
||||
val repo = nodeRepository.value
|
||||
val myNodeNum = repo.myNodeInfo.value?.myNodeNum
|
||||
if (myNodeNum != null) {
|
||||
// We use runBlocking here because this is called from MeshConnectionManager's synchronous methods,
|
||||
|
|
@ -389,15 +384,14 @@ constructor(
|
|||
channelName: String?,
|
||||
isSilent: Boolean = false,
|
||||
) {
|
||||
val ourNode = nodeRepository.get().ourNodeInfo.value
|
||||
val ourNode = nodeRepository.value.ourNodeInfo.value
|
||||
val history =
|
||||
packetRepository
|
||||
.get()
|
||||
packetRepository.value
|
||||
.getMessagesFrom(contactKey, includeFiltered = false) { nodeId ->
|
||||
if (nodeId == DataPacket.ID_LOCAL) {
|
||||
ourNode ?: nodeRepository.get().getNode(nodeId)
|
||||
ourNode ?: nodeRepository.value.getNode(nodeId)
|
||||
} else {
|
||||
nodeRepository.get().getNode(nodeId ?: "")
|
||||
nodeRepository.value.getNode(nodeId ?: "")
|
||||
}
|
||||
}
|
||||
.first()
|
||||
|
|
@ -430,7 +424,7 @@ constructor(
|
|||
it.id != SUMMARY_ID && it.notification.group == GROUP_KEY_MESSAGES
|
||||
}
|
||||
|
||||
val ourNode = nodeRepository.get().ourNodeInfo.value
|
||||
val ourNode = nodeRepository.value.ourNodeInfo.value
|
||||
val meName = ourNode?.user?.long_name ?: getString(Res.string.you)
|
||||
val me =
|
||||
Person.Builder()
|
||||
|
|
@ -542,7 +536,7 @@ constructor(
|
|||
builder.setSilent(true)
|
||||
}
|
||||
|
||||
val ourNode = nodeRepository.get().ourNodeInfo.value
|
||||
val ourNode = nodeRepository.value.ourNodeInfo.value
|
||||
val meName = ourNode?.user?.long_name ?: getString(Res.string.you)
|
||||
val me =
|
||||
Person.Builder()
|
||||
|
|
@ -574,7 +568,7 @@ constructor(
|
|||
|
||||
// Add reactions as separate "messages" in history if they exist
|
||||
msg.emojis.forEach { reaction ->
|
||||
val reactorNode = nodeRepository.get().getNode(reaction.user.id)
|
||||
val reactorNode = nodeRepository.value.getNode(reaction.user.id)
|
||||
val reactor =
|
||||
Person.Builder()
|
||||
.setName(reaction.user.long_name)
|
||||
|
|
|
|||
|
|
@ -20,19 +20,20 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
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.model.service.ServiceAction
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ReactionReceiver : BroadcastReceiver() {
|
||||
class ReactionReceiver :
|
||||
BroadcastReceiver(),
|
||||
KoinComponent {
|
||||
|
||||
@Inject lateinit var serviceRepository: ServiceRepository
|
||||
private val serviceRepository: ServiceRepository by inject()
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,16 +20,15 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.RemoteInput
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import jakarta.inject.Inject
|
||||
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.model.DataPacket
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
|
||||
/**
|
||||
* A [BroadcastReceiver] that handles inline replies from notifications.
|
||||
|
|
@ -38,11 +37,12 @@ import org.meshtastic.core.repository.ServiceRepository
|
|||
* and the contact key from the intent, sends the message using the [ServiceRepository], and then cancels the original
|
||||
* notification.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class ReplyReceiver : BroadcastReceiver() {
|
||||
@Inject lateinit var radioController: RadioController
|
||||
class ReplyReceiver :
|
||||
BroadcastReceiver(),
|
||||
KoinComponent {
|
||||
private val radioController: RadioController by inject()
|
||||
|
||||
@Inject lateinit var meshServiceNotifications: MeshServiceNotifications
|
||||
private val meshServiceNotifications: MeshServiceNotifications by inject()
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Parcelable
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
|
|
@ -29,17 +29,11 @@ import org.meshtastic.core.model.NodeInfo
|
|||
import org.meshtastic.core.model.util.toPIIString
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import org.meshtastic.core.repository.ServiceBroadcasts as SharedServiceBroadcasts
|
||||
|
||||
@Singleton
|
||||
class ServiceBroadcasts
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
) : SharedServiceBroadcasts {
|
||||
@Single
|
||||
class ServiceBroadcasts(private val context: Context, private val serviceRepository: ServiceRepository) :
|
||||
SharedServiceBroadcasts {
|
||||
// A mapping of receiver class name to package name - used for explicit broadcasts
|
||||
private val clientPackages = mutableMapOf<String, String>()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.app.settings
|
||||
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.domain.usecase.settings.CleanNodeDatabaseUseCase
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
import org.meshtastic.feature.settings.radio.CleanNodeDatabaseViewModel
|
||||
|
||||
@KoinViewModel
|
||||
class AndroidCleanNodeDatabaseViewModel(
|
||||
cleanNodeDatabaseUseCase: CleanNodeDatabaseUseCase,
|
||||
alertManager: AlertManager,
|
||||
) : CleanNodeDatabaseViewModel(cleanNodeDatabaseUseCase, alertManager)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.app.settings
|
||||
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.repository.MeshLogPrefs
|
||||
import org.meshtastic.core.repository.MeshLogRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
import org.meshtastic.feature.settings.debugging.DebugViewModel
|
||||
import java.util.Locale
|
||||
|
||||
@KoinViewModel
|
||||
class AndroidDebugViewModel(
|
||||
meshLogRepository: MeshLogRepository,
|
||||
nodeRepository: NodeRepository,
|
||||
meshLogPrefs: MeshLogPrefs,
|
||||
alertManager: AlertManager,
|
||||
) : DebugViewModel(meshLogRepository, nodeRepository, meshLogPrefs, alertManager) {
|
||||
|
||||
override fun Int.toHex(length: Int): String = "!%0${length}x".format(Locale.getDefault(), this)
|
||||
|
||||
override fun Byte.toHex(): String = "%02x".format(Locale.getDefault(), this)
|
||||
}
|
||||
|
|
@ -14,21 +14,13 @@
|
|||
* 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.app.di
|
||||
package org.meshtastic.app.settings
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.repository.FilterPrefs
|
||||
import org.meshtastic.core.repository.MessageFilter
|
||||
import org.meshtastic.feature.settings.filter.FilterSettingsViewModel
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface DatabaseModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindDatabaseManager(
|
||||
impl: org.meshtastic.core.database.DatabaseManager,
|
||||
): org.meshtastic.core.common.database.DatabaseManager
|
||||
}
|
||||
@KoinViewModel
|
||||
class AndroidFilterSettingsViewModel(filterPrefs: FilterPrefs, messageFilter: MessageFilter) :
|
||||
FilterSettingsViewModel(filterPrefs, messageFilter)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue