mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Migrate networking to Ktor and enhance multiplatform support (#4890)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
acb328dae3
commit
b3b38acc0b
49 changed files with 435 additions and 897 deletions
2
.github/copilot-instructions.md
vendored
2
.github/copilot-instructions.md
vendored
|
|
@ -79,6 +79,8 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
|
|||
- **Dependency Injection:** Use **Koin Annotations** with the K2 compiler plugin (`koin-plugin` in version catalog). The `koin-annotations` library version is unified with `koin-core` (both use `version.ref = "koin"`). The `KoinConventionPlugin` uses the typed `KoinGradleExtension` to configure the K2 plugin (e.g., `compileSafety.set(false)`). Keep root graph assembly in `app`.
|
||||
- **ViewModels:** Follow the MVI/UDF pattern. Use the multiplatform `androidx.lifecycle.ViewModel` in `commonMain`.
|
||||
- **BLE:** All Bluetooth communication must route through `core:ble` using Kable.
|
||||
- **Networking:** Pure **Ktor** — no OkHttp anywhere. Engines: `ktor-client-android` for Android, `ktor-client-java` for desktop/JVM. Use Ktor `Logging` plugin for HTTP debug logging (not OkHttp interceptors). `HttpClient` is provided via Koin in `app/di/NetworkModule` and `core:network/di/CoreNetworkAndroidModule`.
|
||||
- **Image Loading (Coil):** Use `coil-network-ktor3` with `KtorNetworkFetcherFactory` on **all** platforms. `ImageLoader` is configured in host modules only (`app` via Koin `@Single`, `desktop` via `setSingletonImageLoaderFactory`). Feature modules depend only on `libs.coil` (coil-compose) for `AsyncImage` — never add `coil-network-*` or `coil-svg` to feature modules.
|
||||
- **Dependencies:** Check `gradle/libs.versions.toml` before assuming a library is available.
|
||||
- **JetBrains fork aliases:** Version catalog aliases for JetBrains-forked AndroidX artifacts use the `jetbrains-*` prefix (e.g., `jetbrains-lifecycle-runtime-compose`, `jetbrains-navigation3-ui`). Plain `androidx-*` aliases are true Google AndroidX artifacts. Never mix them up in `commonMain`.
|
||||
- **Compose Multiplatform:** Version catalog aliases for Compose Multiplatform artifacts use the `compose-multiplatform-*` prefix (e.g., `compose-multiplatform-material3`, `compose-multiplatform-foundation`). Never use plain `androidx.compose` dependencies in common Main.
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
|
|||
- **Dependency Injection:** Use **Koin Annotations** with the K2 compiler plugin (`koin-plugin` in version catalog). The `koin-annotations` library version is unified with `koin-core` (both use `version.ref = "koin"`). The `KoinConventionPlugin` uses the typed `KoinGradleExtension` to configure the K2 plugin (e.g., `compileSafety.set(false)`). Keep root graph assembly in `app`.
|
||||
- **ViewModels:** Follow the MVI/UDF pattern. Use the multiplatform `androidx.lifecycle.ViewModel` in `commonMain`.
|
||||
- **BLE:** All Bluetooth communication must route through `core:ble` using Kable.
|
||||
- **Networking:** Pure **Ktor** — no OkHttp anywhere. Engines: `ktor-client-android` for Android, `ktor-client-java` for desktop/JVM. Use Ktor `Logging` plugin for HTTP debug logging (not OkHttp interceptors). `HttpClient` is provided via Koin in `app/di/NetworkModule` and `core:network/di/CoreNetworkAndroidModule`.
|
||||
- **Image Loading (Coil):** Use `coil-network-ktor3` with `KtorNetworkFetcherFactory` on **all** platforms. `ImageLoader` is configured in host modules only (`app` via Koin `@Single`, `desktop` via `setSingletonImageLoaderFactory`). Feature modules depend only on `libs.coil` (coil-compose) for `AsyncImage` — never add `coil-network-*` or `coil-svg` to feature modules.
|
||||
- **Dependencies:** Check `gradle/libs.versions.toml` before assuming a library is available.
|
||||
- **JetBrains fork aliases:** Version catalog aliases for JetBrains-forked AndroidX artifacts use the `jetbrains-*` prefix (e.g., `jetbrains-lifecycle-runtime-compose`, `jetbrains-navigation3-ui`). Plain `androidx-*` aliases are true Google AndroidX artifacts. Never mix them up in `commonMain`.
|
||||
- **Compose Multiplatform:** Version catalog aliases for Compose Multiplatform artifacts use the `compose-multiplatform-*` prefix (e.g., `compose-multiplatform-material3`, `compose-multiplatform-foundation`). Never use plain `androidx.compose` dependencies in common Main.
|
||||
|
|
|
|||
|
|
@ -251,17 +251,17 @@ dependencies {
|
|||
implementation(libs.androidx.lifecycle.process)
|
||||
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
|
||||
implementation(libs.jetbrains.lifecycle.runtime.compose)
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
implementation(libs.androidx.paging.compose)
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
implementation(libs.ktor.client.android)
|
||||
implementation(libs.ktor.client.content.negotiation)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
implementation(libs.coil.network.okhttp)
|
||||
implementation(libs.ktor.client.logging)
|
||||
implementation(libs.coil)
|
||||
implementation(libs.coil.network.ktor3)
|
||||
implementation(libs.coil.svg)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.okhttp3.logging.interceptor)
|
||||
implementation(libs.usb.serial.android)
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.koin.android)
|
||||
|
|
@ -281,7 +281,6 @@ dependencies {
|
|||
googleImplementation(libs.maps.compose)
|
||||
googleImplementation(libs.maps.compose.utils)
|
||||
googleImplementation(libs.maps.compose.widgets)
|
||||
googleImplementation(libs.dd.sdk.android.okhttp)
|
||||
googleImplementation(libs.dd.sdk.android.compose)
|
||||
googleImplementation(libs.dd.sdk.android.logs)
|
||||
googleImplementation(libs.dd.sdk.android.rum)
|
||||
|
|
|
|||
|
|
@ -1,415 +0,0 @@
|
|||
androidx.activity:activity-compose:1.12.3
|
||||
androidx.activity:activity-ktx:1.12.3
|
||||
androidx.activity:activity:1.12.3
|
||||
androidx.annotation:annotation-experimental:1.5.1
|
||||
androidx.annotation:annotation-jvm:1.9.1
|
||||
androidx.annotation:annotation:1.9.1
|
||||
androidx.appcompat:appcompat-resources:1.7.1
|
||||
androidx.appcompat:appcompat:1.7.1
|
||||
androidx.arch.core:core-common:2.2.0
|
||||
androidx.arch.core:core-runtime:2.2.0
|
||||
androidx.autofill:autofill:1.0.0
|
||||
androidx.cardview:cardview:1.0.0
|
||||
androidx.collection:collection-jvm:1.5.0
|
||||
androidx.collection:collection-ktx:1.5.0
|
||||
androidx.collection:collection:1.5.0
|
||||
androidx.compose.animation:animation-android:1.11.0-alpha04
|
||||
androidx.compose.animation:animation-core-android:1.11.0-alpha04
|
||||
androidx.compose.animation:animation-core:1.11.0-alpha04
|
||||
androidx.compose.animation:animation:1.11.0-alpha04
|
||||
androidx.compose.foundation:foundation-android:1.11.0-alpha04
|
||||
androidx.compose.foundation:foundation-layout-android:1.11.0-alpha04
|
||||
androidx.compose.foundation:foundation-layout:1.11.0-alpha04
|
||||
androidx.compose.foundation:foundation:1.11.0-alpha04
|
||||
androidx.compose.material3.adaptive:adaptive-android:1.3.0-alpha07
|
||||
androidx.compose.material3.adaptive:adaptive-layout-android:1.3.0-alpha07
|
||||
androidx.compose.material3.adaptive:adaptive-layout:1.3.0-alpha07
|
||||
androidx.compose.material3.adaptive:adaptive-navigation-android:1.3.0-alpha07
|
||||
androidx.compose.material3.adaptive:adaptive-navigation:1.3.0-alpha07
|
||||
androidx.compose.material3.adaptive:adaptive:1.3.0-alpha07
|
||||
androidx.compose.material3:material3-adaptive-navigation-suite-android:1.5.0-alpha13
|
||||
androidx.compose.material3:material3-adaptive-navigation-suite:1.5.0-alpha13
|
||||
androidx.compose.material3:material3-android:1.5.0-alpha13
|
||||
androidx.compose.material3:material3:1.5.0-alpha13
|
||||
androidx.compose.material:material-android:1.11.0-alpha04
|
||||
androidx.compose.material:material-icons-core-android:1.7.8
|
||||
androidx.compose.material:material-icons-core:1.7.8
|
||||
androidx.compose.material:material-icons-extended-android:1.7.8
|
||||
androidx.compose.material:material-icons-extended:1.7.8
|
||||
androidx.compose.material:material-ripple-android:1.11.0-alpha04
|
||||
androidx.compose.material:material-ripple:1.11.0-alpha04
|
||||
androidx.compose.material:material:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-android:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-annotation-android:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-annotation:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-livedata:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-retain-android:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-retain:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-saveable-android:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-saveable:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime-tracing:1.11.0-alpha04
|
||||
androidx.compose.runtime:runtime:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-geometry-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-geometry:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-graphics-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-graphics:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-text-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-text:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-tooling-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-tooling-data-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-tooling-data:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-tooling-preview-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-tooling-preview:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-tooling:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-unit-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-unit:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-util-android:1.11.0-alpha04
|
||||
androidx.compose.ui:ui-util:1.11.0-alpha04
|
||||
androidx.compose.ui:ui:1.11.0-alpha04
|
||||
androidx.compose:compose-bom-alpha:2026.01.01
|
||||
androidx.compose:compose-bom:2026.01.00
|
||||
androidx.concurrent:concurrent-futures-ktx:1.1.0
|
||||
androidx.concurrent:concurrent-futures:1.1.0
|
||||
androidx.constraintlayout:constraintlayout-core:1.0.0
|
||||
androidx.constraintlayout:constraintlayout:2.1.0
|
||||
androidx.coordinatorlayout:coordinatorlayout:1.1.0
|
||||
androidx.core:core-ktx:1.17.0
|
||||
androidx.core:core-location-altitude-external-protobuf:1.0.0-beta01
|
||||
androidx.core:core-location-altitude-proto:1.0.0-beta01
|
||||
androidx.core:core-location-altitude:1.0.0-beta01
|
||||
androidx.core:core-splashscreen:1.2.0
|
||||
androidx.core:core-viewtree:1.0.0
|
||||
androidx.core:core:1.17.0
|
||||
androidx.cursoradapter:cursoradapter:1.0.0
|
||||
androidx.customview:customview-poolingcontainer:1.0.0
|
||||
androidx.customview:customview:1.1.0
|
||||
androidx.databinding:viewbinding:8.13.2
|
||||
androidx.datastore:datastore-android:1.2.0
|
||||
androidx.datastore:datastore-core-android:1.2.0
|
||||
androidx.datastore:datastore-core-okio-jvm:1.2.0
|
||||
androidx.datastore:datastore-core-okio:1.2.0
|
||||
androidx.datastore:datastore-core:1.2.0
|
||||
androidx.datastore:datastore-preferences-android:1.2.0
|
||||
androidx.datastore:datastore-preferences-core-android:1.2.0
|
||||
androidx.datastore:datastore-preferences-core:1.2.0
|
||||
androidx.datastore:datastore-preferences-external-protobuf:1.2.0
|
||||
androidx.datastore:datastore-preferences-proto:1.2.0
|
||||
androidx.datastore:datastore-preferences:1.2.0
|
||||
androidx.datastore:datastore:1.2.0
|
||||
androidx.documentfile:documentfile:1.0.0
|
||||
androidx.drawerlayout:drawerlayout:1.1.1
|
||||
androidx.dynamicanimation:dynamicanimation:1.1.0
|
||||
androidx.emoji2:emoji2-emojipicker:1.6.0
|
||||
androidx.emoji2:emoji2-views-helper:1.6.0
|
||||
androidx.emoji2:emoji2:1.6.0
|
||||
androidx.exifinterface:exifinterface:1.4.1
|
||||
androidx.fragment:fragment-ktx:1.6.2
|
||||
androidx.fragment:fragment:1.6.2
|
||||
androidx.graphics:graphics-path:1.0.1
|
||||
androidx.graphics:graphics-shapes-android:1.0.1
|
||||
androidx.graphics:graphics-shapes:1.0.1
|
||||
androidx.hilt:hilt-common:1.3.0
|
||||
androidx.hilt:hilt-lifecycle-viewmodel-compose:1.3.0
|
||||
androidx.hilt:hilt-lifecycle-viewmodel:1.3.0
|
||||
androidx.hilt:hilt-work:1.3.0
|
||||
androidx.interpolator:interpolator:1.0.0
|
||||
androidx.legacy:legacy-support-core-utils:1.0.0
|
||||
androidx.lifecycle:lifecycle-common-java8:2.10.0
|
||||
androidx.lifecycle:lifecycle-common-jvm:2.10.0
|
||||
androidx.lifecycle:lifecycle-common:2.10.0
|
||||
androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0
|
||||
androidx.lifecycle:lifecycle-livedata-core:2.10.0
|
||||
androidx.lifecycle:lifecycle-livedata-ktx:2.10.0
|
||||
androidx.lifecycle:lifecycle-livedata:2.10.0
|
||||
androidx.lifecycle:lifecycle-process:2.10.0
|
||||
androidx.lifecycle:lifecycle-runtime-android:2.10.0
|
||||
androidx.lifecycle:lifecycle-runtime-compose-android:2.10.0
|
||||
androidx.lifecycle:lifecycle-runtime-compose:2.10.0
|
||||
androidx.lifecycle:lifecycle-runtime-ktx-android:2.10.0
|
||||
androidx.lifecycle:lifecycle-runtime-ktx:2.10.0
|
||||
androidx.lifecycle:lifecycle-runtime:2.10.0
|
||||
androidx.lifecycle:lifecycle-service:2.10.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-android:2.10.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.10.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.10.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.10.0
|
||||
androidx.lifecycle:lifecycle-viewmodel:2.10.0
|
||||
androidx.loader:loader:1.0.0
|
||||
androidx.localbroadcastmanager:localbroadcastmanager:1.1.0
|
||||
androidx.metrics:metrics-performance:1.0.0-beta03
|
||||
androidx.navigation3:navigation3-runtime-android:1.0.0
|
||||
androidx.navigation3:navigation3-runtime:1.0.0
|
||||
androidx.navigation3:navigation3-ui-android:1.0.0
|
||||
androidx.navigation3:navigation3-ui:1.0.0
|
||||
androidx.navigation:navigation-common-android:2.9.7
|
||||
androidx.navigation:navigation-common:2.9.7
|
||||
androidx.navigation:navigation-compose-android:2.9.7
|
||||
androidx.navigation:navigation-compose:2.9.7
|
||||
androidx.navigation:navigation-fragment:2.9.7
|
||||
androidx.navigation:navigation-runtime-android:2.9.7
|
||||
androidx.navigation:navigation-runtime:2.9.7
|
||||
androidx.navigationevent:navigationevent-android:1.0.2
|
||||
androidx.navigationevent:navigationevent-compose-android:1.0.2
|
||||
androidx.navigationevent:navigationevent-compose:1.0.2
|
||||
androidx.navigationevent:navigationevent:1.0.2
|
||||
androidx.paging:paging-common-android:3.4.0
|
||||
androidx.paging:paging-common:3.4.0
|
||||
androidx.paging:paging-compose-android:3.4.0
|
||||
androidx.paging:paging-compose:3.4.0
|
||||
androidx.print:print:1.0.0
|
||||
androidx.privacysandbox.ads:ads-adservices-java:1.1.0-beta11
|
||||
androidx.privacysandbox.ads:ads-adservices:1.1.0-beta11
|
||||
androidx.profileinstaller:profileinstaller:1.4.1
|
||||
androidx.recyclerview:recyclerview:1.3.2
|
||||
androidx.resourceinspection:resourceinspection-annotation:1.0.1
|
||||
androidx.room:room-common-jvm:2.8.4
|
||||
androidx.room:room-common:2.8.4
|
||||
androidx.room:room-paging-android:2.8.4
|
||||
androidx.room:room-paging:2.8.4
|
||||
androidx.room:room-runtime-android:2.8.4
|
||||
androidx.room:room-runtime:2.8.4
|
||||
androidx.savedstate:savedstate-android:1.4.0
|
||||
androidx.savedstate:savedstate-compose-android:1.4.0
|
||||
androidx.savedstate:savedstate-compose:1.4.0
|
||||
androidx.savedstate:savedstate-ktx:1.4.0
|
||||
androidx.savedstate:savedstate:1.4.0
|
||||
androidx.slidingpanelayout:slidingpanelayout:1.2.0
|
||||
androidx.sqlite:sqlite-android:2.6.2
|
||||
androidx.sqlite:sqlite-framework-android:2.6.2
|
||||
androidx.sqlite:sqlite-framework:2.6.2
|
||||
androidx.sqlite:sqlite:2.6.2
|
||||
androidx.startup:startup-runtime:1.2.0
|
||||
androidx.tracing:tracing-ktx:1.2.0
|
||||
androidx.tracing:tracing-perfetto:1.0.1
|
||||
androidx.tracing:tracing:1.2.0
|
||||
androidx.transition:transition:1.6.0
|
||||
androidx.vectordrawable:vectordrawable-animated:1.1.0
|
||||
androidx.vectordrawable:vectordrawable:1.1.0
|
||||
androidx.versionedparcelable:versionedparcelable:1.1.1
|
||||
androidx.viewpager2:viewpager2:1.1.0-beta02
|
||||
androidx.viewpager:viewpager:1.0.0
|
||||
androidx.window:window-core-android:1.5.0
|
||||
androidx.window:window-core:1.5.0
|
||||
androidx.window:window:1.5.0
|
||||
androidx.work:work-runtime-ktx:2.11.1
|
||||
androidx.work:work-runtime:2.11.1
|
||||
co.touchlab:kermit-android:2.0.8
|
||||
co.touchlab:kermit-core-android:2.0.8
|
||||
co.touchlab:kermit-core:2.0.8
|
||||
co.touchlab:kermit:2.0.8
|
||||
com.caverock:androidsvg-aar:1.4
|
||||
com.datadoghq:dd-sdk-android-compose:3.6.0
|
||||
com.datadoghq:dd-sdk-android-core:3.6.0
|
||||
com.datadoghq:dd-sdk-android-internal:3.6.0
|
||||
com.datadoghq:dd-sdk-android-logs:3.6.0
|
||||
com.datadoghq:dd-sdk-android-okhttp:3.6.0
|
||||
com.datadoghq:dd-sdk-android-rum:3.6.0
|
||||
com.datadoghq:dd-sdk-android-session-replay-compose:3.6.0
|
||||
com.datadoghq:dd-sdk-android-session-replay:3.6.0
|
||||
com.datadoghq:dd-sdk-android-timber:3.6.0
|
||||
com.datadoghq:dd-sdk-android-trace-api:3.6.0
|
||||
com.datadoghq:dd-sdk-android-trace-internal:3.6.0
|
||||
com.datadoghq:dd-sdk-android-trace-otel:3.6.0
|
||||
com.datadoghq:dd-sdk-android-trace:3.6.0
|
||||
com.github.mik3y:usb-serial-for-android:3.10.0
|
||||
com.google.accompanist:accompanist-drawablepainter:0.37.3
|
||||
com.google.accompanist:accompanist-permissions:0.37.3
|
||||
com.google.android.datatransport:transport-api:3.2.0
|
||||
com.google.android.datatransport:transport-backend-cct:3.3.0
|
||||
com.google.android.datatransport:transport-runtime:3.3.0
|
||||
com.google.android.gms:play-services-ads-identifier:18.0.0
|
||||
com.google.android.gms:play-services-base:18.5.0
|
||||
com.google.android.gms:play-services-basement:18.9.0
|
||||
com.google.android.gms:play-services-location:21.3.0
|
||||
com.google.android.gms:play-services-maps:20.0.0
|
||||
com.google.android.gms:play-services-measurement-api:23.0.0
|
||||
com.google.android.gms:play-services-measurement-base:23.0.0
|
||||
com.google.android.gms:play-services-measurement-impl:23.0.0
|
||||
com.google.android.gms:play-services-measurement-sdk-api:23.0.0
|
||||
com.google.android.gms:play-services-measurement-sdk:23.0.0
|
||||
com.google.android.gms:play-services-measurement:23.0.0
|
||||
com.google.android.gms:play-services-stats:17.0.2
|
||||
com.google.android.gms:play-services-tasks:18.4.0
|
||||
com.google.android.material:material:1.13.0
|
||||
com.google.auto.value:auto-value-annotations:1.6.3
|
||||
com.google.code.findbugs:jsr305:3.0.2
|
||||
com.google.code.gson:gson:2.13.2
|
||||
com.google.dagger:dagger-lint-aar:2.59
|
||||
com.google.dagger:dagger:2.59
|
||||
com.google.dagger:hilt-android:2.59
|
||||
com.google.dagger:hilt-core:2.59
|
||||
com.google.errorprone:error_prone_annotations:2.41.0
|
||||
com.google.firebase:firebase-analytics:23.0.0
|
||||
com.google.firebase:firebase-annotations:17.0.0
|
||||
com.google.firebase:firebase-bom:34.8.0
|
||||
com.google.firebase:firebase-common:22.0.1
|
||||
com.google.firebase:firebase-components:19.0.0
|
||||
com.google.firebase:firebase-config-interop:16.0.1
|
||||
com.google.firebase:firebase-crashlytics:20.0.4
|
||||
com.google.firebase:firebase-datatransport:19.0.0
|
||||
com.google.firebase:firebase-encoders-json:18.0.1
|
||||
com.google.firebase:firebase-encoders-proto:16.0.0
|
||||
com.google.firebase:firebase-encoders:17.0.0
|
||||
com.google.firebase:firebase-installations-interop:17.2.0
|
||||
com.google.firebase:firebase-installations:19.0.1
|
||||
com.google.firebase:firebase-measurement-connector:20.0.1
|
||||
com.google.firebase:firebase-sessions:3.0.4
|
||||
com.google.guava:failureaccess:1.0.3
|
||||
com.google.guava:guava:33.5.0-android
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
|
||||
com.google.j2objc:j2objc-annotations:3.1
|
||||
com.google.maps.android:android-maps-utils:4.0.0
|
||||
com.google.maps.android:maps-compose-utils:8.0.0
|
||||
com.google.maps.android:maps-compose-widgets:8.0.0
|
||||
com.google.maps.android:maps-compose:8.0.0
|
||||
com.google.maps.android:maps-ktx:6.0.0
|
||||
com.google.maps.android:maps-utils-ktx:6.0.0
|
||||
com.google.re2j:re2j:1.7
|
||||
com.google.zxing:core:3.5.4
|
||||
com.jakewharton.timber:timber:5.0.1
|
||||
com.journeyapps:zxing-android-embedded:4.3.0
|
||||
com.lyft.kronos:kronos-android:0.0.1-alpha11
|
||||
com.lyft.kronos:kronos-java:0.0.1-alpha11
|
||||
com.mikepenz:aboutlibraries-compose-core-android:13.2.1
|
||||
com.mikepenz:aboutlibraries-compose-core:13.2.1
|
||||
com.mikepenz:aboutlibraries-compose-m3-android:13.2.1
|
||||
com.mikepenz:aboutlibraries-compose-m3:13.2.1
|
||||
com.mikepenz:aboutlibraries-core-android:13.2.1
|
||||
com.mikepenz:aboutlibraries-core:13.2.1
|
||||
com.mikepenz:multiplatform-markdown-renderer-android:0.39.2
|
||||
com.mikepenz:multiplatform-markdown-renderer-m3-android:0.39.2
|
||||
com.mikepenz:multiplatform-markdown-renderer-m3:0.39.2
|
||||
com.mikepenz:multiplatform-markdown-renderer:0.39.2
|
||||
com.patrykandpatrick.vico:compose-android:3.0.0-beta.3
|
||||
com.patrykandpatrick.vico:compose-m2-android:3.0.0-beta.3
|
||||
com.patrykandpatrick.vico:compose-m2:3.0.0-beta.3
|
||||
com.patrykandpatrick.vico:compose-m3-android:3.0.0-beta.3
|
||||
com.patrykandpatrick.vico:compose-m3:3.0.0-beta.3
|
||||
com.patrykandpatrick.vico:compose:3.0.0-beta.3
|
||||
com.squareup.okhttp3:logging-interceptor:5.3.2
|
||||
com.squareup.okhttp3:okhttp-android:5.3.2
|
||||
com.squareup.okhttp3:okhttp:5.3.2
|
||||
com.squareup.okio:okio-jvm:3.16.4
|
||||
com.squareup.okio:okio:3.16.4
|
||||
com.squareup.wire:wire-runtime-jvm:5.2.1
|
||||
com.squareup.wire:wire-runtime:5.2.1
|
||||
io.coil-kt.coil3:coil-android:3.3.0
|
||||
io.coil-kt.coil3:coil-compose-android:3.3.0
|
||||
io.coil-kt.coil3:coil-compose-core-android:3.3.0
|
||||
io.coil-kt.coil3:coil-compose-core:3.3.0
|
||||
io.coil-kt.coil3:coil-compose:3.3.0
|
||||
io.coil-kt.coil3:coil-core-android:3.3.0
|
||||
io.coil-kt.coil3:coil-core:3.3.0
|
||||
io.coil-kt.coil3:coil-network-core-android:3.3.0
|
||||
io.coil-kt.coil3:coil-network-core:3.3.0
|
||||
io.coil-kt.coil3:coil-network-okhttp-jvm:3.3.0
|
||||
io.coil-kt.coil3:coil-network-okhttp:3.3.0
|
||||
io.coil-kt.coil3:coil-svg-android:3.3.0
|
||||
io.coil-kt.coil3:coil-svg:3.3.0
|
||||
io.coil-kt.coil3:coil:3.3.0
|
||||
io.ktor:ktor-client-content-negotiation-jvm:3.4.0
|
||||
io.ktor:ktor-client-content-negotiation:3.4.0
|
||||
io.ktor:ktor-client-core-jvm:3.4.0
|
||||
io.ktor:ktor-client-core:3.4.0
|
||||
io.ktor:ktor-client-okhttp-jvm:3.4.0
|
||||
io.ktor:ktor-client-okhttp:3.4.0
|
||||
io.ktor:ktor-events-jvm:3.4.0
|
||||
io.ktor:ktor-events:3.4.0
|
||||
io.ktor:ktor-http-cio-jvm:3.4.0
|
||||
io.ktor:ktor-http-cio:3.4.0
|
||||
io.ktor:ktor-http-jvm:3.4.0
|
||||
io.ktor:ktor-http:3.4.0
|
||||
io.ktor:ktor-io-jvm:3.4.0
|
||||
io.ktor:ktor-io:3.4.0
|
||||
io.ktor:ktor-network-jvm:3.4.0
|
||||
io.ktor:ktor-network:3.4.0
|
||||
io.ktor:ktor-serialization-jvm:3.4.0
|
||||
io.ktor:ktor-serialization-kotlinx-json-jvm:3.4.0
|
||||
io.ktor:ktor-serialization-kotlinx-json:3.4.0
|
||||
io.ktor:ktor-serialization-kotlinx-jvm:3.4.0
|
||||
io.ktor:ktor-serialization-kotlinx:3.4.0
|
||||
io.ktor:ktor-serialization:3.4.0
|
||||
io.ktor:ktor-sse-jvm:3.4.0
|
||||
io.ktor:ktor-sse:3.4.0
|
||||
io.ktor:ktor-utils-jvm:3.4.0
|
||||
io.ktor:ktor-utils:3.4.0
|
||||
io.ktor:ktor-websocket-serialization-jvm:3.4.0
|
||||
io.ktor:ktor-websocket-serialization:3.4.0
|
||||
io.ktor:ktor-websockets-jvm:3.4.0
|
||||
io.ktor:ktor-websockets:3.4.0
|
||||
io.opentelemetry:opentelemetry-api:1.40.0
|
||||
io.opentelemetry:opentelemetry-context:1.40.0
|
||||
jakarta.inject:jakarta.inject-api:2.0.1
|
||||
javax.inject:javax.inject:1
|
||||
no.nordicsemi.android:dfu:2.10.1
|
||||
no.nordicsemi.kotlin.ble:client-android:2.0.0-alpha12
|
||||
no.nordicsemi.kotlin.ble:client-core-android:2.0.0-alpha12
|
||||
no.nordicsemi.kotlin.ble:client-core:2.0.0-alpha12
|
||||
no.nordicsemi.kotlin.ble:core:2.0.0-alpha12
|
||||
org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5
|
||||
org.jctools:jctools-core:3.3.0
|
||||
org.jetbrains.androidx.lifecycle:lifecycle-common:2.9.6
|
||||
org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.9.6
|
||||
org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.9.6
|
||||
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.6
|
||||
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.9.6
|
||||
org.jetbrains.androidx.savedstate:savedstate-compose:1.3.6
|
||||
org.jetbrains.androidx.savedstate:savedstate:1.3.6
|
||||
org.jetbrains.compose.animation:animation-core:1.10.0
|
||||
org.jetbrains.compose.animation:animation:1.10.0
|
||||
org.jetbrains.compose.annotation-internal:annotation:1.10.0
|
||||
org.jetbrains.compose.collection-internal:collection:1.10.0
|
||||
org.jetbrains.compose.components:components-resources-android:1.10.0
|
||||
org.jetbrains.compose.components:components-resources:1.10.0
|
||||
org.jetbrains.compose.foundation:foundation-layout:1.10.0
|
||||
org.jetbrains.compose.foundation:foundation:1.10.0
|
||||
org.jetbrains.compose.material3:material3:1.9.0
|
||||
org.jetbrains.compose.material:material-ripple:1.10.0
|
||||
org.jetbrains.compose.material:material:1.10.0
|
||||
org.jetbrains.compose.runtime:runtime-saveable:1.10.0
|
||||
org.jetbrains.compose.runtime:runtime:1.10.0
|
||||
org.jetbrains.compose.ui:ui-backhandler-android:1.9.1
|
||||
org.jetbrains.compose.ui:ui-backhandler:1.9.1
|
||||
org.jetbrains.compose.ui:ui-geometry:1.10.0
|
||||
org.jetbrains.compose.ui:ui-graphics:1.10.0
|
||||
org.jetbrains.compose.ui:ui-text:1.10.0
|
||||
org.jetbrains.compose.ui:ui-tooling-preview:1.10.0-rc02
|
||||
org.jetbrains.compose.ui:ui-unit:1.10.0
|
||||
org.jetbrains.compose.ui:ui-util:1.10.0
|
||||
org.jetbrains.compose.ui:ui:1.10.0
|
||||
org.jetbrains.kotlin:kotlin-bom:1.8.22
|
||||
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.3.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:2.3.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.3.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.3.0
|
||||
org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.4.0
|
||||
org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.10.2
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.10.2
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.10.2
|
||||
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.7.1-0.6.x-compat
|
||||
org.jetbrains.kotlinx:kotlinx-datetime:0.7.1-0.6.x-compat
|
||||
org.jetbrains.kotlinx:kotlinx-io-bytestring-jvm:0.8.2
|
||||
org.jetbrains.kotlinx:kotlinx-io-bytestring:0.8.2
|
||||
org.jetbrains.kotlinx:kotlinx-io-core-jvm:0.8.2
|
||||
org.jetbrains.kotlinx:kotlinx-io-core:0.8.2
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.10.0
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.10.0
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core:1.10.0
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json-io-jvm:1.10.0
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json-io:1.10.0
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.10.0
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0
|
||||
org.jetbrains:annotations:23.0.0
|
||||
org.jetbrains:markdown-jvm:0.7.3
|
||||
org.jetbrains:markdown:0.7.3
|
||||
org.jspecify:jspecify:1.0.0
|
||||
org.slf4j:slf4j-api:2.0.17
|
||||
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
|
|
@ -27,8 +27,7 @@
|
|||
-keep class com.google.protobuf.** { *; }
|
||||
-keep class org.meshtastic.proto.** { *; }
|
||||
|
||||
# OkHttp
|
||||
-dontwarn okhttp3.internal.platform.**
|
||||
# Networking
|
||||
-dontwarn org.conscrypt.**
|
||||
-dontwarn org.bouncycastle.**
|
||||
-dontwarn org.openjsse.**
|
||||
|
|
|
|||
|
|
@ -16,11 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.app.di
|
||||
|
||||
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
|
||||
|
|
@ -28,18 +25,6 @@ import org.meshtastic.core.network.service.ApiService
|
|||
@Module
|
||||
class FDroidNetworkModule {
|
||||
|
||||
@Single
|
||||
fun provideOkHttpClient(buildConfigProvider: BuildConfigProvider): OkHttpClient = OkHttpClient.Builder()
|
||||
.addInterceptor(
|
||||
interceptor =
|
||||
HttpLoggingInterceptor().apply {
|
||||
if (buildConfigProvider.isDebug) {
|
||||
setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
}
|
||||
},
|
||||
)
|
||||
.build()
|
||||
|
||||
@Single
|
||||
fun provideApiService(): ApiService = object : ApiService {
|
||||
override suspend fun getDeviceHardware(): List<NetworkDeviceHardware> =
|
||||
|
|
|
|||
|
|
@ -16,43 +16,13 @@
|
|||
*/
|
||||
package org.meshtastic.app.di
|
||||
|
||||
import android.content.Context
|
||||
import com.datadog.android.okhttp.DatadogEventListener
|
||||
import com.datadog.android.okhttp.DatadogInterceptor
|
||||
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
|
||||
|
||||
@Module
|
||||
class GoogleNetworkModule {
|
||||
|
||||
@Single fun bindApiService(apiServiceImpl: ApiServiceImpl): ApiService = apiServiceImpl
|
||||
|
||||
@Single
|
||||
fun provideOkHttpClient(context: Context, buildConfigProvider: BuildConfigProvider): OkHttpClient =
|
||||
OkHttpClient.Builder()
|
||||
.cache(
|
||||
cache =
|
||||
Cache(
|
||||
directory = File(context.applicationContext.cacheDir, "http_cache"),
|
||||
maxSize = 50L * 1024L * 1024L, // 50 MiB
|
||||
),
|
||||
)
|
||||
.addInterceptor(
|
||||
interceptor =
|
||||
HttpLoggingInterceptor().apply {
|
||||
if (buildConfigProvider.isDebug) {
|
||||
setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
}
|
||||
},
|
||||
)
|
||||
.addInterceptor(interceptor = DatadogInterceptor.Builder(tracedHosts = listOf("meshtastic.org")).build())
|
||||
.eventListenerFactory(eventListenerFactory = DatadogEventListener.Factory())
|
||||
.build()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,10 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import coil3.ImageLoader
|
||||
import coil3.compose.setSingletonImageLoaderFactory
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
|
@ -106,6 +109,10 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
setContent {
|
||||
// Bridge Koin-provided ImageLoader (with flavor-specific HttpClient, SVG, debug logger)
|
||||
// to Coil's singleton so all AsyncImage composables use the custom configuration.
|
||||
setSingletonImageLoaderFactory { get<ImageLoader>() }
|
||||
|
||||
val theme by model.theme.collectAsStateWithLifecycle()
|
||||
val dynamic = theme == MODE_DYNAMIC
|
||||
val dark =
|
||||
|
|
|
|||
|
|
@ -21,19 +21,21 @@ import android.content.Context
|
|||
import android.net.ConnectivityManager
|
||||
import android.net.nsd.NsdManager
|
||||
import coil3.ImageLoader
|
||||
import coil3.annotation.ExperimentalCoilApi
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.memory.MemoryCache
|
||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||
import coil3.network.ktor3.KtorNetworkFetcherFactory
|
||||
import coil3.request.crossfade
|
||||
import coil3.svg.SvgDecoder
|
||||
import coil3.util.DebugLogger
|
||||
import coil3.util.Logger
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.engine.android.Android
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.logging.LogLevel
|
||||
import io.ktor.client.plugins.logging.Logging
|
||||
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
|
||||
|
|
@ -52,26 +54,24 @@ class NetworkModule {
|
|||
fun provideNsdManager(application: Application): NsdManager =
|
||||
application.getSystemService(Context.NSD_SERVICE) as NsdManager
|
||||
|
||||
@OptIn(ExperimentalCoilApi::class)
|
||||
@Single
|
||||
fun provideImageLoader(
|
||||
okHttpClient: OkHttpClient,
|
||||
httpClient: HttpClient,
|
||||
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()
|
||||
}
|
||||
): ImageLoader = ImageLoader.Builder(context = application)
|
||||
.components {
|
||||
add(KtorNetworkFetcherFactory(httpClient = httpClient))
|
||||
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 provideJson(): Json = Json {
|
||||
|
|
@ -80,9 +80,11 @@ class NetworkModule {
|
|||
}
|
||||
|
||||
@Single
|
||||
fun provideHttpClient(okHttpClient: OkHttpClient, json: Json): HttpClient = HttpClient(engineFactory = OkHttp) {
|
||||
engine { preconfigured = okHttpClient }
|
||||
|
||||
install(plugin = ContentNegotiation) { json(json) }
|
||||
}
|
||||
fun provideHttpClient(json: Json, buildConfigProvider: BuildConfigProvider): HttpClient =
|
||||
HttpClient(engineFactory = Android) {
|
||||
install(plugin = ContentNegotiation) { json(json) }
|
||||
if (buildConfigProvider.isDebug) {
|
||||
install(plugin = Logging) { level = LogLevel.BODY }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import androidx.work.WorkerParameters
|
|||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.HttpClientEngine
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import okhttp3.OkHttpClient
|
||||
import org.junit.Test
|
||||
import org.koin.test.verify.definition
|
||||
import org.koin.test.verify.injectedParameters
|
||||
|
|
@ -53,7 +52,6 @@ class KoinVerificationTest {
|
|||
NodeIdLookup::class,
|
||||
HttpClient::class,
|
||||
HttpClientEngine::class,
|
||||
OkHttpClient::class,
|
||||
),
|
||||
injections =
|
||||
injectedParameters(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -32,11 +32,7 @@ java {
|
|||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } }
|
||||
|
||||
dependencies {
|
||||
// This allows the use of the 'libs' type-safe accessor in the Kotlin source of the plugins
|
||||
|
|
@ -78,17 +74,16 @@ spotless {
|
|||
target("src/*/kotlin/**/*.kt", "src/*/java/**/*.kt")
|
||||
targetExclude("**/build/**/*.kt")
|
||||
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) }
|
||||
ktlint(libs.versions.ktlint.get()).setEditorConfigPath(rootProject.file("../config/spotless/.editorconfig").path)
|
||||
ktlint(libs.versions.ktlint.get())
|
||||
.setEditorConfigPath(rootProject.file("../config/spotless/.editorconfig").path)
|
||||
licenseHeaderFile(rootProject.file("../config/spotless/copyright.kt"))
|
||||
}
|
||||
kotlinGradle {
|
||||
target("**/*.gradle.kts")
|
||||
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) }
|
||||
ktlint(libs.versions.ktlint.get()).setEditorConfigPath(rootProject.file("../config/spotless/.editorconfig").path)
|
||||
licenseHeaderFile(
|
||||
rootProject.file("../config/spotless/copyright.kts"),
|
||||
"(^(?![\\/ ]\\*).*$)"
|
||||
)
|
||||
ktlint(libs.versions.ktlint.get())
|
||||
.setEditorConfigPath(rootProject.file("../config/spotless/.editorconfig").path)
|
||||
licenseHeaderFile(rootProject.file("../config/spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,12 +93,7 @@ detekt {
|
|||
buildUponDefaultConfig = true
|
||||
allRules = false
|
||||
baseline = file("detekt-baseline.xml")
|
||||
source.setFrom(
|
||||
files(
|
||||
"src/main/java",
|
||||
"src/main/kotlin",
|
||||
)
|
||||
)
|
||||
source.setFrom(files("src/main/java", "src/main/kotlin"))
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
|
|
@ -196,6 +186,5 @@ gradlePlugin {
|
|||
id = "meshtastic.root"
|
||||
implementationClass = "RootConventionPlugin"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
|
||||
import com.datadog.gradle.plugin.DdExtension
|
||||
|
|
@ -29,8 +28,8 @@ import org.meshtastic.buildlogic.libs
|
|||
import org.meshtastic.buildlogic.plugin
|
||||
|
||||
/**
|
||||
* Convention plugin for analytics (Google Services, Crashlytics, Datadog).
|
||||
* Segregates these plugins to only affect the "google" flavor and disables their tasks for "fdroid".
|
||||
* Convention plugin for analytics (Google Services, Crashlytics, Datadog). Segregates these plugins to only affect the
|
||||
* "google" flavor and disables their tasks for "fdroid".
|
||||
*/
|
||||
class AnalyticsConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
|
|
@ -50,7 +49,9 @@ class AnalyticsConventionPlugin : Plugin<Project> {
|
|||
// This avoids iterating all tasks with a generic filter and improves configuration performance.
|
||||
plugins.withId("com.google.gms.google-services") {
|
||||
tasks.configureEach {
|
||||
if (name.contains("GoogleServices", ignoreCase = true) && name.contains("fdroid", ignoreCase = true)) {
|
||||
if (
|
||||
name.contains("GoogleServices", ignoreCase = true) && name.contains("fdroid", ignoreCase = true)
|
||||
) {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
|
@ -66,10 +67,13 @@ class AnalyticsConventionPlugin : Plugin<Project> {
|
|||
|
||||
plugins.withId("com.datadoghq.dd-sdk-android-gradle-plugin") {
|
||||
tasks.configureEach {
|
||||
if ((name.contains("datadog", ignoreCase = true) ||
|
||||
name.contains("uploadMapping", ignoreCase = true) ||
|
||||
name.contains("buildId", ignoreCase = true)) &&
|
||||
name.contains("fdroid", ignoreCase = true)) {
|
||||
if (
|
||||
(
|
||||
name.contains("datadog", ignoreCase = true) ||
|
||||
name.contains("uploadMapping", ignoreCase = true) ||
|
||||
name.contains("buildId", ignoreCase = true)
|
||||
) && name.contains("fdroid", ignoreCase = true)
|
||||
) {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
|
@ -26,19 +25,17 @@ import org.meshtastic.buildlogic.plugin
|
|||
|
||||
/**
|
||||
* Compose configuration for Android applications.
|
||||
*
|
||||
* Note: This has identical implementation to AndroidLibraryComposeConventionPlugin.
|
||||
* Both use the same configureAndroidCompose() function which works with CommonExtension.
|
||||
* Kept separate to maintain explicit intent in build.gradle.kts configuration despite duplication.
|
||||
*
|
||||
* Note: This has identical implementation to AndroidLibraryComposeConventionPlugin. Both use the same
|
||||
* configureAndroidCompose() function which works with CommonExtension. Kept separate to maintain explicit intent in
|
||||
* build.gradle.kts configuration despite duplication.
|
||||
*/
|
||||
class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
apply(plugin = libs.plugin("compose-compiler").get().pluginId)
|
||||
apply(plugin = libs.plugin("compose-multiplatform").get().pluginId)
|
||||
extensions.configure<ApplicationExtension> {
|
||||
configureAndroidCompose(this)
|
||||
}
|
||||
extensions.configure<ApplicationExtension> { configureAndroidCompose(this) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
|
@ -24,17 +23,13 @@ import org.meshtastic.buildlogic.configureFlavors
|
|||
/**
|
||||
* Flavor configuration for Android applications.
|
||||
*
|
||||
* Optimization note: This is nearly identical to AndroidLibraryFlavorsConventionPlugin.
|
||||
* The underlying configureFlavors() function already handles both ApplicationExtension and LibraryExtension.
|
||||
* Could be consolidated into a single plugin accepting CommonExtension, but kept separate for now
|
||||
* to maintain explicit intent in build.gradle.kts declarations.
|
||||
* Optimization note: This is nearly identical to AndroidLibraryFlavorsConventionPlugin. The underlying
|
||||
* configureFlavors() function already handles both ApplicationExtension and LibraryExtension. Could be consolidated
|
||||
* into a single plugin accepting CommonExtension, but kept separate for now to maintain explicit intent in
|
||||
* build.gradle.kts declarations.
|
||||
*/
|
||||
class AndroidApplicationFlavorsConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
extensions.configure<ApplicationExtension> {
|
||||
configureFlavors(this)
|
||||
}
|
||||
}
|
||||
with(target) { extensions.configure<ApplicationExtension> { configureFlavors(this) } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
|
@ -26,19 +25,17 @@ import org.meshtastic.buildlogic.plugin
|
|||
|
||||
/**
|
||||
* Compose configuration for Android libraries.
|
||||
*
|
||||
* Note: This has identical implementation to AndroidApplicationComposeConventionPlugin.
|
||||
* Both use the same configureAndroidCompose() function which works with CommonExtension.
|
||||
* Kept separate to maintain explicit intent in build.gradle.kts configuration despite duplication.
|
||||
*
|
||||
* Note: This has identical implementation to AndroidApplicationComposeConventionPlugin. Both use the same
|
||||
* configureAndroidCompose() function which works with CommonExtension. Kept separate to maintain explicit intent in
|
||||
* build.gradle.kts configuration despite duplication.
|
||||
*/
|
||||
class AndroidLibraryComposeConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
apply(plugin = libs.plugin("compose-compiler").get().pluginId)
|
||||
apply(plugin = libs.plugin("compose-multiplatform").get().pluginId)
|
||||
extensions.configure<LibraryExtension> {
|
||||
configureAndroidCompose(this)
|
||||
}
|
||||
extensions.configure<LibraryExtension> { configureAndroidCompose(this) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
|
@ -24,17 +23,13 @@ import org.meshtastic.buildlogic.configureFlavors
|
|||
/**
|
||||
* Flavor configuration for Android libraries.
|
||||
*
|
||||
* Optimization note: This is nearly identical to AndroidApplicationFlavorsConventionPlugin.
|
||||
* The underlying configureFlavors() function already handles both ApplicationExtension and LibraryExtension.
|
||||
* Could be consolidated into a single plugin accepting CommonExtension, but kept separate for now
|
||||
* to maintain explicit intent in build.gradle.kts declarations.
|
||||
* Optimization note: This is nearly identical to AndroidApplicationFlavorsConventionPlugin. The underlying
|
||||
* configureFlavors() function already handles both ApplicationExtension and LibraryExtension. Could be consolidated
|
||||
* into a single plugin accepting CommonExtension, but kept separate for now to maintain explicit intent in
|
||||
* build.gradle.kts declarations.
|
||||
*/
|
||||
class AndroidLibraryFlavorsConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
extensions.configure<LibraryExtension> {
|
||||
configureFlavors(this)
|
||||
}
|
||||
}
|
||||
with(target) { extensions.configure<LibraryExtension> { configureFlavors(this) } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import androidx.room3.gradle.RoomExtension
|
||||
import com.google.devtools.ksp.gradle.KspExtension
|
||||
import org.gradle.api.Plugin
|
||||
|
|
@ -33,9 +32,7 @@ class AndroidRoomConventionPlugin : Plugin<Project> {
|
|||
apply(plugin = "androidx.room3")
|
||||
apply(plugin = "com.google.devtools.ksp")
|
||||
|
||||
extensions.configure<KspExtension> {
|
||||
arg("room.generateKotlin", "true")
|
||||
}
|
||||
extensions.configure<KspExtension> { arg("room.generateKotlin", "true") }
|
||||
|
||||
extensions.configure<RoomExtension> {
|
||||
// The schemas directory contains a schema file for each version of the Room database.
|
||||
|
|
@ -50,13 +47,9 @@ class AndroidRoomConventionPlugin : Plugin<Project> {
|
|||
|
||||
pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform") {
|
||||
extensions.configure<KotlinMultiplatformExtension> {
|
||||
sourceSets.getByName("commonMain").dependencies {
|
||||
implementation(roomRuntime)
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
add("kspAndroid", roomCompiler)
|
||||
sourceSets.getByName("commonMain").dependencies { implementation(roomRuntime) }
|
||||
}
|
||||
dependencies { add("kspAndroid", roomCompiler) }
|
||||
}
|
||||
|
||||
pluginManager.withPlugin("org.jetbrains.kotlin.android") {
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.apply
|
||||
|
|
@ -26,10 +25,9 @@ import org.meshtastic.buildlogic.libs
|
|||
/**
|
||||
* Convention plugin for KMP feature modules.
|
||||
*
|
||||
* Composes [KmpLibraryConventionPlugin], [KmpLibraryComposeConventionPlugin], and
|
||||
* [KoinConventionPlugin] and wires the common Compose / Lifecycle / Koin dependencies
|
||||
* that every feature module needs. Feature `build.gradle.kts` files only declare
|
||||
* their module-specific deps.
|
||||
* Composes [KmpLibraryConventionPlugin], [KmpLibraryComposeConventionPlugin], and [KoinConventionPlugin] and wires the
|
||||
* common Compose / Lifecycle / Koin dependencies that every feature module needs. Feature `build.gradle.kts` files only
|
||||
* declare their module-specific deps.
|
||||
*
|
||||
* Modelled after the `AndroidFeatureImplConventionPlugin` pattern from
|
||||
* [Now in Android](https://github.com/android/nowinandroid).
|
||||
|
|
@ -71,12 +69,8 @@ class KmpFeatureConventionPlugin : Plugin<Project> {
|
|||
implementation(libs.library("androidx-compose-ui-tooling-preview"))
|
||||
}
|
||||
|
||||
sourceSets.getByName("commonTest").dependencies {
|
||||
implementation(project(":core:testing"))
|
||||
}
|
||||
sourceSets.getByName("commonTest").dependencies { implementation(project(":core:testing")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,20 +14,16 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.meshtastic.buildlogic.configureJvmAndroidMainHierarchy
|
||||
|
||||
/**
|
||||
* Opt-in convention for KMP modules that intentionally share a `jvmAndroidMain` source set
|
||||
* between the desktop JVM target and the Android target.
|
||||
* Opt-in convention for KMP modules that intentionally share a `jvmAndroidMain` source set between the desktop JVM
|
||||
* target and the Android target.
|
||||
*/
|
||||
class KmpJvmAndroidConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
configureJvmAndroidMainHierarchy()
|
||||
}
|
||||
with(target) { configureJvmAndroidMainHierarchy() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import dev.mokkery.gradle.MokkeryGradleExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
|
@ -23,6 +22,7 @@ import org.gradle.kotlin.dsl.configure
|
|||
import org.meshtastic.buildlogic.configureAndroidMarketplaceFallback
|
||||
import org.meshtastic.buildlogic.configureKmpTestDependencies
|
||||
import org.meshtastic.buildlogic.configureKotlinMultiplatform
|
||||
import org.meshtastic.buildlogic.configureTestOptions
|
||||
import org.meshtastic.buildlogic.libs
|
||||
import org.meshtastic.buildlogic.plugin
|
||||
|
||||
|
|
@ -38,12 +38,11 @@ class KmpLibraryConventionPlugin : Plugin<Project> {
|
|||
apply(plugin = "meshtastic.kover")
|
||||
apply(plugin = libs.plugin("mokkery").get().pluginId)
|
||||
|
||||
extensions.configure<MokkeryGradleExtension> {
|
||||
stubs.allowConcreteClassInstantiation.set(true)
|
||||
}
|
||||
extensions.configure<MokkeryGradleExtension> { stubs.allowConcreteClassInstantiation.set(true) }
|
||||
|
||||
configureKotlinMultiplatform()
|
||||
configureKmpTestDependencies()
|
||||
configureTestOptions()
|
||||
configureAndroidMarketplaceFallback()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.apply
|
||||
|
|
|
|||
|
|
@ -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,22 +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.buildlogic
|
||||
|
||||
import com.android.build.api.dsl.CommonExtension
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Configure Compose-specific options
|
||||
*/
|
||||
internal fun Project.configureAndroidCompose(
|
||||
commonExtension: CommonExtension,
|
||||
) {
|
||||
commonExtension.apply {
|
||||
buildFeatures.compose = true
|
||||
}
|
||||
/** Configure Compose-specific options */
|
||||
internal fun Project.configureAndroidCompose(commonExtension: CommonExtension) {
|
||||
commonExtension.apply { buildFeatures.compose = true }
|
||||
|
||||
val hasAndroidTest = project.projectDir.resolve("src/androidTest").exists()
|
||||
dependencies {
|
||||
|
|
@ -38,10 +31,9 @@ internal fun Project.configureAndroidCompose(
|
|||
if (hasAndroidTest) {
|
||||
"androidTestImplementation"(platform(bom))
|
||||
}
|
||||
"implementation"(libs.library("androidx-compose-ui-tooling"))
|
||||
"debugImplementation"(libs.library("androidx-compose-ui-tooling"))
|
||||
"implementation"(libs.library("androidx-compose-runtime"))
|
||||
"runtimeOnly"(libs.library("androidx-compose-runtime-tracing"))
|
||||
"debugImplementation"(libs.library("androidx-compose-ui-tooling"))
|
||||
|
||||
"implementation"(libs.library("compose-multiplatform-runtime"))
|
||||
"implementation"(libs.library("compose-multiplatform-resources"))
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* 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.buildlogic
|
||||
|
||||
import org.gradle.api.Project
|
||||
|
|
@ -38,16 +37,8 @@ fun Project.configureDokka() {
|
|||
}
|
||||
|
||||
// Dokka 2.x requires each source file to belong to exactly one source set.
|
||||
val baseSourceSets = listOf(
|
||||
"main",
|
||||
"commonMain",
|
||||
"androidMain",
|
||||
"jvmMain",
|
||||
"jvmAndroidMain",
|
||||
"fdroid",
|
||||
"google",
|
||||
"release"
|
||||
)
|
||||
val baseSourceSets =
|
||||
listOf("main", "commonMain", "androidMain", "jvmMain", "jvmAndroidMain", "fdroid", "google", "release")
|
||||
|
||||
val isCoreSourceSet = name in baseSourceSets
|
||||
suppress.set(!isCoreSourceSet)
|
||||
|
|
@ -59,8 +50,7 @@ fun Project.configureDokka() {
|
|||
|
||||
// Standardized repo-root based source links
|
||||
localDirectory.set(project.projectDir)
|
||||
val relativePath =
|
||||
project.projectDir.relativeTo(rootProject.projectDir).path.replace("\\", "/")
|
||||
val relativePath = project.projectDir.relativeTo(rootProject.projectDir).path.replace("\\", "/")
|
||||
remoteUrl.set(URI("https://github.com/meshtastic/Meshtastic-Android/blob/main/$relativePath"))
|
||||
remoteLineSuffix.set("#L")
|
||||
}
|
||||
|
|
@ -68,20 +58,14 @@ fun Project.configureDokka() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Dokka aggregation in a way that is compatible with Gradle Isolated Projects.
|
||||
*/
|
||||
/** Configure Dokka aggregation in a way that is compatible with Gradle Isolated Projects. */
|
||||
fun Project.configureDokkaAggregation() {
|
||||
extensions.configure<DokkaExtension> {
|
||||
moduleName.set("Meshtastic App")
|
||||
dokkaPublications.configureEach {
|
||||
suppressInheritedMembers.set(true)
|
||||
}
|
||||
dokkaPublications.configureEach { suppressInheritedMembers.set(true) }
|
||||
}
|
||||
|
||||
subprojects.forEach { subproject ->
|
||||
subproject.pluginManager.withPlugin("org.jetbrains.dokka") {
|
||||
dependencies.add("dokka", subproject)
|
||||
}
|
||||
subproject.pluginManager.withPlugin("org.jetbrains.dokka") { dependencies.add("dokka", subproject) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,30 @@
|
|||
* 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.buildlogic
|
||||
|
||||
import com.android.build.api.attributes.ProductFlavorAttr
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.attributes.Attribute
|
||||
import org.gradle.api.attributes.AttributeDisambiguationRule
|
||||
import org.gradle.api.attributes.MultipleCandidatesDetails
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val LEGACY_MARKETPLACE_ATTRIBUTE_NAME = "marketplace"
|
||||
|
||||
/**
|
||||
* Registers [AttributeDisambiguationRule]s so Gradle can pick a default product flavor when a consumer configuration
|
||||
* (e.g. `androidHostTestRuntimeClasspath` from a KMP module) does not carry the marketplace flavor attribute, but the
|
||||
* producer (e.g. `core:barcode`) publishes multiple flavor variants.
|
||||
*
|
||||
* This replaces the previous `afterEvaluate { configurations.configureEach { … } }` approach that stamped attributes on
|
||||
* every resolvable Android configuration. Disambiguation rules fire during dependency resolution — not configuration
|
||||
* time — so they are immune to KGP's lazy configuration creation order and fully compatible with Configuration Cache,
|
||||
* Isolated Projects, and future Gradle/KGP changes.
|
||||
*
|
||||
* The default flavor is configurable via the `meshtastic.defaultMarketplace` Gradle property (defaults to the
|
||||
* [MeshtasticFlavor] entry marked `default = true`, which is `google`).
|
||||
*/
|
||||
internal fun Project.configureAndroidMarketplaceFallback() {
|
||||
val defaultMarketplace =
|
||||
providers
|
||||
|
|
@ -30,27 +45,34 @@ internal fun Project.configureAndroidMarketplaceFallback() {
|
|||
.orElse(MeshtasticFlavor.entries.first { it.default }.name)
|
||||
.get()
|
||||
|
||||
// AGP publishes the typed ProductFlavorAttr on flavored variant configurations.
|
||||
val marketplaceAttr = ProductFlavorAttr.of(MeshtasticFlavor.fdroid.dimension.name)
|
||||
dependencies.attributesSchema.attribute(marketplaceAttr) {
|
||||
disambiguationRules.add(ProductFlavorDisambiguationRule::class.java) { params(defaultMarketplace) }
|
||||
}
|
||||
|
||||
// Some AGP versions also publish a plain String "marketplace" attribute.
|
||||
val legacyMarketplaceAttr = Attribute.of(LEGACY_MARKETPLACE_ATTRIBUTE_NAME, String::class.java)
|
||||
|
||||
afterEvaluate {
|
||||
configurations.configureEach {
|
||||
if (!isCanBeResolved || isCanBeConsumed) return@configureEach
|
||||
if (!name.contains("android", ignoreCase = true)) return@configureEach
|
||||
if (attributes.getAttribute(marketplaceAttr) != null && attributes.getAttribute(legacyMarketplaceAttr) != null) {
|
||||
return@configureEach
|
||||
}
|
||||
|
||||
// Prefer explicit flavor from configuration name; otherwise use configurable default.
|
||||
val inferredMarketplace =
|
||||
when {
|
||||
name.contains(MeshtasticFlavor.fdroid.name, ignoreCase = true) -> MeshtasticFlavor.fdroid.name
|
||||
name.contains(MeshtasticFlavor.google.name, ignoreCase = true) -> MeshtasticFlavor.google.name
|
||||
else -> defaultMarketplace
|
||||
}
|
||||
|
||||
attributes.attribute(marketplaceAttr, objects.named(ProductFlavorAttr::class.java, inferredMarketplace))
|
||||
attributes.attribute(legacyMarketplaceAttr, inferredMarketplace)
|
||||
}
|
||||
dependencies.attributesSchema.attribute(legacyMarketplaceAttr) {
|
||||
disambiguationRules.add(StringDisambiguationRule::class.java) { params(defaultMarketplace) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the default marketplace flavor when Gradle encounters ambiguous [ProductFlavorAttr] candidates during
|
||||
* variant-aware dependency resolution.
|
||||
*/
|
||||
internal abstract class ProductFlavorDisambiguationRule @Inject constructor(private val defaultFlavor: String) :
|
||||
AttributeDisambiguationRule<ProductFlavorAttr> {
|
||||
override fun execute(details: MultipleCandidatesDetails<ProductFlavorAttr>) {
|
||||
details.candidateValues.find { it.name == defaultFlavor }?.let { details.closestMatch(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/** Selects the default marketplace for the legacy plain-String "marketplace" attribute. */
|
||||
internal abstract class StringDisambiguationRule @Inject constructor(private val defaultFlavor: String) :
|
||||
AttributeDisambiguationRule<String> {
|
||||
override fun execute(details: MultipleCandidatesDetails<String>) {
|
||||
details.candidateValues.find { it == defaultFlavor }?.let { details.closestMatch(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* 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.buildlogic
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
|
|
@ -35,9 +34,7 @@ import org.gradle.kotlin.dsl.withType
|
|||
import org.meshtastic.buildlogic.PluginType.Unknown
|
||||
import kotlin.text.RegexOption.DOT_MATCHES_ALL
|
||||
|
||||
/**
|
||||
* Declaration order is important, as only the first match will be retained.
|
||||
*/
|
||||
/** Declaration order is important, as only the first match will be retained. */
|
||||
internal enum class PluginType(val id: String, val ref: String, val style: String) {
|
||||
AndroidApplication(
|
||||
id = "meshtastic.android.application",
|
||||
|
|
@ -89,56 +86,62 @@ internal enum class PluginType(val id: String, val ref: String, val style: Strin
|
|||
ref = "kmp-library",
|
||||
style = "fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000",
|
||||
),
|
||||
Unknown(
|
||||
id = "?",
|
||||
ref = "unknown",
|
||||
style = "fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000",
|
||||
),
|
||||
Unknown(id = "?", ref = "unknown", style = "fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000"),
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized and Isolated Projects compatible graph configuration.
|
||||
*/
|
||||
/** Optimized and Isolated Projects compatible graph configuration. */
|
||||
internal fun Project.configureGraphTasks() {
|
||||
if (!buildFile.exists()) return
|
||||
|
||||
val supportedConfigurations = providers.gradleProperty("graph.supportedConfigurations")
|
||||
.map { it.split(",").toSet() }
|
||||
.orElse(setOf("api", "implementation", "baselineProfile", "testedApks"))
|
||||
val supportedConfigurations =
|
||||
providers
|
||||
.gradleProperty("graph.supportedConfigurations")
|
||||
.map { it.split(",").toSet() }
|
||||
.orElse(setOf("api", "implementation", "baselineProfile", "testedApks"))
|
||||
|
||||
val targetProjectPath = path
|
||||
|
||||
val dumpTask = tasks.register<GraphDumpTask>("graphDump") {
|
||||
projectPath.set(targetProjectPath)
|
||||
|
||||
dependenciesData.set(providers.provider {
|
||||
val deps = mutableMapOf<String, Set<Pair<String, String>>>()
|
||||
val projectDeps = mutableSetOf<Pair<String, String>>()
|
||||
configurations.filter { it.name in supportedConfigurations.get() }.forEach { config ->
|
||||
config.dependencies.withType<ProjectDependency>().forEach { dep ->
|
||||
projectDeps.add(config.name to dep.path)
|
||||
}
|
||||
}
|
||||
deps[targetProjectPath] = projectDeps
|
||||
deps
|
||||
})
|
||||
val dumpTask =
|
||||
tasks.register<GraphDumpTask>("graphDump") {
|
||||
projectPath.set(targetProjectPath)
|
||||
|
||||
pluginsData.set(providers.provider {
|
||||
val projectPlugins = mutableMapOf<String, PluginType>()
|
||||
val type = when {
|
||||
pluginManager.hasPlugin("meshtastic.android.application") || pluginManager.hasPlugin("meshtastic.android.application.compose") -> PluginType.AndroidApplication
|
||||
targetProjectPath.startsWith(":desktop") -> PluginType.ComposeDesktopApplication
|
||||
pluginManager.hasPlugin("meshtastic.kmp.feature") -> PluginType.KmpFeature
|
||||
targetProjectPath.startsWith(":feature:") -> PluginType.AndroidFeature
|
||||
else -> PluginType.entries.firstOrNull { pluginManager.hasPlugin(it.id) } ?: Unknown
|
||||
}
|
||||
projectPlugins[targetProjectPath] = type
|
||||
projectPlugins
|
||||
})
|
||||
dependenciesData.set(
|
||||
providers.provider {
|
||||
val deps = mutableMapOf<String, Set<Pair<String, String>>>()
|
||||
val projectDeps = mutableSetOf<Pair<String, String>>()
|
||||
configurations
|
||||
.filter { it.name in supportedConfigurations.get() }
|
||||
.forEach { config ->
|
||||
config.dependencies.withType<ProjectDependency>().forEach { dep ->
|
||||
projectDeps.add(config.name to dep.path)
|
||||
}
|
||||
}
|
||||
deps[targetProjectPath] = projectDeps
|
||||
deps
|
||||
},
|
||||
)
|
||||
|
||||
output.set(layout.buildDirectory.file("mermaid/graph.txt"))
|
||||
legend.set(layout.buildDirectory.file("mermaid/legend.txt"))
|
||||
}
|
||||
pluginsData.set(
|
||||
providers.provider {
|
||||
val projectPlugins = mutableMapOf<String, PluginType>()
|
||||
val type =
|
||||
when {
|
||||
pluginManager.hasPlugin("meshtastic.android.application") ||
|
||||
pluginManager.hasPlugin("meshtastic.android.application.compose") ->
|
||||
PluginType.AndroidApplication
|
||||
targetProjectPath.startsWith(":desktop") -> PluginType.ComposeDesktopApplication
|
||||
pluginManager.hasPlugin("meshtastic.kmp.feature") -> PluginType.KmpFeature
|
||||
targetProjectPath.startsWith(":feature:") -> PluginType.AndroidFeature
|
||||
else -> PluginType.entries.firstOrNull { pluginManager.hasPlugin(it.id) } ?: Unknown
|
||||
}
|
||||
projectPlugins[targetProjectPath] = type
|
||||
projectPlugins
|
||||
},
|
||||
)
|
||||
|
||||
output.set(layout.buildDirectory.file("mermaid/graph.txt"))
|
||||
legend.set(layout.buildDirectory.file("mermaid/legend.txt"))
|
||||
}
|
||||
|
||||
tasks.register<GraphUpdateTask>("graphUpdate") {
|
||||
projectPath.set(targetProjectPath)
|
||||
|
|
@ -151,20 +154,15 @@ internal fun Project.configureGraphTasks() {
|
|||
@CacheableTask
|
||||
private abstract class GraphDumpTask : DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
abstract val projectPath: Property<String>
|
||||
@get:Input abstract val projectPath: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val dependenciesData: MapProperty<String, Set<Pair<String, String>>>
|
||||
@get:Input abstract val dependenciesData: MapProperty<String, Set<Pair<String, String>>>
|
||||
|
||||
@get:Input
|
||||
abstract val pluginsData: MapProperty<String, PluginType>
|
||||
@get:Input abstract val pluginsData: MapProperty<String, PluginType>
|
||||
|
||||
@get:OutputFile
|
||||
abstract val output: RegularFileProperty
|
||||
@get:OutputFile abstract val output: RegularFileProperty
|
||||
|
||||
@get:OutputFile
|
||||
abstract val legend: RegularFileProperty
|
||||
@get:OutputFile abstract val legend: RegularFileProperty
|
||||
|
||||
@TaskAction
|
||||
operator fun invoke() {
|
||||
|
|
@ -177,17 +175,20 @@ private abstract class GraphDumpTask : DefaultTask() {
|
|||
val currentProject = projectPath.get()
|
||||
val projectPlugins = pluginsData.get()
|
||||
val projectDeps = dependenciesData.get()[currentProject] ?: emptySet()
|
||||
|
||||
appendLine(" $currentProject[${currentProject.substringAfterLast(":")}]:::${projectPlugins[currentProject]?.ref}")
|
||||
|
||||
|
||||
appendLine(
|
||||
" $currentProject[${currentProject.substringAfterLast(":")}]:::${projectPlugins[currentProject]?.ref}",
|
||||
)
|
||||
|
||||
projectDeps.forEach { (config, depPath) ->
|
||||
val link = when (config) {
|
||||
"api" -> "-->"
|
||||
else -> "-.->"
|
||||
}
|
||||
val link =
|
||||
when (config) {
|
||||
"api" -> "-->"
|
||||
else -> "-.->"
|
||||
}
|
||||
appendLine(" $currentProject $link $depPath")
|
||||
}
|
||||
|
||||
|
||||
appendLine()
|
||||
PluginType.entries.forEach { appendLine("classDef ${it.ref} ${it.style};") }
|
||||
}
|
||||
|
|
@ -206,16 +207,17 @@ private abstract class GraphDumpTask : DefaultTask() {
|
|||
|
||||
@CacheableTask
|
||||
private abstract class GraphUpdateTask : DefaultTask() {
|
||||
@get:Input
|
||||
abstract val projectPath: Property<String>
|
||||
@get:Input abstract val projectPath: Property<String>
|
||||
|
||||
@get:InputFile
|
||||
@get:PathSensitive(NONE)
|
||||
abstract val input: RegularFileProperty
|
||||
|
||||
@get:InputFile
|
||||
@get:PathSensitive(NONE)
|
||||
abstract val legend: RegularFileProperty
|
||||
@get:OutputFile
|
||||
abstract val output: RegularFileProperty
|
||||
|
||||
@get:OutputFile abstract val output: RegularFileProperty
|
||||
|
||||
@TaskAction
|
||||
fun update() {
|
||||
|
|
@ -223,10 +225,11 @@ private abstract class GraphUpdateTask : DefaultTask() {
|
|||
if (!readme.exists()) return
|
||||
val mermaid = input.get().asFile.readText()
|
||||
val currentContent = readme.readText()
|
||||
val newContent = currentContent.replace(
|
||||
Regex("<!--region graph-->.*?<!--endregion-->", DOT_MATCHES_ALL),
|
||||
"<!--region graph-->\n```mermaid\n$mermaid\n```\n<!--endregion-->"
|
||||
)
|
||||
val newContent =
|
||||
currentContent.replace(
|
||||
Regex("<!--region graph-->.*?<!--endregion-->", DOT_MATCHES_ALL),
|
||||
"<!--region graph-->\n```mermaid\n$mermaid\n```\n<!--endregion-->",
|
||||
)
|
||||
if (currentContent != newContent) {
|
||||
readme.writeText(newContent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* 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.buildlogic
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
|
|
@ -35,12 +34,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
|||
import org.jetbrains.kotlin.gradle.plugin.KotlinHierarchyTemplate
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
/**
|
||||
* Configure base Kotlin with Android options
|
||||
*/
|
||||
internal fun Project.configureKotlinAndroid(
|
||||
commonExtension: CommonExtension,
|
||||
) {
|
||||
/** Configure base Kotlin with Android options */
|
||||
internal fun Project.configureKotlinAndroid(commonExtension: CommonExtension) {
|
||||
val compileSdkVersion = configProperties.getProperty("COMPILE_SDK").toInt()
|
||||
val minSdkVersion = configProperties.getProperty("MIN_SDK").toInt()
|
||||
val targetSdkVersion = configProperties.getProperty("TARGET_SDK").toInt()
|
||||
|
|
@ -49,7 +44,7 @@ internal fun Project.configureKotlinAndroid(
|
|||
compileSdk = compileSdkVersion
|
||||
|
||||
defaultConfig.minSdk = minSdkVersion
|
||||
|
||||
|
||||
if (this is ApplicationExtension) {
|
||||
defaultConfig.targetSdk = targetSdkVersion
|
||||
}
|
||||
|
|
@ -62,9 +57,7 @@ internal fun Project.configureKotlinAndroid(
|
|||
configureKotlin<KotlinAndroidProjectExtension>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Kotlin Multiplatform options
|
||||
*/
|
||||
/** Configure Kotlin Multiplatform options */
|
||||
internal fun Project.configureKotlinMultiplatform() {
|
||||
extensions.configure<KotlinMultiplatformExtension> {
|
||||
// Standard KMP targets for Meshtastic
|
||||
|
|
@ -80,7 +73,7 @@ internal fun Project.configureKotlinMultiplatform() {
|
|||
extensions.findByType<KotlinMultiplatformAndroidLibraryTarget>()?.apply {
|
||||
compileSdk = configProperties.getProperty("COMPILE_SDK").toInt()
|
||||
minSdk = configProperties.getProperty("MIN_SDK").toInt()
|
||||
|
||||
|
||||
// Set the namespace automatically if not already set
|
||||
if (namespace == null) {
|
||||
val pkg = this@configureKotlinMultiplatform.path.removePrefix(":").replace(":", ".")
|
||||
|
|
@ -96,8 +89,10 @@ internal fun Project.configureKotlinMultiplatform() {
|
|||
tasks.configureEach {
|
||||
val taskName = name.lowercase()
|
||||
if (taskName.contains("iosarm64") || taskName.contains("iossimulatorarm64")) {
|
||||
if (taskName.startsWith("link") && taskName.contains("test") ||
|
||||
taskName == "iosarm64test" || taskName == "iossimulatorarm64test" ||
|
||||
if (
|
||||
taskName.startsWith("link") && taskName.contains("test") ||
|
||||
taskName == "iosarm64test" ||
|
||||
taskName == "iossimulatorarm64test" ||
|
||||
taskName.endsWith("testbinaries")
|
||||
) {
|
||||
enabled = false
|
||||
|
|
@ -109,22 +104,18 @@ internal fun Project.configureKotlinMultiplatform() {
|
|||
configureKotlin<KotlinMultiplatformExtension>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Mokkery for the project
|
||||
*/
|
||||
/** Configure Mokkery for the project */
|
||||
internal fun Project.configureMokkery() {
|
||||
pluginManager.withPlugin(libs.plugin("mokkery").get().pluginId) {
|
||||
extensions.configure<MokkeryGradleExtension> {
|
||||
stubs.allowConcreteClassInstantiation.set(true)
|
||||
}
|
||||
extensions.configure<MokkeryGradleExtension> { stubs.allowConcreteClassInstantiation.set(true) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a shared `jvmAndroidMain` source set using Kotlin's hierarchy template DSL.
|
||||
*
|
||||
* This is for modules that intentionally share JVM-only implementations between the desktop
|
||||
* `jvm()` target and the Android target without hand-written `dependsOn` edges.
|
||||
* This is for modules that intentionally share JVM-only implementations between the desktop `jvm()` target and the
|
||||
* Android target without hand-written `dependsOn` edges.
|
||||
*/
|
||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
internal fun Project.configureJvmAndroidMainHierarchy() {
|
||||
|
|
@ -133,8 +124,7 @@ internal fun Project.configureJvmAndroidMainHierarchy() {
|
|||
common {
|
||||
group("jvmAndroid") {
|
||||
withCompilations { compilation ->
|
||||
compilation.target.targetName == "android" ||
|
||||
compilation.target.targetName == "jvm"
|
||||
compilation.target.targetName == "android" || compilation.target.targetName == "jvm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -142,9 +132,7 @@ internal fun Project.configureJvmAndroidMainHierarchy() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure common test dependencies for KMP modules
|
||||
*/
|
||||
/** Configure common test dependencies for KMP modules */
|
||||
internal fun Project.configureKmpTestDependencies() {
|
||||
extensions.configure<KotlinMultiplatformExtension> {
|
||||
sourceSets.apply {
|
||||
|
|
@ -155,7 +143,7 @@ internal fun Project.configureKmpTestDependencies() {
|
|||
implementation(libs.library("kotest-property"))
|
||||
implementation(libs.library("turbine"))
|
||||
}
|
||||
|
||||
|
||||
// Configure androidHostTest if it exists
|
||||
val androidHostTest = findByName("androidHostTest")
|
||||
androidHostTest?.dependencies {
|
||||
|
|
@ -167,23 +155,17 @@ internal fun Project.configureKmpTestDependencies() {
|
|||
|
||||
// Configure jvmTest if it exists
|
||||
val jvmTest = findByName("jvmTest")
|
||||
jvmTest?.dependencies {
|
||||
implementation(libs.library("kotest-runner-junit6"))
|
||||
}
|
||||
jvmTest?.dependencies { implementation(libs.library("kotest-runner-junit6")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure base Kotlin options for JVM (non-Android)
|
||||
*/
|
||||
/** Configure base Kotlin options for JVM (non-Android) */
|
||||
internal fun Project.configureKotlinJvm() {
|
||||
configureKotlin<KotlinJvmProjectExtension>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure base Kotlin options
|
||||
*/
|
||||
/** Configure base Kotlin options */
|
||||
private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() {
|
||||
extensions.configure<T> {
|
||||
// Using Java 17 for better compatibility with consumers (e.g. plugins, older environments)
|
||||
|
|
@ -203,7 +185,7 @@ private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() {
|
|||
"-Xexpect-actual-classes",
|
||||
"-Xcontext-parameters",
|
||||
"-Xannotation-default-target=param-property",
|
||||
"-Xskip-prerelease-check"
|
||||
"-Xskip-prerelease-check",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -212,10 +194,12 @@ private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() {
|
|||
}
|
||||
}
|
||||
|
||||
val warningsAsErrors = providers.gradleProperty("warningsAsErrors").map { it.toBoolean() }.getOrElse(false)
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_17)
|
||||
allWarningsAsErrors.set(false)
|
||||
allWarningsAsErrors.set(warningsAsErrors)
|
||||
freeCompilerArgs.addAll(
|
||||
// Enable experimental coroutines APIs, including Flow
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
|
|
@ -225,7 +209,7 @@ private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() {
|
|||
"-Xexpect-actual-classes",
|
||||
"-Xcontext-parameters",
|
||||
"-Xannotation-default-target=param-property",
|
||||
"-Xskip-prerelease-check"
|
||||
"-Xskip-prerelease-check",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* 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.buildlogic
|
||||
|
||||
import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
|
||||
|
|
@ -25,12 +24,8 @@ fun Project.configureKover() {
|
|||
extensions.configure<KoverProjectExtension> {
|
||||
reports {
|
||||
total {
|
||||
xml {
|
||||
onCheck.set(true)
|
||||
}
|
||||
html {
|
||||
onCheck.set(true)
|
||||
}
|
||||
xml { onCheck.set(true) }
|
||||
html { onCheck.set(true) }
|
||||
}
|
||||
filters {
|
||||
excludes {
|
||||
|
|
@ -42,16 +37,14 @@ fun Project.configureKover() {
|
|||
classes("*.R")
|
||||
classes("*.R$*")
|
||||
|
||||
// Exclude iOS compile-only stubs (no test execution on these targets)
|
||||
classes("*NoopStubs*")
|
||||
|
||||
// Exclude UI components
|
||||
annotatedBy("*Preview")
|
||||
|
||||
// Exclude declarations
|
||||
annotatedBy(
|
||||
"*.Module",
|
||||
"*.Provides",
|
||||
"*.Binds",
|
||||
"*.Composable",
|
||||
)
|
||||
annotatedBy("*.Module", "*.Provides", "*.Binds", "*.Composable")
|
||||
|
||||
// Suppress generated code
|
||||
packages("koin_aggregated_deps")
|
||||
|
|
@ -63,13 +56,11 @@ fun Project.configureKover() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Configure Kover aggregation in a way that is compatible with Gradle Isolated Projects.
|
||||
* Instead of blindly adding all subprojects, we only add those that have the Kover plugin applied.
|
||||
* Configure Kover aggregation in a way that is compatible with Gradle Isolated Projects. Instead of blindly adding all
|
||||
* subprojects, we only add those that have the Kover plugin applied.
|
||||
*/
|
||||
fun Project.configureKoverAggregation() {
|
||||
subprojects.forEach { subproject ->
|
||||
subproject.pluginManager.withPlugin("org.jetbrains.kotlinx.kover") {
|
||||
dependencies.add("kover", subproject)
|
||||
}
|
||||
subproject.pluginManager.withPlugin("org.jetbrains.kotlinx.kover") { dependencies.add("kover", subproject) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +14,6 @@
|
|||
* 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.buildlogic
|
||||
|
||||
import org.gradle.api.Project
|
||||
|
|
@ -37,17 +36,13 @@ import java.util.Properties
|
|||
val Project.libs
|
||||
get(): VersionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
|
||||
fun VersionCatalog.library(alias: String): Provider<MinimalExternalModuleDependency> =
|
||||
findLibrary(alias).get()
|
||||
fun VersionCatalog.library(alias: String): Provider<MinimalExternalModuleDependency> = findLibrary(alias).get()
|
||||
|
||||
fun VersionCatalog.bundle(alias: String): Provider<ExternalModuleDependencyBundle> =
|
||||
findBundle(alias).get()
|
||||
fun VersionCatalog.bundle(alias: String): Provider<ExternalModuleDependencyBundle> = findBundle(alias).get()
|
||||
|
||||
fun VersionCatalog.plugin(alias: String): Provider<PluginDependency> =
|
||||
findPlugin(alias).get()
|
||||
fun VersionCatalog.plugin(alias: String): Provider<PluginDependency> = findPlugin(alias).get()
|
||||
|
||||
fun VersionCatalog.version(alias: String): String =
|
||||
findVersion(alias).get().requiredVersion
|
||||
fun VersionCatalog.version(alias: String): String = findVersion(alias).get().requiredVersion
|
||||
|
||||
val Project.configProperties: Properties
|
||||
get() {
|
||||
|
|
@ -59,19 +54,23 @@ val Project.configProperties: Properties
|
|||
return properties
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure common test options like parallel execution and logging.
|
||||
*/
|
||||
/** Configure common test options like parallel execution and logging. */
|
||||
internal fun Project.configureTestOptions() {
|
||||
tasks.withType<Test>().configureEach {
|
||||
// Parallelize unit tests
|
||||
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||
maxHeapSize = "2g"
|
||||
// Allow modules with no discovered tests to pass without failing the build
|
||||
filter { isFailOnNoMatchingTests = false }
|
||||
|
||||
// Show test results in the console
|
||||
testLogging {
|
||||
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
|
||||
}
|
||||
testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
|
||||
}
|
||||
|
||||
// Gradle 9+ fails when test sources exist but no test classes are discovered (e.g. all
|
||||
// tests are commented out). Disable to avoid breaking builds for modules with WIP tests.
|
||||
tasks.withType<AbstractTestTask>().configureEach {
|
||||
failOnNoDiscoveredTests.set(false)
|
||||
}
|
||||
|
||||
// Configure test retry if the plugin is applied
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.kotlin.multiplatform.library) apply false
|
||||
|
|
@ -26,7 +24,6 @@ plugins {
|
|||
alias(libs.plugins.devtools.ksp) apply false
|
||||
alias(libs.plugins.koin.compiler) apply false
|
||||
alias(libs.plugins.firebase.crashlytics) apply false
|
||||
alias(libs.plugins.firebase.perf) apply false
|
||||
alias(libs.plugins.google.services) apply false
|
||||
alias(libs.plugins.room) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
|
|
@ -41,20 +38,9 @@ plugins {
|
|||
alias(libs.plugins.spotless) apply false
|
||||
alias(libs.plugins.dokka)
|
||||
alias(libs.plugins.test.retry) apply false
|
||||
alias(libs.plugins.dependency.guard) apply false
|
||||
alias(libs.plugins.meshtastic.root)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
dependencies {
|
||||
dokkaPlugin(libs.dokka.android.documentation.plugin)
|
||||
}
|
||||
|
||||
subprojects {
|
||||
tasks.withType<Test> {
|
||||
failOnNoDiscoveredTests = false
|
||||
}
|
||||
}
|
||||
|
|
@ -18,8 +18,5 @@ okio.ByteString
|
|||
// Kotlin Immutable Collections
|
||||
kotlinx.collections.immutable.*
|
||||
|
||||
// Java Time
|
||||
java.time.*
|
||||
|
||||
// External Libraries
|
||||
com.google.android.gms.maps.model.**
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ kotlin {
|
|||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
}
|
||||
|
||||
jvmMain.dependencies {}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ kotlin {
|
|||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
api(libs.aboutlibraries.core)
|
||||
implementation(libs.aboutlibraries.compose.m3)
|
||||
implementation(libs.kotlinx.atomicfu)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
api(libs.kotlinx.datetime)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
plugins {
|
||||
alias(libs.plugins.meshtastic.kmp.library)
|
||||
alias(libs.plugins.meshtastic.kotlinx.serialization)
|
||||
id("meshtastic.kmp.jvm.android")
|
||||
id("meshtastic.koin")
|
||||
}
|
||||
|
||||
|
|
@ -51,21 +52,18 @@ kotlin {
|
|||
implementation(libs.kotlinx.collections.immutable)
|
||||
}
|
||||
|
||||
// Room / SQLite runtime shared between Android and Desktop JVM targets
|
||||
val jvmAndroidMain by getting {
|
||||
dependencies {
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.androidx.room.paging)
|
||||
implementation(libs.androidx.sqlite.bundled)
|
||||
}
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.core.location.altitude)
|
||||
|
||||
// Needed because core:data references MeshtasticDatabase (supertype RoomDatabase)
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.androidx.room.paging)
|
||||
implementation(libs.androidx.sqlite.bundled)
|
||||
}
|
||||
|
||||
jvmMain.dependencies {
|
||||
// Room / SQLite runtime for JVM target
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.androidx.room.paging)
|
||||
implementation(libs.androidx.sqlite.bundled)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ kotlin {
|
|||
commonMain.dependencies {
|
||||
implementation(projects.core.resources)
|
||||
implementation(libs.kotlinx.serialization.core)
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
}
|
||||
|
||||
commonTest.dependencies { implementation(kotlin("test")) }
|
||||
|
|
|
|||
|
|
@ -61,10 +61,6 @@ kotlin {
|
|||
implementation(projects.core.ble)
|
||||
implementation(projects.core.prefs)
|
||||
implementation(libs.usb.serial.android)
|
||||
implementation(libs.coil.network.okhttp)
|
||||
implementation(libs.coil.svg)
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
implementation(libs.okhttp3.logging.interceptor)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
|
|
|
|||
|
|
@ -16,18 +16,9 @@
|
|||
*/
|
||||
package org.meshtastic.core.network.di
|
||||
|
||||
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 org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Module
|
||||
import org.koin.core.annotation.Single
|
||||
|
||||
@Module
|
||||
@ComponentScan("org.meshtastic.core.network")
|
||||
class CoreNetworkAndroidModule {
|
||||
@Single
|
||||
fun provideHttpClient(json: Json): HttpClient = HttpClient(OkHttp) { install(ContentNegotiation) { json(json) } }
|
||||
}
|
||||
class CoreNetworkAndroidModule
|
||||
|
|
|
|||
|
|
@ -121,6 +121,11 @@ dependencies {
|
|||
implementation(libs.aboutlibraries.core)
|
||||
implementation(libs.aboutlibraries.compose.m3)
|
||||
|
||||
// Coil image loading (network + SVG decoding for device hardware images)
|
||||
implementation(libs.coil)
|
||||
implementation(libs.coil.network.ktor3)
|
||||
implementation(libs.coil.svg)
|
||||
|
||||
// Core KMP modules (JVM variants)
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.di)
|
||||
|
|
@ -177,7 +182,6 @@ dependencies {
|
|||
implementation(libs.okio)
|
||||
|
||||
// Ktor HttpClient (Java engine for JVM/Desktop)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.java)
|
||||
implementation(libs.ktor.client.content.negotiation)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.meshtastic.desktop
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -28,7 +29,16 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.isMetaPressed
|
||||
import androidx.compose.ui.input.key.isShiftPressed
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Notification
|
||||
|
|
@ -41,11 +51,21 @@ import androidx.compose.ui.window.rememberWindowState
|
|||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import co.touchlab.kermit.Logger
|
||||
import coil3.ImageLoader
|
||||
import coil3.compose.setSingletonImageLoaderFactory
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.memory.MemoryCache
|
||||
import coil3.network.ktor3.KtorNetworkFetcherFactory
|
||||
import coil3.request.crossfade
|
||||
import coil3.svg.SvgDecoder
|
||||
import kotlinx.coroutines.flow.first
|
||||
import okio.Path.Companion.toPath
|
||||
import org.jetbrains.skia.Image
|
||||
import org.koin.core.context.startKoin
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.navigation.MeshtasticNavSavedStateConfig
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.navigation.TopLevelDestination
|
||||
import org.meshtastic.core.service.MeshServiceOrchestrator
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
|
@ -73,6 +93,26 @@ import java.util.Locale
|
|||
*/
|
||||
private val LocalAppLocale = staticCompositionLocalOf { "" }
|
||||
|
||||
private const val MEMORY_CACHE_MAX_BYTES = 64L * 1024L * 1024L // 64 MiB
|
||||
private const val DISK_CACHE_MAX_BYTES = 32L * 1024L * 1024L // 32 MiB
|
||||
|
||||
/**
|
||||
* Loads a [Painter] from a Java classpath resource path (e.g. `"icon.png"`).
|
||||
*
|
||||
* This replaces the deprecated `androidx.compose.ui.res.painterResource(String)` API. Desktop native-distribution icons
|
||||
* (`.icns`, `.ico`) remain in `src/main/resources` for the packaging plugin; this helper reads the same directory at
|
||||
* runtime.
|
||||
*/
|
||||
@Composable
|
||||
private fun classpathPainterResource(path: String): Painter {
|
||||
val bitmap: ImageBitmap =
|
||||
remember(path) {
|
||||
val bytes = Thread.currentThread().contextClassLoader!!.getResourceAsStream(path)!!.readAllBytes()
|
||||
Image.makeFromEncoded(bytes).toComposeImageBitmap()
|
||||
}
|
||||
return remember(bitmap) { BitmapPainter(bitmap) }
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
fun main(args: Array<String>) = application(exitProcessOnExit = false) {
|
||||
Logger.i { "Meshtastic Desktop — Starting" }
|
||||
|
|
@ -130,7 +170,7 @@ fun main(args: Array<String>) = application(exitProcessOnExit = false) {
|
|||
var isAppVisible by remember { mutableStateOf(true) }
|
||||
var isWindowReady by remember { mutableStateOf(false) }
|
||||
val trayState = rememberTrayState()
|
||||
val appIcon = painterResource("icon.png")
|
||||
val appIcon = classpathPainterResource("icon.png")
|
||||
|
||||
val notificationManager = remember { koinApp.koin.get<DesktopNotificationManager>() }
|
||||
val desktopPrefs = remember { koinApp.koin.get<DesktopPreferencesDataSource>() }
|
||||
|
|
@ -188,14 +228,81 @@ fun main(args: Array<String>) = application(exitProcessOnExit = false) {
|
|||
)
|
||||
|
||||
if (isWindowReady && isAppVisible) {
|
||||
val backStack =
|
||||
rememberNavBackStack(MeshtasticNavSavedStateConfig, TopLevelDestination.Connections.route as NavKey)
|
||||
|
||||
Window(
|
||||
onCloseRequest = { isAppVisible = false },
|
||||
title = "Meshtastic Desktop",
|
||||
icon = appIcon,
|
||||
state = windowState,
|
||||
onPreviewKeyEvent = { event ->
|
||||
if (event.type != KeyEventType.KeyDown || !event.isMetaPressed) return@Window false
|
||||
when {
|
||||
// ⌘Q → Quit
|
||||
event.key == Key.Q -> {
|
||||
exitApplication()
|
||||
true
|
||||
}
|
||||
// ⌘, → Settings
|
||||
event.key == Key.Comma -> {
|
||||
if (
|
||||
TopLevelDestination.Settings != TopLevelDestination.fromNavKey(backStack.lastOrNull())
|
||||
) {
|
||||
navigateTopLevel(backStack, TopLevelDestination.Settings.route)
|
||||
}
|
||||
true
|
||||
}
|
||||
// ⌘⇧T → Toggle theme
|
||||
event.key == Key.T && event.isShiftPressed -> {
|
||||
uiPrefs.setTheme(if (isDarkTheme) 1 else 2)
|
||||
true
|
||||
}
|
||||
// ⌘1 → Conversations
|
||||
event.key == Key.One -> {
|
||||
navigateTopLevel(backStack, TopLevelDestination.Conversations.route)
|
||||
true
|
||||
}
|
||||
// ⌘2 → Nodes
|
||||
event.key == Key.Two -> {
|
||||
navigateTopLevel(backStack, TopLevelDestination.Nodes.route)
|
||||
true
|
||||
}
|
||||
// ⌘3 → Map
|
||||
event.key == Key.Three -> {
|
||||
navigateTopLevel(backStack, TopLevelDestination.Map.route)
|
||||
true
|
||||
}
|
||||
// ⌘4 → Connections
|
||||
event.key == Key.Four -> {
|
||||
navigateTopLevel(backStack, TopLevelDestination.Connections.route)
|
||||
true
|
||||
}
|
||||
// ⌘/ → About
|
||||
event.key == Key.Slash -> {
|
||||
backStack.add(SettingsRoutes.About)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
},
|
||||
) {
|
||||
val backStack =
|
||||
rememberNavBackStack(MeshtasticNavSavedStateConfig, TopLevelDestination.Connections.route as NavKey)
|
||||
// Configure Coil ImageLoader for desktop with SVG decoding and network fetching.
|
||||
// This is the desktop equivalent of the Android app's NetworkModule.provideImageLoader().
|
||||
setSingletonImageLoaderFactory { context ->
|
||||
val cacheDir = System.getProperty("user.home") + "/.meshtastic/image_cache"
|
||||
ImageLoader.Builder(context)
|
||||
.components {
|
||||
add(KtorNetworkFetcherFactory())
|
||||
add(SvgDecoder.Factory())
|
||||
}
|
||||
.memoryCache { MemoryCache.Builder().maxSizeBytes(MEMORY_CACHE_MAX_BYTES).build() }
|
||||
.diskCache {
|
||||
DiskCache.Builder().directory(cacheDir.toPath()).maxSizeBytes(DISK_CACHE_MAX_BYTES).build()
|
||||
}
|
||||
.crossfade(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
// Providing localePref via a staticCompositionLocalOf forces the entire subtree to
|
||||
// recompose when the locale changes — CMP Resources' rememberResourceEnvironment then
|
||||
|
|
@ -207,3 +314,11 @@ fun main(args: Array<String>) = application(exitProcessOnExit = false) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Replaces the backstack with a single top-level destination route. */
|
||||
private fun navigateTopLevel(backStack: MutableList<NavKey>, route: NavKey) {
|
||||
backStack.add(route)
|
||||
while (backStack.size > 1) {
|
||||
backStack.removeAt(0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Roadmap
|
||||
|
||||
> Last updated: 2026-03-17
|
||||
> Last updated: 2026-03-23
|
||||
|
||||
Forward-looking priorities for the Meshtastic KMP multi-target effort. For current state, see [`kmp-status.md`](./kmp-status.md). For the full gap analysis, see [`decisions/architecture-review-2026-03.md`](./decisions/architecture-review-2026-03.md).
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ These items address structural gaps identified in the March 2026 architecture re
|
|||
- Test navigation flows end-to-end
|
||||
2. **Tier 2: Polish (High Priority)**
|
||||
- Additional desktop-specific settings polish
|
||||
- ✅ **MenuBar integration** and Keyboard shortcuts
|
||||
- ✅ **Keyboard shortcuts** via `onPreviewKeyEvent` (MenuBar removed)
|
||||
- Window management
|
||||
- State persistence
|
||||
3. **Tier 3: Advanced (Nice-to-have)**
|
||||
|
|
@ -74,7 +74,7 @@ These items address structural gaps identified in the March 2026 architecture re
|
|||
| Charts | ✅ Vico KMP charts wired in commonMain (Device, Environment, Signal, Power, Pax) |
|
||||
| Debug Panel | ✅ Real screen (mesh log viewer via shared `DebugViewModel`) |
|
||||
| Notifications | ✅ Desktop native notifications with system tray icon support |
|
||||
| MenuBar | ✅ Done — Native application menu bar with File/View menus |
|
||||
| MenuBar | ✅ Removed — replaced with `onPreviewKeyEvent` keyboard shortcuts (⌘Q, ⌘,, ⌘⇧T, ⌘1-4, ⌘/) |
|
||||
| About | ✅ Shared `commonMain` screen (AboutLibraries KMP `produceLibraries` + per-platform JSON) |
|
||||
| Packaging | ✅ Done — Native distribution pipeline in CI (DMG, MSI, DEB) |
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ kotlin {
|
|||
implementation(projects.core.network)
|
||||
implementation(projects.feature.settings)
|
||||
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
}
|
||||
|
||||
androidMain.dependencies { implementation(libs.usb.serial.android) }
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ kotlin {
|
|||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
implementation(projects.core.ble)
|
||||
implementation(projects.core.common)
|
||||
|
|
@ -48,19 +47,18 @@ kotlin {
|
|||
implementation(projects.core.resources)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
implementation(libs.coil)
|
||||
implementation(libs.kable.core)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.markdown.renderer)
|
||||
implementation(libs.markdown.renderer.m3)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.nordic.dfu)
|
||||
implementation(libs.coil)
|
||||
implementation(libs.coil.network.okhttp)
|
||||
implementation(libs.markdown.renderer.android)
|
||||
implementation(libs.markdown.renderer.m3)
|
||||
implementation(libs.markdown.renderer)
|
||||
}
|
||||
|
||||
val androidHostTest by getting {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,9 @@ kotlin {
|
|||
implementation(projects.core.ui)
|
||||
implementation(projects.core.resources)
|
||||
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
}
|
||||
|
||||
androidMain.dependencies { implementation(libs.jetbrains.navigation3.ui) }
|
||||
|
||||
androidUnitTest.dependencies {
|
||||
implementation(libs.junit)
|
||||
implementation(libs.robolectric)
|
||||
|
|
|
|||
|
|
@ -29,9 +29,8 @@ kotlin {
|
|||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
implementation(projects.core.common)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(projects.core.data)
|
||||
implementation(projects.core.database)
|
||||
implementation(projects.core.datastore)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ kotlin {
|
|||
implementation(projects.core.service)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
implementation(libs.jetbrains.navigationevent.compose)
|
||||
implementation(libs.androidx.paging.common)
|
||||
implementation(libs.androidx.paging.compose)
|
||||
|
|
|
|||
|
|
@ -48,12 +48,11 @@ kotlin {
|
|||
implementation(projects.core.di)
|
||||
implementation(projects.feature.map)
|
||||
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.markdown.renderer)
|
||||
implementation(libs.markdown.renderer.m3)
|
||||
implementation(libs.vico.compose)
|
||||
implementation(libs.vico.compose.m2)
|
||||
implementation(libs.vico.compose.m3)
|
||||
|
||||
// JetBrains Material 3 Adaptive (multiplatform ListDetailPaneScaffold)
|
||||
|
|
@ -65,10 +64,7 @@ kotlin {
|
|||
androidMain.dependencies {
|
||||
implementation(libs.androidx.appcompat)
|
||||
|
||||
implementation(libs.coil)
|
||||
implementation(libs.markdown.renderer.android)
|
||||
implementation(libs.markdown.renderer.m3)
|
||||
implementation(libs.markdown.renderer)
|
||||
}
|
||||
|
||||
androidUnitTest.dependencies {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ kotlin {
|
|||
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.aboutlibraries.compose.m3)
|
||||
implementation(libs.jetbrains.navigation3.runtime)
|
||||
implementation(libs.jetbrains.navigation3.ui)
|
||||
}
|
||||
|
||||
|
|
@ -55,11 +54,6 @@ kotlin {
|
|||
implementation(projects.core.barcode)
|
||||
implementation(projects.core.nfc)
|
||||
implementation(libs.androidx.appcompat)
|
||||
|
||||
implementation(libs.coil)
|
||||
implementation(libs.markdown.renderer.android)
|
||||
implementation(libs.markdown.renderer.m3)
|
||||
implementation(libs.markdown.renderer)
|
||||
}
|
||||
|
||||
androidUnitTest.dependencies {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ dependencies {
|
|||
implementation(projects.core.resources)
|
||||
implementation(projects.core.repository)
|
||||
|
||||
implementation(libs.androidx.compose.ui) // LocalConfiguration, LocalDensity
|
||||
implementation(libs.androidx.glance.appwidget)
|
||||
implementation(libs.androidx.glance.material3)
|
||||
implementation(libs.androidx.glance.preview)
|
||||
|
|
|
|||
|
|
@ -11,12 +11,7 @@
|
|||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
#Sat Feb 28 21:28:07 CST 2026
|
||||
android.enableJetifier=false
|
||||
android.enableR8.fullMode=true
|
||||
android.experimental.lint.analysisPerComponent=true
|
||||
android.nonTransitiveRClass=true
|
||||
android.useAndroidX=true
|
||||
dependency.analysis.print.build.health=true
|
||||
enableComposeCompilerMetrics=false
|
||||
enableComposeCompilerReports=false
|
||||
kotlin.code.style=official
|
||||
|
|
@ -26,7 +21,6 @@ ksp.incremental=true
|
|||
ksp.incremental.classpath=true
|
||||
ksp.incremental.intermodule=true
|
||||
ksp.run.in.process=true
|
||||
ksp.useKSP2=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.configureondemand=false
|
||||
|
|
|
|||
|
|
@ -98,9 +98,6 @@ jetbrains-lifecycle-runtime = { module = "org.jetbrains.androidx.lifecycle:lifec
|
|||
jetbrains-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "jetbrains-lifecycle" }
|
||||
jetbrains-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "jetbrains-lifecycle" }
|
||||
jetbrains-lifecycle-viewmodel-navigation3 = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "jetbrains-lifecycle" }
|
||||
# JetBrains Navigation 3 currently publishes `navigation3-ui` (no separate `navigation3-runtime` artifact).
|
||||
# Both `jetbrains-navigation3-runtime` and `jetbrains-navigation3-ui` resolve to the same coordinate.
|
||||
jetbrains-navigation3-runtime = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "navigation3" }
|
||||
jetbrains-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "navigation3" }
|
||||
jetbrains-navigationevent-compose = { module = "org.jetbrains.androidx.navigationevent:navigationevent-compose", version.ref = "navigationevent" }
|
||||
androidx-paging-common = { module = "androidx.paging:paging-common", version.ref = "paging" }
|
||||
|
|
@ -178,12 +175,12 @@ kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serializa
|
|||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
||||
|
||||
# Networking
|
||||
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
|
||||
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
|
||||
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
||||
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||
okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version = "5.3.2" }
|
||||
|
||||
# Testing
|
||||
androidx-test-core = { module = "androidx.test:core", version = "1.7.0" }
|
||||
|
|
@ -204,11 +201,10 @@ aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref
|
|||
aboutlibraries-compose-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlibraries" }
|
||||
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
|
||||
coil = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
|
||||
coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
|
||||
coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" }
|
||||
dd-sdk-android-compose = { module = "com.datadoghq:dd-sdk-android-compose", version.ref = "dd-sdk-android" }
|
||||
dd-sdk-android-logs = { module = "com.datadoghq:dd-sdk-android-logs", version.ref = "dd-sdk-android" }
|
||||
dd-sdk-android-okhttp = { module = "com.datadoghq:dd-sdk-android-okhttp", version.ref = "dd-sdk-android" }
|
||||
dd-sdk-android-rum = { module = "com.datadoghq:dd-sdk-android-rum", version.ref = "dd-sdk-android" }
|
||||
dd-sdk-android-timber = { module = "com.datadoghq:dd-sdk-android-timber", version.ref = "dd-sdk-android" }
|
||||
dd-sdk-android-trace = { module = "com.datadoghq:dd-sdk-android-trace", version.ref = "dd-sdk-android" }
|
||||
|
|
@ -233,7 +229,6 @@ kermit = { module = "co.touchlab:kermit", version = "2.1.0" }
|
|||
|
||||
usb-serial-android = { module = "com.github.mik3y:usb-serial-for-android", version = "3.10.0" }
|
||||
vico-compose = { group = "com.patrykandpatrick.vico", name = "compose", version.ref = "vico" }
|
||||
vico-compose-m2 = { group = "com.patrykandpatrick.vico", name = "compose-m2", version.ref = "vico" }
|
||||
vico-compose-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", version.ref = "vico" }
|
||||
|
||||
# Build Logic
|
||||
|
|
@ -283,7 +278,6 @@ secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugi
|
|||
|
||||
# Firebase
|
||||
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-gradle" }
|
||||
firebase-perf = { id = "com.google.firebase.firebase-perf", version = "2.0.2" }
|
||||
|
||||
# Other
|
||||
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
|
||||
|
|
@ -306,7 +300,6 @@ meshtastic-android-library-compose = { id = "meshtastic.android.library.compose"
|
|||
meshtastic-android-library-flavors = { id = "meshtastic.android.library.flavors" }
|
||||
meshtastic-android-lint = { id = "meshtastic.android.lint" }
|
||||
meshtastic-android-room = { id = "meshtastic.android.room" }
|
||||
meshtastic-android-test = { id = "meshtastic.android.test" }
|
||||
meshtastic-detekt = { id = "meshtastic.detekt" }
|
||||
meshtastic-koin = { id = "meshtastic.koin" }
|
||||
meshtastic-kotlinx-serialization = { id = "meshtastic.kotlinx.serialization" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue