mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-27 10:11:48 -04: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:
@@ -9,7 +9,7 @@ We are incrementally migrating Meshtastic-Android to a **Kotlin Multiplatform (K
|
||||
|
||||
| Directory | Description |
|
||||
| :--- | :--- |
|
||||
| `app/` | Main application module. Contains `MainActivity`, Hilt DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
|
||||
| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
|
||||
| `core/model` | Domain models and common data structures. |
|
||||
| `core:proto` | Protobuf definitions (Git submodule). |
|
||||
| `core:common` | Low-level utilities, I/O abstractions (Okio), and common types. |
|
||||
@@ -39,8 +39,8 @@ We are incrementally migrating Meshtastic-Android to a **Kotlin Multiplatform (K
|
||||
- **Concurrency:** Use Kotlin Coroutines and Flow.
|
||||
- **Thread-Safety:** Use `atomicfu` and `kotlinx.collections.immutable` for shared state in `commonMain`. Avoid `synchronized` or JVM-specific atomics.
|
||||
- **Dependency Injection:**
|
||||
- Use **Hilt**.
|
||||
- **Restriction:** Move Hilt modules to the `app` module if the library module is KMP with multiple flavors, as KSP/Hilt generation often fails in these complex scenarios.
|
||||
- Use **Koin**.
|
||||
- **Restriction:** Move Koin modules to the `app` module if the library module is KMP with multiple flavors, as KSP/Koin generation often fails in these complex scenarios.
|
||||
|
||||
### C. Namespacing
|
||||
- **Standard:** Use the `org.meshtastic.*` namespace for all code.
|
||||
@@ -58,4 +58,4 @@ Use `expect`/`actual` sparingly for platform-specific types (e.g., `Location`, `
|
||||
|
||||
## 5. Troubleshooting
|
||||
- **Build Failures:** Always check `gradle/libs.versions.toml` for dependency conflicts.
|
||||
- **Hilt Generation:** If `@Inject` fails in a KMP module, ensure the corresponding module is bound in the `app` layer's DI package.
|
||||
- **Koin Generation:** If a component fails to inject in a KMP module, ensure the corresponding module is bound in the `app` layer's DI package.
|
||||
|
||||
75
GEMINI.md
Normal file
75
GEMINI.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Meshtastic-Android: AI Agent Instructions (GEMINI.md)
|
||||
|
||||
**CRITICAL AGENT DIRECTIVE:** This file contains validated, comprehensive instructions for interacting with the Meshtastic-Android repository. You MUST adhere strictly to these rules, build commands, and architectural constraints. Only deviate or explore alternatives if the documented commands fail with unexpected errors.
|
||||
|
||||
## 1. Project Overview & Architecture
|
||||
Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, decentralized mesh networks.
|
||||
|
||||
- **Language:** Kotlin (primary), AIDL.
|
||||
- **Build System:** Gradle (Kotlin DSL). JDK 17 is REQUIRED.
|
||||
- **Target SDK:** API 36. Min SDK: API 26 (Android 8.0).
|
||||
- **Flavors:**
|
||||
- `fdroid`: Open source only, no tracking/analytics.
|
||||
- `google`: Includes Google Play Services (Maps) and DataDog analytics.
|
||||
- **Core Architecture:** Modern Android Development (MAD) with KMP core.
|
||||
- **KMP Modules:** `core:model`, `core:proto`, `core:common`, `core:resources`, `core:database`, `core:datastore`, `core:repository`, `core:domain`, `core:prefs`, `core:network`, `core:di`, and `core:data`.
|
||||
- **UI:** Jetpack Compose (Material 3).
|
||||
- **DI:** Koin (centralized in `app` module for KMP modules).
|
||||
- **Navigation:** Type-Safe Jetpack Navigation.
|
||||
- **Room KMP:** Always use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder` and `inMemoryDatabaseBuilder`. DAOs and Entities reside in `commonMain`.
|
||||
|
||||
## 2. Environment Setup (Mandatory First Steps)
|
||||
Before attempting any builds or tests, ensure the environment is configured:
|
||||
|
||||
1. **JDK 17 MUST be used** to prevent Gradle sync/build failures.
|
||||
2. **Secrets:** You must copy `secrets.defaults.properties` to `local.properties` to satisfy build requirements, even for dummy builds:
|
||||
```properties
|
||||
# local.properties example
|
||||
MAPS_API_KEY=dummy_key
|
||||
datadogApplicationId=dummy_id
|
||||
datadogClientToken=dummy_token
|
||||
```
|
||||
|
||||
## 3. Strict Execution Commands
|
||||
Always run commands in the following order to ensure reliability. Do not attempt to bypass `clean` if you are facing build issues.
|
||||
|
||||
**Formatting & Linting (Run BEFORE committing):**
|
||||
```bash
|
||||
./gradlew spotlessApply # Always run to auto-fix formatting
|
||||
./gradlew detekt # Run static analysis
|
||||
```
|
||||
|
||||
**Building:**
|
||||
```bash
|
||||
./gradlew clean # Always start here if facing issues
|
||||
./gradlew assembleDebug # Full build (fdroid and google)
|
||||
```
|
||||
|
||||
**Testing:**
|
||||
```bash
|
||||
./gradlew testAndroid # Run Android unit tests (Robolectric)
|
||||
./gradlew testCommonMain # Run KMP common tests (if applicable)
|
||||
./gradlew connectedAndroidTest # Run instrumented tests
|
||||
```
|
||||
*Note: If testing Compose UI on the JVM (Robolectric) with Java 17, pin your tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.*
|
||||
|
||||
## 4. Coding Standards & Mandates
|
||||
|
||||
- **UI Components:** Always utilize `:core:ui` for shared Jetpack Compose components (e.g., `MeshtasticResourceDialog`, `TransportIcon`). Do not reinvent standard dialogs or preference screens.
|
||||
- **Strings/Localization:** **NEVER** use hardcoded strings or the legacy `app/src/main/res/values/strings.xml`.
|
||||
- **Rule:** You MUST use the Compose Multiplatform Resource library.
|
||||
- **Location:** `core/resources/src/commonMain/composeResources/values/strings.xml`.
|
||||
- **Usage:** `stringResource(Res.string.your_key)`
|
||||
- **Bluetooth/BLE:** Do not use legacy Android Bluetooth callbacks. All BLE communication MUST route through `:core:ble`, utilizing Nordic Semiconductor's Android Common Libraries and Kotlin Coroutines/Flows.
|
||||
- **Dependencies:** Never assume a library is available. Check `gradle/libs.versions.toml` first. If adding a new dependency, it MUST be added to the version catalog, not directly to a `build.gradle.kts` file.
|
||||
- **Namespacing:** Prefer the `org.meshtastic` namespace for all new code. The legacy `com.geeksville.mesh` ApplicationId is maintained for compatibility.
|
||||
|
||||
## 5. Module Map
|
||||
When locating code to modify, use this map:
|
||||
- **`app/`**: Main application wiring and Koin modules. Package: `org.meshtastic.app`.
|
||||
- **`:core:data`**: Core business logic and managers. Package: `org.meshtastic.core.data`.
|
||||
- **`:core:repository`**: Domain interfaces and common models. Package: `org.meshtastic.core.repository`.
|
||||
- **`:core:ble`**: Coroutine-based Bluetooth logic.
|
||||
- **`:core:api`**: AIDL service interface (`IMeshService.aidl`) for third-party integrations (like ATAK).
|
||||
- **`:core:ui`**: Shared Compose UI elements and theming.
|
||||
- **`:feature:*`**: Isolated feature screens (e.g., `:feature:messaging` for chat, `:feature:map` for mapping).
|
||||
@@ -63,7 +63,7 @@ The app follows modern Android development practices, built on top of a shared K
|
||||
- **KMP Modules:** Business logic (`core:domain`), data sources (`core:data`, `core:database`, `core:datastore`), and communications (`core:network`, `core:ble`) are entirely platform-agnostic, enabling future support for Desktop and Web.
|
||||
- **UI:** Jetpack Compose (Material 3) using Compose Multiplatform resources.
|
||||
- **State Management:** Unidirectional Data Flow (UDF) with ViewModels, Coroutines, and Flow.
|
||||
- **Dependency Injection:** Hilt (mapped to KMP `javax.inject` interfaces).
|
||||
- **Dependency Injection:** Koin with Koin Annotations (Compiler Plugin).
|
||||
- **Navigation:** Type-Safe Navigation (Jetpack Navigation).
|
||||
- **Data Layer:** Repository pattern with Room KMP (local DB), DataStore (prefs), and Protobuf (device comms).
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ The single Activity of the application. It hosts the `NavHost` and manages the r
|
||||
### 2. `MeshService`
|
||||
The core background service that manages long-running communication with the mesh radio. It runs as a **Foreground Service** to ensure reliable communication even when the app is in the background.
|
||||
|
||||
### 3. Hilt Application
|
||||
`MeshUtilApplication` is the Hilt entry point, providing the global dependency injection container.
|
||||
### 3. Koin Application
|
||||
`MeshUtilApplication` is the Koin entry point, providing the global dependency injection container.
|
||||
|
||||
## Architecture
|
||||
The module primarily serves as a "glue" layer, connecting:
|
||||
|
||||
@@ -29,7 +29,7 @@ plugins {
|
||||
alias(libs.plugins.meshtastic.android.application)
|
||||
alias(libs.plugins.meshtastic.android.application.flavors)
|
||||
alias(libs.plugins.meshtastic.android.application.compose)
|
||||
alias(libs.plugins.meshtastic.hilt)
|
||||
id("meshtastic.koin")
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
alias(libs.plugins.devtools.ksp)
|
||||
alias(libs.plugins.secrets)
|
||||
@@ -216,6 +216,7 @@ dependencies {
|
||||
implementation(projects.core.database)
|
||||
implementation(projects.core.datastore)
|
||||
implementation(projects.core.di)
|
||||
implementation(projects.core.domain)
|
||||
implementation(projects.core.model)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.network)
|
||||
@@ -261,9 +262,11 @@ dependencies {
|
||||
implementation(libs.org.eclipse.paho.client.mqttv3)
|
||||
implementation(libs.usb.serial.android)
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.androidx.hilt.work)
|
||||
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
|
||||
ksp(libs.androidx.hilt.compiler)
|
||||
implementation(libs.koin.android)
|
||||
implementation(libs.koin.androidx.compose)
|
||||
implementation(libs.koin.compose.viewmodel)
|
||||
implementation(libs.koin.androidx.workmanager)
|
||||
implementation(libs.koin.annotations)
|
||||
implementation(libs.accompanist.permissions)
|
||||
implementation(libs.kermit)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
@@ -300,13 +303,13 @@ dependencies {
|
||||
|
||||
androidTestImplementation(libs.androidx.test.runner)
|
||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||
androidTestImplementation(libs.hilt.android.testing)
|
||||
androidTestImplementation(libs.kotlinx.coroutines.test)
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
androidTestImplementation(libs.nordic.client.android.mock)
|
||||
androidTestImplementation(libs.nordic.core.mock)
|
||||
|
||||
testImplementation(libs.androidx.work.testing)
|
||||
testImplementation(libs.koin.test)
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.mockk)
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
<CurrentIssues>
|
||||
<ID>CyclomaticComplexMethod:SettingsNavigation.kt$@Suppress("LongMethod") fun NavGraphBuilder.settingsGraph(navController: NavHostController)</ID>
|
||||
<ID>LongMethod:TCPInterface.kt$TCPInterface$private suspend fun startConnect()</ID>
|
||||
<ID>LongParameterList:AndroidMetricsViewModel.kt$AndroidMetricsViewModel$( savedStateHandle: SavedStateHandle, private val app: Application, dispatchers: CoroutineDispatchers, meshLogRepository: MeshLogRepository, serviceRepository: ServiceRepository, nodeRepository: NodeRepository, tracerouteSnapshotRepository: TracerouteSnapshotRepository, nodeRequestActions: NodeRequestActions, alertManager: AlertManager, getNodeDetailsUseCase: GetNodeDetailsUseCase, )</ID>
|
||||
<ID>LongParameterList:AndroidNodeListViewModel.kt$AndroidNodeListViewModel$( savedStateHandle: SavedStateHandle, nodeRepository: NodeRepository, radioConfigRepository: RadioConfigRepository, serviceRepository: ServiceRepository, radioController: RadioController, nodeManagementActions: NodeManagementActions, getFilteredNodesUseCase: GetFilteredNodesUseCase, nodeFilterPreferences: NodeFilterPreferences, )</ID>
|
||||
<ID>LongParameterList:AndroidRadioConfigViewModel.kt$AndroidRadioConfigViewModel$( savedStateHandle: SavedStateHandle, private val app: Application, radioConfigRepository: RadioConfigRepository, packetRepository: PacketRepository, serviceRepository: ServiceRepository, nodeRepository: NodeRepository, private val locationRepository: LocationRepository, mapConsentPrefs: MapConsentPrefs, analyticsPrefs: AnalyticsPrefs, homoglyphEncodingPrefs: HomoglyphPrefs, toggleAnalyticsUseCase: ToggleAnalyticsUseCase, toggleHomoglyphEncodingUseCase: ToggleHomoglyphEncodingUseCase, importProfileUseCase: ImportProfileUseCase, exportProfileUseCase: ExportProfileUseCase, exportSecurityConfigUseCase: ExportSecurityConfigUseCase, installProfileUseCase: InstallProfileUseCase, radioConfigUseCase: RadioConfigUseCase, adminActionsUseCase: AdminActionsUseCase, processRadioResponseUseCase: ProcessRadioResponseUseCase, )</ID>
|
||||
<ID>LongParameterList:AndroidSettingsViewModel.kt$AndroidSettingsViewModel$( private val app: Application, radioConfigRepository: RadioConfigRepository, radioController: RadioController, nodeRepository: NodeRepository, uiPrefs: UiPrefs, buildConfigProvider: BuildConfigProvider, databaseManager: DatabaseManager, meshLogPrefs: MeshLogPrefs, setThemeUseCase: SetThemeUseCase, setAppIntroCompletedUseCase: SetAppIntroCompletedUseCase, setProvideLocationUseCase: SetProvideLocationUseCase, setDatabaseCacheLimitUseCase: SetDatabaseCacheLimitUseCase, setMeshLogSettingsUseCase: SetMeshLogSettingsUseCase, meshLocationUseCase: MeshLocationUseCase, exportDataUseCase: ExportDataUseCase, isOtaCapableUseCase: IsOtaCapableUseCase, )</ID>
|
||||
<ID>MagicNumber:AndroidMetricsViewModel.kt$AndroidMetricsViewModel$1000L</ID>
|
||||
<ID>MagicNumber:AndroidMetricsViewModel.kt$AndroidMetricsViewModel$1e-5</ID>
|
||||
<ID>MagicNumber:AndroidMetricsViewModel.kt$AndroidMetricsViewModel$1e-7</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$21972</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$32809</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$6790</ID>
|
||||
@@ -15,10 +22,9 @@
|
||||
<ID>MagicNumber:StreamInterface.kt$StreamInterface$4</ID>
|
||||
<ID>MagicNumber:StreamInterface.kt$StreamInterface$8</ID>
|
||||
<ID>MagicNumber:TCPInterface.kt$TCPInterface$1000</ID>
|
||||
<ID>MaxLineLength:DataSourceModule.kt$DataSourceModule$fun</ID>
|
||||
<ID>ParameterListWrapping:DataSourceModule.kt$DataSourceModule$(impl: BootloaderOtaQuirksJsonDataSourceImpl)</ID>
|
||||
<ID>SwallowedException:NsdManager.kt$ex: IllegalArgumentException</ID>
|
||||
<ID>SwallowedException:TCPInterface.kt$TCPInterface$ex: SocketTimeoutException</ID>
|
||||
<ID>TooGenericExceptionCaught:AndroidRadioConfigViewModel.kt$AndroidRadioConfigViewModel$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:NordicBleInterface.kt$NordicBleInterface$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:TCPInterface.kt$TCPInterface$ex: Throwable</ID>
|
||||
<ID>TooManyFunctions:NordicBleInterface.kt$NordicBleInterface : IRadioInterface</ID>
|
||||
|
||||
@@ -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.")
|
||||
|
||||
22
app/src/fdroid/kotlin/org/meshtastic/app/di/FlavorModule.kt
Normal file
22
app/src/fdroid/kotlin/org/meshtastic/app/di/FlavorModule.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@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.feature.node.component
|
||||
package org.meshtastic.app.node.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import org.meshtastic.core.model.Node
|
||||
|
||||
@Composable
|
||||
internal fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
// No-op for F-Droid builds
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* 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
|
||||
@@ -14,15 +14,15 @@
|
||||
* 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.feature.node.metrics
|
||||
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
|
||||
|
||||
internal object TracerouteMapOverlayInsets {
|
||||
val overlayAlignment: Alignment = Alignment.BottomEnd
|
||||
val overlayPadding: PaddingValues = PaddingValues(end = 16.dp, bottom = 16.dp)
|
||||
val contentHorizontalAlignment: Alignment.Horizontal = Alignment.End
|
||||
}
|
||||
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,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* 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.feature.node.component
|
||||
package org.meshtastic.app.node.component
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -39,7 +39,7 @@ private const val DEFAULT_ZOOM = 15f
|
||||
|
||||
@OptIn(MapsComposeExperimentalApi::class)
|
||||
@Composable
|
||||
internal fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val mapColorScheme =
|
||||
when (dark) {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.di
|
||||
package org.meshtastic.app
|
||||
|
||||
import javax.inject.Qualifier
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Module
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ProcessLifecycle
|
||||
@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,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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,23 +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 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
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
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 DataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideLocationManager(@ApplicationContext context: Context): LocationManager =
|
||||
context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
}
|
||||
@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)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user