refactor(build): rename entry modules and remove DESKTOP_ONLY mode (#5476)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich
2026-05-18 07:44:12 -05:00
committed by GitHub
parent d24fc9ac92
commit f4b6b02ace
215 changed files with 1454 additions and 539 deletions

View File

@@ -18,7 +18,7 @@ git submodule update --init
./gradlew :core:data:allTests
# Single module tests (Android-only module like :app)
./gradlew :app:testFdroidDebugUnitTest
./gradlew :androidApp:testFdroidDebugUnitTest
# Cross-platform compilation check (no tests)
./gradlew kmpSmokeCompile
@@ -46,7 +46,7 @@ KMP modules have different task names than pure-Android modules. Using the wrong
## Quick Reference
- **Architecture**: KMP project (Android, Desktop, iOS). Business logic in `commonMain`; platform shells (`app/`, `desktop/`) wire DI and host UI. See `AGENTS.md` and `.skills/kmp-architecture/`.
- **Architecture**: KMP project (Android, Desktop, iOS). Business logic in `commonMain`; platform shells (`androidApp/`, `desktopApp/`) wire DI and host UI. See `AGENTS.md` and `.skills/kmp-architecture/`.
- **Flavors**: `fdroid` (OSS) / `google` (Maps + DataDog). Only one installable at a time (different signing keys).
- **Verify before push**: Run `./gradlew spotlessApply detekt assembleDebug test allTests`, then confirm CI with `gh pr checks <PR>`.
- **Strings**: `stringResource(Res.string.key)` — run `python3 scripts/sort-strings.py` after adding strings.

View File

@@ -5,7 +5,7 @@ excludeAgent: "code-review"
# CI Workflow Rules
- Prefer explicit Gradle task paths (`app:lintFdroidDebug`) over shorthand (`lintDebug`).
- Prefer explicit Gradle task paths (`androidApp:lintFdroidDebug`) over shorthand (`lintDebug`).
- CI uses `.github/ci-gradle.properties` — don't assume local `gradle.properties` values.
- CI passes `-Pci=true` to enable full processor usage via `maxParallelForks`.
- Use `fetch-depth: 0` only where needed (spotless ratcheting, version code). Use `fetch-depth: 1` otherwise.

View File

@@ -8,10 +8,10 @@ on:
- main
paths:
# Only rebuild docs when source code changes (Dokka generates from KDoc)
- 'app/src/**'
- 'androidApp/src/**'
- 'core/**/src/**'
- 'feature/**/src/**'
- 'desktop/src/**'
- 'desktopApp/src/**'
- 'build-logic/**'
- 'build.gradle.kts'
- 'settings.gradle.kts'

View File

@@ -30,9 +30,9 @@ jobs:
- '.github/workflows/**'
- '.github/actions/**'
# Product modules validated by reusable-check
- 'app/**'
- 'androidApp/**'
- 'baselineprofile/**'
- 'desktop/**'
- 'desktopApp/**'
- 'core/**'
- 'feature/**'
- 'screenshot-tests/**'
@@ -95,9 +95,9 @@ jobs:
PY
# 2. VALIDATION & BUILD: Delegate to reusable-check.yml
# We disable coverage for PRs to keep feedback fast (< 10 mins).
# Desktop compilation is covered by :desktop:test in the shard-app test shard.
# Native desktop packaging (createDistributable) only runs in release.yml.
# We disable coverage and desktop builds for PRs to keep feedback fast
# (< 10 mins). Desktop compilation is already covered by the :desktopApp:test
# task in the shard-app test shard.
validate-and-build:
needs: check-changes
if: needs.check-changes.outputs.android == 'true'

View File

@@ -150,9 +150,9 @@ jobs:
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
GOOGLE_PLAY_JSON_KEY: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
run: |
rm -f ./app/google-services.json
echo $GSERVICES > ./app/google-services.json
echo $KEYSTORE | base64 -di > ./app/$KEYSTORE_FILENAME
rm -f ./androidApp/google-services.json
echo $GSERVICES > ./androidApp/google-services.json
echo $KEYSTORE | base64 -di > ./androidApp/$KEYSTORE_FILENAME
echo "$KEYSTORE_PROPERTIES" > ./keystore.properties
echo "datadogApplicationId=$DATADOG_APPLICATION_ID" >> ./secrets.properties
echo "datadogClientToken=$DATADOG_CLIENT_TOKEN" >> ./secrets.properties
@@ -172,14 +172,14 @@ jobs:
run: bundle exec fastlane internal
- name: List outputs
run: ls -R app/build/outputs/
run: ls -R androidApp/build/outputs/
- name: Upload Google AAB artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: google-aab
path: app/build/outputs/bundle/googleRelease/app-google-release.aab
path: androidApp/build/outputs/bundle/googleRelease/androidApp-google-release.aab
retention-days: 1
- name: Upload Google APK artifact
@@ -187,20 +187,20 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: google-apk
path: app/build/outputs/apk/google/release/*.apk
path: androidApp/build/outputs/apk/google/release/*.apk
retention-days: 1
- name: Attest Google AAB provenance
if: success()
uses: actions/attest-build-provenance@v4
with:
subject-path: app/build/outputs/bundle/googleRelease/app-google-release.aab
subject-path: androidApp/build/outputs/bundle/googleRelease/androidApp-google-release.aab
- name: Attest Google APK provenance
if: success()
uses: actions/attest-build-provenance@v4
with:
subject-path: app/build/outputs/apk/google/release/*.apk
subject-path: androidApp/build/outputs/apk/google/release/*.apk
release-fdroid:
runs-on: ubuntu-24.04
@@ -229,7 +229,7 @@ jobs:
KEYSTORE_FILENAME: ${{ secrets.KEYSTORE_FILENAME }}
KEYSTORE_PROPERTIES: ${{ secrets.KEYSTORE_PROPERTIES }}
run: |
echo $KEYSTORE | base64 -di > ./app/$KEYSTORE_FILENAME
echo $KEYSTORE | base64 -di > ./androidApp/$KEYSTORE_FILENAME
echo "$KEYSTORE_PROPERTIES" > ./keystore.properties
- name: Setup Fastlane
@@ -245,21 +245,21 @@ jobs:
run: bundle exec fastlane fdroid_build
- name: List outputs
run: ls -R app/build/outputs/
run: ls -R androidApp/build/outputs/
- name: Upload F-Droid APK artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: fdroid-apk
path: app/build/outputs/apk/fdroid/release/*.apk
path: androidApp/build/outputs/apk/fdroid/release/*.apk
retention-days: 1
- name: Attest F-Droid APK provenance
if: success()
uses: actions/attest-build-provenance@v4
with:
subject-path: app/build/outputs/apk/fdroid/release/*.apk
subject-path: androidApp/build/outputs/apk/fdroid/release/*.apk
release-desktop:
if: ${{ inputs.build_desktop }}
@@ -307,13 +307,13 @@ jobs:
# `-PaboutLibraries.release=true` as member access on `-PaboutLibraries`,
# splitting the token and feeding `.release=true` to Gradle as a task name.
run: >
./gradlew :desktop:packageReleaseDistributionForCurrentOS
${{ contains(runner.os, 'macOS') && ':desktop:packageReleaseUberJarForCurrentOS' || '' }}
./gradlew :desktopApp:packageReleaseDistributionForCurrentOS
${{ contains(runner.os, 'macOS') && ':desktopApp:packageReleaseUberJarForCurrentOS' || '' }}
'-PaboutLibraries.release=true' --no-daemon
- name: List Desktop Binaries
if: runner.os == 'Linux' || runner.os == 'macOS'
run: ls -R desktop/build/compose/binaries/main-release
run: ls -R desktopApp/build/compose/binaries/main-release
- name: Upload Desktop Artifacts
if: always()
@@ -321,13 +321,13 @@ jobs:
with:
name: desktop-${{ runner.os }}-${{ runner.arch }}
path: |
desktop/build/compose/binaries/main-release/*/*.dmg
desktop/build/compose/binaries/main-release/*/*.msi
desktop/build/compose/binaries/main-release/*/*.exe
desktop/build/compose/binaries/main-release/*/*.deb
desktop/build/compose/binaries/main-release/*/*.rpm
desktop/build/compose/binaries/main-release/*/*.AppImage
desktop/build/compose/jars/*-release.jar
desktopApp/build/compose/binaries/main-release/*/*.dmg
desktopApp/build/compose/binaries/main-release/*/*.msi
desktopApp/build/compose/binaries/main-release/*/*.exe
desktopApp/build/compose/binaries/main-release/*/*.deb
desktopApp/build/compose/binaries/main-release/*/*.rpm
desktopApp/build/compose/binaries/main-release/*/*.AppImage
desktopApp/build/compose/jars/*-release.jar
retention-days: 1
if-no-files-found: ignore
@@ -336,13 +336,13 @@ jobs:
uses: actions/attest-build-provenance@v4
with:
subject-path: |
desktop/build/compose/binaries/main-release/*/*.dmg
desktop/build/compose/binaries/main-release/*/*.msi
desktop/build/compose/binaries/main-release/*/*.exe
desktop/build/compose/binaries/main-release/*/*.deb
desktop/build/compose/binaries/main-release/*/*.rpm
desktop/build/compose/binaries/main-release/*/*.AppImage
desktop/build/compose/jars/*-release.jar
desktopApp/build/compose/binaries/main-release/*/*.dmg
desktopApp/build/compose/binaries/main-release/*/*.msi
desktopApp/build/compose/binaries/main-release/*/*.exe
desktopApp/build/compose/binaries/main-release/*/*.deb
desktopApp/build/compose/binaries/main-release/*/*.rpm
desktopApp/build/compose/binaries/main-release/*/*.AppImage
desktopApp/build/compose/jars/*-release.jar
create-flatpak-src:
if: ${{ inputs.build_flatpak_src }}
@@ -370,22 +370,7 @@ jobs:
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
cache_read_only: 'true'
- name: Python Setup
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install ast-grep
run: pip install ast-grep-cli
# Remove Android/iOS targets and other non-desktop bits
# that would break the flatpakGradleGenerator
- name: Prepare Offline Desktop Build
run: ./scripts/desktop-only-prep.sh
- name: Generate Flatpak Sources
env:
DESKTOP_ONLY: true
run: >
./gradlew :build-logic:convention:flatpakGradleGenerator flatpakGradleGenerator
--no-configuration-cache --refresh-dependencies --no-parallel

View File

@@ -94,7 +94,7 @@ jobs:
- name: Lint, Analysis & KMP Smoke Compile
if: inputs.run_lint == true
run: ./gradlew spotlessCheck detekt app:lintFdroidDebug app:lintGoogleDebug core:barcode:lintFdroidDebug core:barcode:lintGoogleDebug core:api:lintDebug kmpSmokeCompile -Pci=true --continue --scan
run: ./gradlew spotlessCheck detekt androidApp:lintFdroidDebug androidApp:lintGoogleDebug core:barcode:lintFdroidDebug core:barcode:lintGoogleDebug core:api:lintDebug kmpSmokeCompile -Pci=true --continue --scan
- name: KMP Smoke Compile (lint skipped)
if: inputs.run_lint == false
@@ -152,13 +152,13 @@ jobs:
# See: https://github.com/meshtastic/Meshtastic-Android/issues/3231
echo "── Step 1: Verify aboutlibraries.json determinism ──"
rm -f app/src/main/resources/aboutlibraries.json
./gradlew :app:exportLibraryDefinitions -Pci=true --no-configuration-cache
cp app/src/main/resources/aboutlibraries.json /tmp/aboutlibraries-run1.json
rm -f androidApp/src/main/resources/aboutlibraries.json
./gradlew :androidApp:exportLibraryDefinitions -Pci=true --no-configuration-cache
cp androidApp/src/main/resources/aboutlibraries.json /tmp/aboutlibraries-run1.json
rm -f app/src/main/resources/aboutlibraries.json
./gradlew :app:exportLibraryDefinitions -Pci=true --no-configuration-cache --rerun-tasks
cp app/src/main/resources/aboutlibraries.json /tmp/aboutlibraries-run2.json
rm -f androidApp/src/main/resources/aboutlibraries.json
./gradlew :androidApp:exportLibraryDefinitions -Pci=true --no-configuration-cache --rerun-tasks
cp androidApp/src/main/resources/aboutlibraries.json /tmp/aboutlibraries-run2.json
if ! diff -q /tmp/aboutlibraries-run1.json /tmp/aboutlibraries-run2.json; then
echo "::error::aboutlibraries.json is NOT deterministic across runs!"
@@ -168,9 +168,9 @@ jobs:
echo "✅ aboutlibraries.json is deterministic"
echo "── Step 2: Build fdroid release APK ──"
./gradlew :app:assembleFdroidRelease -Pci=true -Pmeshtastic.disableAbiSplits=true --no-configuration-cache
./gradlew :androidApp:assembleFdroidRelease -Pci=true -Pmeshtastic.disableAbiSplits=true --no-configuration-cache
APK=$(find app/build/outputs/apk/fdroid/release -name "*.apk" | head -1)
APK=$(find androidApp/build/outputs/apk/fdroid/release -name "*.apk" | head -1)
if [ -z "$APK" ]; then
echo "::error::No fdroid release APK found"
exit 1
@@ -295,7 +295,7 @@ jobs:
# Tests are split into 3 shards that run in parallel:
# shard-core: core:* KMP module tests (allTests)
# shard-feature: feature:* KMP module tests (allTests)
# shard-app: Pure-Android/JVM tests (app, desktop, core:barcode, etc.)
# shard-app: Pure-Android/JVM tests (androidApp, desktopApp, core:barcode, etc.)
test-shards:
runs-on: ubuntu-24.04
permissions:
@@ -359,17 +359,17 @@ jobs:
:feature:settings:koverXmlReport
- name: shard-app
tasks: >-
:app:testFdroidDebugUnitTest
:app:testGoogleDebugUnitTest
:desktop:test
:androidApp:testFdroidDebugUnitTest
:androidApp:testGoogleDebugUnitTest
:desktopApp:test
:core:barcode:testFdroidDebugUnitTest
:core:barcode:testGoogleDebugUnitTest
kover: >-
:app:koverXmlReportFdroidDebug
:app:koverXmlReportGoogleDebug
:androidApp:koverXmlReportFdroidDebug
:androidApp:koverXmlReportGoogleDebug
:core:barcode:koverXmlReportFdroidDebug
:core:barcode:koverXmlReportGoogleDebug
:desktop:koverXmlReport
:desktopApp:koverXmlReport
steps:
- name: Checkout code
@@ -447,14 +447,14 @@ jobs:
cache_read_only: ${{ needs.lint-check.outputs.cache_read_only }}
- name: Build Android APKs
run: ./gradlew app:assembleFdroidDebug app:assembleGoogleDebug -Pci=true --parallel --configuration-cache --continue --scan
run: ./gradlew androidApp:assembleFdroidDebug androidApp:assembleGoogleDebug -Pci=true --parallel --configuration-cache --continue --scan
- name: Upload debug artifact
if: ${{ inputs.upload_artifacts }}
uses: actions/upload-artifact@v7
with:
name: app-debug-apks
path: app/build/outputs/apk/*/debug/*.apk
path: androidApp/build/outputs/apk/*/debug/*.apk
retention-days: 7
- name: Report App Size
@@ -463,8 +463,47 @@ jobs:
echo "### App Size Report" >> $GITHUB_STEP_SUMMARY
echo "| Artifact | Size |" >> $GITHUB_STEP_SUMMARY
echo "| --- | --- |" >> $GITHUB_STEP_SUMMARY
find app/build/outputs/apk -name "*.apk" -exec du -h {} + | awk '{print "| " $2 " | " $1 " |"}' >> $GITHUB_STEP_SUMMARY
find androidApp/build/outputs/apk -name "*.apk" -exec du -h {} + | awk '{print "| " $2 " | " $1 " |"}' >> $GITHUB_STEP_SUMMARY
# ── Desktop Build ───────────────────────────────────────────────────
build-desktop:
name: Build Desktop Debug (${{ matrix.os }})
if: inputs.run_desktop_builds == true
runs-on: ${{ matrix.os }}
permissions:
contents: read
timeout-minutes: 60
needs: lint-check
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]
env:
VERSION_CODE: ${{ needs.lint-check.outputs.version_code }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 1
submodules: true
- name: Gradle Setup
uses: ./.github/actions/gradle-setup
with:
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
cache_read_only: ${{ needs.lint-check.outputs.cache_read_only }}
- name: Build Desktop
run: ./gradlew :desktopApp:createDistributable -Pci=true --scan
- name: Upload Desktop artifact
if: ${{ inputs.upload_artifacts }}
uses: actions/upload-artifact@v7
with:
name: desktop-app-${{ runner.os }}-${{ runner.arch }}
path: desktopApp/build/compose/binaries/main/app/
retention-days: 7
# ── Flatpak Sources ───────────────────────────────────────────────────
build-flatpak-src:
@@ -494,23 +533,8 @@ jobs:
with:
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
cache_read_only: true
- name: Python Setup
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install ast-grep
run: pip install ast-grep-cli
# Remove Android/iOS targets and other non-desktop bits
# that would break the flatpakGradleGenerator
- name: Prepare Offline Desktop Build
run: ./scripts/desktop-only-prep.sh
- name: Generate Flatpak Sources
env:
DESKTOP_ONLY: true
run: >
./gradlew :build-logic:convention:flatpakGradleGenerator flatpakGradleGenerator
--no-configuration-cache --refresh-dependencies --no-parallel

View File

@@ -22,7 +22,7 @@ jobs:
- name: Update firmware releases list
id: firmware
run: |
firmware_file_path="app/src/main/assets/firmware_releases.json"
firmware_file_path="androidApp/src/main/assets/firmware_releases.json"
temp_firmware_file="/tmp/new_firmware_releases.json"
echo "Fetching latest firmware releases..."
@@ -51,7 +51,7 @@ jobs:
- name: Update hardware list
id: hardware
run: |
hardware_file_path="app/src/main/assets/device_hardware.json"
hardware_file_path="androidApp/src/main/assets/device_hardware.json"
temp_hardware_file="/tmp/new_device_hardware.json"
echo "Fetching latest device hardware data..."
@@ -172,8 +172,8 @@ jobs:
base: 'main'
delete-branch: true
add-paths: |
app/src/main/assets/firmware_releases.json
app/src/main/assets/device_hardware.json
androidApp/src/main/assets/firmware_releases.json
androidApp/src/main/assets/device_hardware.json
fastlane/metadata/android/**
**/strings.xml
**/README.md

View File

@@ -23,8 +23,8 @@ When reviewing code, meticulously verify the following categories. Flag any devi
- [ ] **Compose Multiplatform Resources:** Ensure NO hardcoded strings. Must use `core:resources` (e.g., `stringResource(Res.string.key)` or asynchronous `getStringSuspend(Res.string.key)` for ViewModels/Coroutines). NEVER use blocking `getString()` in a coroutine.
- [ ] **String Formatting:** CMP only supports `%N$s` and `%N$d`. Flag any float formats (`%N$.1f`) in Compose string resources; they must be pre-formatted using `NumberFormatter.format()` from `core:common`. Use `MetricFormatter` for metric-specific displays (temperature, voltage, current, percent, humidity, pressure, SNR, RSSI).
- [ ] **Centralized Dialogs & Alerts:** Flag inline alert-rendering logic. Mandate the use of `AlertHost(alertManager)` or `SharedDialogs` from `core:ui/commonMain`.
- [ ] **Placeholders:** Require `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktop/JVM features. No inline placeholders in feature modules.
- [ ] **Adaptive Layouts:** Verify use of `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support desktop/tablet breakpoints (≥ 1200dp).
- [ ] **Placeholders:** Require `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktopApp/JVM features. No inline placeholders in feature modules.
- [ ] **Adaptive Layouts:** Verify use of `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support desktopApp/tablet breakpoints (≥ 1200dp).
### 3. Navigation & State
- [ ] **Shared Navigation Graphs:** Feature navigation graphs must be defined as extension functions on `EntryProviderScope<NavKey>` in `commonMain` (e.g., `fun EntryProviderScope<NavKey>.settingsGraph(...)`). Flag any graphs defined in platform-specific source sets.
@@ -56,8 +56,8 @@ When reviewing code, meticulously verify the following categories. Flag any devi
- [ ] **Robolectric Configuration:** Check that Compose UI tests running via Robolectric on JVM are pinned to `@Config(sdk = [34])` to prevent Java 21 / SDK 35 compatibility issues.
### 8. ProGuard / R8 Rules
- [ ] **New Dependencies:** If a new reflection-heavy dependency is added (DI, serialization, JNI, ServiceLoader), verify keep rules exist in **both** `app/proguard-rules.pro` (R8) and `desktop/proguard-rules.pro` (ProGuard). The two files must stay aligned.
- [ ] **Release Smoke-Test:** For dependency or ProGuard rule changes, verify `assembleRelease` and `./gradlew :desktop:runRelease` succeed.
- [ ] **New Dependencies:** If a new reflection-heavy dependency is added (DI, serialization, JNI, ServiceLoader), verify keep rules exist in **both** `androidApp/proguard-rules.pro` (R8) and `desktopApp/proguard-rules.pro` (ProGuard). The two files must stay aligned.
- [ ] **Release Smoke-Test:** For dependency or ProGuard rule changes, verify `assembleRelease` and `./gradlew :desktopApp:runRelease` succeed.
## Review Output Guidelines
1. **Be Specific & Constructive:** Provide exact file references and code snippets illustrating the required project pattern.

View File

@@ -4,9 +4,9 @@
Guidelines for building shared UI, adaptive layouts, and handling strings/resources in Meshtastic-Android. The codebase uses Material 3 Adaptive.
## 1. UI Components & Layouts
- **Material 3 / Adaptive:** Use `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support Large (1200dp) and XL (1600dp) breakpoints. Investigate 3-pane "Power User" scenes using Navigation 3 Scenes and draggable dividers for desktop/tablets.
- **Material 3 / Adaptive:** Use `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support Large (1200dp) and XL (1600dp) breakpoints. Investigate 3-pane "Power User" scenes using Navigation 3 Scenes and draggable dividers for desktopApp/tablets.
- **Dialogs & Alerts:** Use centralized components like `AlertHost(alertManager)` from `core:ui/commonMain`. Do NOT trigger alerts inline or duplicate alert logic. Use `SharedDialogs(uiViewModel)` for general popups.
- **Placeholders:** Use `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktop/JVM features.
- **Placeholders:** Use `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktopApp/JVM features.
- **Theme Picker:** Use `ThemePickerDialog` from `feature:settings/commonMain`.
- **Platform Implementations:** Inject platform-specific behavior (e.g., Map providers) via `CompositionLocal` from the `app` or `desktop` shells. Do not tightly couple Google Maps/osmdroid dependencies to `commonMain`.
@@ -58,4 +58,4 @@ Choose the right tool for the job:
## Reference Anchors
- **Shared Strings:** `core/resources/src/commonMain/composeResources/values/strings.xml`
- **Platform abstraction contract:** `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt`
- **Provider wiring:** `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt`
- **Provider wiring:** `androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt`

View File

@@ -35,7 +35,7 @@ A step-by-step workflow for implementing a new feature in the Meshtastic-Android
```bash
./gradlew spotlessCheck spotlessApply detekt assembleDebug test allTests
```
- If the feature adds a new reflection-heavy dependency, add keep rules to **both** `app/proguard-rules.pro` and `desktop/proguard-rules.pro`, then verify release builds:
- If the feature adds a new reflection-heavy dependency, add keep rules to **both** `androidApp/proguard-rules.pro` and `desktopApp/proguard-rules.pro`, then verify release builds:
```bash
./gradlew assembleFdroidRelease :desktop:runRelease
./gradlew assembleFdroidRelease :desktopApp:runRelease
```

View File

@@ -52,10 +52,10 @@ Guidelines on managing Kotlin Multiplatform (KMP) source-sets, expected abstract
1. Ensure all new logic compiles against the KMP core (`jvm()`, `iosArm64()`, etc.).
2. Do not use platform-specific constructs in `commonMain` or you break the iOS/Desktop builds.
3. Test using `kmpSmokeCompile` to verify cross-platform compilation.
4. For desktop wiring, copy the pattern in `desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt` and use `NoopStubs.kt` to temporarily mock missing platform implementations.
4. For desktop wiring, copy the pattern in `desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt` and use `NoopStubs.kt` to temporarily mock missing platform implementations.
## Reference Anchors
- **Shared Okio I/O:** `core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ImportProfileUseCase.kt`
- **Desktop DI Stubs:** `desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt`
- **Desktop DI Stubs:** `desktopApp/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt`
- **Version Catalog:** `gradle/libs.versions.toml`
- **Convention Plugins:** `build-logic/convention/`

View File

@@ -7,7 +7,7 @@ This skill covers dependency injection (Koin Annotations 4.2.x) and JetBrains Na
### Guidelines
1. **Annotations First:** Use `@Module`, `@ComponentScan`, and `@KoinViewModel` annotations directly in `commonMain` shared modules to encapsulate dependency graphs per feature.
2. **App Root Assembly:** Don't assume feature/core `@Module` classes are active automatically. Ensure they are included by the app root module (`@Module(includes = [...])`) in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt` and `desktop/.../DesktopKoinModule.kt`.
2. **App Root Assembly:** Don't assume feature/core `@Module` classes are active automatically. Ensure they are included by the app root module (`@Module(includes = [...])`) in `androidApp/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt` and `desktopApp/.../DesktopKoinModule.kt`.
3. **No Platform Bleed:** Don't put Android framework dependencies (`Context`, `Activity`, `Application`) into shared `commonMain` business logic. Inject interfaces instead.
4. **Resolution:** Resolve app-layer wrappers via `koinViewModel()` or injected bindings within Compose navigation graphs.
@@ -49,8 +49,8 @@ startKoin<AndroidKoinApp> {
- **Custom Backstack Mutation:** Do **not** mutate back navigation with custom stacks disconnected from the app backstack. Mutate `NavBackStack<NavKey>` directly with `add(...)` and `removeLastOrNull()`.
## Reference Anchors
- **App Startup / Koin Bootstrap:** `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`
- **DI Bootstrap Object:** `app/src/main/kotlin/org/meshtastic/app/di/AndroidKoinApp.kt`
- **DI App Wiring:** `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`
- **App Startup / Koin Bootstrap:** `androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`
- **DI Bootstrap Object:** `androidApp/src/main/kotlin/org/meshtastic/app/di/AndroidKoinApp.kt`
- **DI App Wiring:** `androidApp/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`
- **Shared Routes:** `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt`
- **Desktop Nav Shell:** `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt`
- **Desktop Nav Shell:** `desktopApp/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt`

View File

@@ -11,7 +11,7 @@ Module directory, namespacing conventions, environment setup, and troubleshootin
| Directory | Description |
| :--- | :--- |
| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
| `androidApp/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.kmp.jvm.android`, `meshtastic.koin`). |
| `config/` | Detekt static analysis rules (`config/detekt/detekt.yml`) and Spotless formatting config (`config/spotless/.editorconfig`). |
| `docs/` | Architecture docs and agent playbooks. See `docs/kmp-status.md` and `docs/roadmap.md` for current status. |
@@ -38,7 +38,7 @@ Module directory, namespacing conventions, environment setup, and troubleshootin
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`, `firmware`, `wifi-provision`, `widget`). All are KMP except `widget`. Use `meshtastic.kmp.feature` convention plugin. |
| `feature/wifi-provision` | KMP WiFi provisioning via BLE (Nymea protocol). Uses `core:ble` Kable abstractions. |
| `feature/firmware` | Fully KMP firmware update system: Unified OTA (BLE + WiFi), native Nordic Secure DFU protocol (pure KMP), USB/UF2 updates, and `FirmwareRetriever` with manifest-based resolution. Desktop is a first-class target. |
| `desktop/` | Compose Desktop application. Thin host shell relying on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports. Versioning via `config.properties` + `GitVersionValueSource`. |
| `desktopApp/` | Compose Desktop application. Thin host shell relying on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports. Versioning via `config.properties` + `GitVersionValueSource`. |
## Namespacing
- **Standard:** Use the `org.meshtastic.*` namespace for all code.

View File

@@ -17,7 +17,7 @@ Run in a single invocation for routine changes to ensure code formatting, analys
> In KMP modules, the `test` task name is **ambiguous**. Gradle matches both `testAndroid` and
> `testAndroidHostTest` and refuses to run either, silently skipping KMP modules.
> `allTests` is the `KotlinTestReport` lifecycle task registered by the KMP plugin.
> Conversely, `allTests` does **not** cover pure-Android modules (`:app`, `:core:api`, etc.), which is why both `test` and `allTests` are needed.
> Conversely, `allTests` does **not** cover pure-Android modules (`:androidApp`, `:core:api`, etc.), which is why both `test` and `allTests` are needed.
*Note: If testing Compose UI on the JVM (Robolectric) with Java 21, pin tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.*

View File

@@ -1 +1 @@
{"feature_directory":"specs/20260511-211823-compose-screenshot-testing"}
{"feature_directory":"specs/006-kmp-project-structure"}

View File

@@ -49,5 +49,5 @@ You are an expert Android/KMP engineer. Maintain architectural boundaries, use M
<!-- SPECKIT START -->
For additional context about technologies to be used, project structure,
shell commands, and other important information, read the current plan
at `specs/20260511-211823-compose-screenshot-testing/plan.md`
at `specs/006-kmp-project-structure/plan.md`
<!-- SPECKIT END -->

View File

@@ -20,7 +20,7 @@ Thank you for your interest in contributing to Meshtastic-Android! We welcome co
- Add comments where necessary, especially for complex logic.
- Keep methods and classes focused and concise.
- **Strings:** Use localised strings via the **Compose Multiplatform Resource** library in `:core:resources`.
- Do **not** use the legacy `app/src/main/res/values/strings.xml`.
- Do **not** use the legacy `androidApp/src/main/res/values/strings.xml`.
- **Definition:** Add strings to `core/resources/src/commonMain/composeResources/values/strings.xml`.
- **Usage:**
```kotlin

View File

@@ -1,7 +1,7 @@
# `:app`
# `:androidApp`
## Overview
The `:app` module is the entry point for the Meshtastic Android application. It orchestrates the various feature modules, manages global state, and provides the main UI shell.
The `:androidApp` module is the entry point for the Meshtastic Android application. It orchestrates the various feature modules, manages global state, and provides the main UI shell.
## Key Components
@@ -9,7 +9,7 @@ The `:app` module is the entry point for the Meshtastic Android application. It
The single Activity of the application. It hosts the shared `MeshtasticNavDisplay` navigation shell and manages the root UI structure (Navigation Bar, Rail, etc.).
### 2. `MeshService`
The core background service that manages long-running communication with the mesh radio. While it is declared in the `:app` manifest for system visibility, its implementation resides in the `:core:service` module. It runs as a **Foreground Service** to ensure reliable communication even when the app is in the background.
The core background service that manages long-running communication with the mesh radio. While it is declared in the `:androidApp` manifest for system visibility, its implementation resides in the `:core:service` module. It runs as a **Foreground Service** to ensure reliable communication even when the app is in the background.
### 3. Koin Application
`MeshUtilApplication` is the Koin entry point, providing the global dependency injection container.
@@ -24,34 +24,34 @@ The module primarily serves as a "glue" layer, connecting:
<!--region graph-->
```mermaid
graph TB
:app[app]:::android-application
:app -.-> :core:ble
:app -.-> :core:common
:app -.-> :core:data
:app -.-> :core:database
:app -.-> :core:datastore
:app -.-> :core:di
:app -.-> :core:domain
:app -.-> :core:model
:app -.-> :core:navigation
:app -.-> :core:network
:app -.-> :core:nfc
:app -.-> :core:prefs
:app -.-> :core:proto
:app -.-> :core:service
:app -.-> :core:resources
:app -.-> :core:ui
:app -.-> :core:barcode
:app -.-> :core:takserver
:app -.-> :feature:intro
:app -.-> :feature:messaging
:app -.-> :feature:connections
:app -.-> :feature:map
:app -.-> :feature:node
:app -.-> :feature:settings
:app -.-> :feature:firmware
:app -.-> :feature:wifi-provision
:app -.-> :feature:widget
:androidApp[androidApp]:::android-application
:androidApp -.-> :core:ble
:androidApp -.-> :core:common
:androidApp -.-> :core:data
:androidApp -.-> :core:database
:androidApp -.-> :core:datastore
:androidApp -.-> :core:di
:androidApp -.-> :core:domain
:androidApp -.-> :core:model
:androidApp -.-> :core:navigation
:androidApp -.-> :core:network
:androidApp -.-> :core:nfc
:androidApp -.-> :core:prefs
:androidApp -.-> :core:proto
:androidApp -.-> :core:service
:androidApp -.-> :core:resources
:androidApp -.-> :core:ui
:androidApp -.-> :core:barcode
:androidApp -.-> :core:takserver
:androidApp -.-> :feature:intro
:androidApp -.-> :feature:messaging
:androidApp -.-> :feature:connections
:androidApp -.-> :feature:map
:androidApp -.-> :feature:node
:androidApp -.-> :feature:settings
:androidApp -.-> :feature:firmware
:androidApp -.-> :feature:wifi-provision
:androidApp -.-> :feature:widget
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;

View File

@@ -116,7 +116,7 @@ configure<ApplicationExtension> {
}
// Disable ABI splits for bundle builds or when explicitly requested via Gradle property.
// Usage: ./gradlew :app:bundleGoogleRelease -Pmeshtastic.disableAbiSplits=true
// Usage: ./gradlew :androidApp:bundleGoogleRelease -Pmeshtastic.disableAbiSplits=true
val disableSplits = providers.gradleProperty("meshtastic.disableAbiSplits").map { it.toBoolean() }.getOrElse(false)
// Enable ABI splits to generate smaller APKs per architecture for F-Droid/IzzyOnDroid

View File

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Some files were not shown because too many files have changed in this diff Show More