Skip to content

feat: introduce Desktop target and expand Kotlin Multiplatform (KMP) architecture#4761

Merged
jamesarich merged 13 commits intomainfrom
feat/kmp-jvm-desktop
Mar 12, 2026
Merged

feat: introduce Desktop target and expand Kotlin Multiplatform (KMP) architecture#4761
jamesarich merged 13 commits intomainfrom
feat/kmp-jvm-desktop

Conversation

@jamesarich
Copy link
Copy Markdown
Collaborator

This commit marks a major milestone by introducing the :desktop module, the first full non-Android target, and significantly expanding the KMP shared module graph. Core business logic, ViewModels, and UI components have been migrated to commonMain to support JVM targets, facilitating platform parity and code reuse.

Key changes include:

  • Desktop Module Implementation:

    • Created the :desktop module using Compose for Desktop and Navigation 3.
    • Implemented a NavigationRail shell with adaptive list-detail layouts for Nodes and Messaging using Material 3 Adaptive.
    • Developed DesktopRadioInterfaceService using a shared TcpTransport with auto-reconnect and backoff logic.
    • Added DesktopMeshServiceController to manage the handshaking and message-processing lifecycle in-process.
  • Feature & ViewModel Refactoring:

    • Extracted :feature:connections from the app module to share device discovery and connection UI.
    • Moved core ViewModels (MessageViewModel, NodeListViewModel, SettingsViewModel, etc.) to commonMain and annotated them with @KoinViewModel.
    • Introduced BaseUIViewModel to share application-level state (alerts, unread counts, firmware checks) across platforms.
    • Migrated feature screens (Metrics, Administration, About, Filter Settings) and components (Message bubbles, Node items) to commonMain.
  • Core Infrastructure & Transport:

    • Replaced the Android-specific IRadioInterface with a platform-agnostic RadioTransport interface.
    • Introduced StreamFrameCodec and TcpTransport in core:network to share framing and socket logic between Android and JVM.
    • Added DirectRadioControllerImpl for in-process service interaction on Desktop and future targets.
    • Abstracted platform-specific capabilities (NFC, Barcode decoding, Map providers) behind CompositionLocal providers.
  • Shared Testing Framework:

    • Created the :core:testing module to provide centralized test doubles (FakeNodeRepository, FakeRadioController, FakePacketRepository) for use in commonTest across all modules.
  • Utilities & Maintenance:

    • Replaced platform-dependent utilities (Base64, NumberFormatter, UrlUtils) with pure Kotlin implementations in core:common.
    • Introduced CommonUri and CommonParcelable abstractions to handle platform-specific URI and serialization logic.
    • Added support for runtime locale switching via UiPreferencesDataSource.
    • Updated build logic and CI workflows to include JVM smoke compilation and Desktop verification.

Specific changes:

  • Deleted Android-specific ViewModel wrappers in the app module.
  • Relocated and refactored dozens of UI components to align with KMP source set structures.
  • Updated AGENTS.md and README.md to reflect the multi-target architecture and development guidelines.

…architecture

This commit marks a major milestone by introducing the `:desktop` module, the first full non-Android target, and significantly expanding the KMP shared module graph. Core business logic, ViewModels, and UI components have been migrated to `commonMain` to support JVM targets, facilitating platform parity and code reuse.

Key changes include:

- **Desktop Module Implementation:**
  - Created the `:desktop` module using Compose for Desktop and Navigation 3.
  - Implemented a `NavigationRail` shell with adaptive list-detail layouts for Nodes and Messaging using Material 3 Adaptive.
  - Developed `DesktopRadioInterfaceService` using a shared `TcpTransport` with auto-reconnect and backoff logic.
  - Added `DesktopMeshServiceController` to manage the handshaking and message-processing lifecycle in-process.

- **Feature & ViewModel Refactoring:**
  - Extracted `:feature:connections` from the `app` module to share device discovery and connection UI.
  - Moved core ViewModels (`MessageViewModel`, `NodeListViewModel`, `SettingsViewModel`, etc.) to `commonMain` and annotated them with `@KoinViewModel`.
  - Introduced `BaseUIViewModel` to share application-level state (alerts, unread counts, firmware checks) across platforms.
  - Migrated feature screens (Metrics, Administration, About, Filter Settings) and components (Message bubbles, Node items) to `commonMain`.

- **Core Infrastructure & Transport:**
  - Replaced the Android-specific `IRadioInterface` with a platform-agnostic `RadioTransport` interface.
  - Introduced `StreamFrameCodec` and `TcpTransport` in `core:network` to share framing and socket logic between Android and JVM.
  - Added `DirectRadioControllerImpl` for in-process service interaction on Desktop and future targets.
  - Abstracted platform-specific capabilities (NFC, Barcode decoding, Map providers) behind `CompositionLocal` providers.

- **Shared Testing Framework:**
  - Created the `:core:testing` module to provide centralized test doubles (`FakeNodeRepository`, `FakeRadioController`, `FakePacketRepository`) for use in `commonTest` across all modules.

- **Utilities & Maintenance:**
  - Replaced platform-dependent utilities (`Base64`, `NumberFormatter`, `UrlUtils`) with pure Kotlin implementations in `core:common`.
  - Introduced `CommonUri` and `CommonParcelable` abstractions to handle platform-specific URI and serialization logic.
  - Added support for runtime locale switching via `UiPreferencesDataSource`.
  - Updated build logic and CI workflows to include JVM smoke compilation and Desktop verification.

Specific changes:
- Deleted Android-specific ViewModel wrappers in the `app` module.
- Relocated and refactored dozens of UI components to align with KMP source set structures.
- Updated `AGENTS.md` and `README.md` to reflect the multi-target architecture and development guidelines.

Signed-off-by: James Rich <[email protected]>
@github-actions github-actions bot added the enhancement New feature or request label Mar 12, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 7.41771% with 1997 lines in your changes missing coverage. Please review.
✅ Project coverage is 12.17%. Comparing base (f4364cf) to head (2ea6c3e).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...n/kotlin/org/meshtastic/core/ui/emoji/EmojiData.kt 0.00% 1222 Missing ⚠️
.../meshtastic/core/network/transport/TcpTransport.kt 0.00% 107 Missing ⚠️
...rg/meshtastic/core/ui/viewmodel/BaseUIViewModel.kt 0.00% 91 Missing ⚠️
...shtastic/core/service/DirectRadioControllerImpl.kt 0.00% 58 Missing ⚠️
.../org/meshtastic/core/ui/emoji/EmojiPickerDialog.kt 0.00% 49 Missing ⚠️
...rg/meshtastic/core/common/util/JvmPlatformUtils.kt 0.00% 47 Missing ⚠️
...meshtastic/core/service/MeshServiceOrchestrator.kt 0.00% 36 Missing ⚠️
...g/meshtastic/core/service/ServiceRepositoryImpl.kt 0.00% 32 Missing ⚠️
...tic/feature/connections/AndroidScannerViewModel.kt 0.00% 31 Missing ⚠️
...ic/core/common/util/SyncContinuation.jvmAndroid.kt 0.00% 24 Missing ⚠️
... and 61 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4761      +/-   ##
==========================================
+ Coverage   10.77%   12.17%   +1.39%     
==========================================
  Files         517      531      +14     
  Lines       16067    17726    +1659     
  Branches     2586     2647      +61     
==========================================
+ Hits         1732     2158     +426     
- Misses      14040    15255    +1215     
- Partials      295      313      +18     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

…tings

This commit introduces a centralized mechanism for resolving Android product flavors within the build logic and refactors the settings feature to support locale configuration. It also improves test stability and resource handling in the connections feature.

Specific changes include:

- **Build Logic**:
  - Introduced `FlavorResolution.kt` to provide a unified `configureAndroidMarketplaceFallback` strategy, allowing configurations to automatically infer the correct marketplace flavor (e.g., Google or F-Droid) based on the configuration name.
  - Applied the new flavor resolution logic to `KmpLibraryConventionPlugin`.
  - Removed redundant manual attribute configuration from `feature/settings/build.gradle.kts`.

- **Settings Feature**:
  - Updated `AndroidSettingsViewModel` to include `SetLocaleUseCase`, enabling locale management within the application settings.
  - Added `@Suppress("LongParameterList")` to the ViewModel to accommodate necessary dependency growth.

- **Connections Feature**:
  - Refactored `ScannerViewModelTest` to use `mockk` for `RadioController` instead of a fake implementation and replaced the mock `GetDiscoveredDevicesUseCase` with an anonymous object for better reliability.
  - Improved `CommonGetDiscoveredDevicesUseCase` by adding a safety `runCatching` block when fetching the "demo_mode" string resource to prevent crashes during initialization.
  - Added `kotlin("test")` dependency to `commonTest` in `feature/connections`.

Signed-off-by: James Rich <[email protected]>
This commit enables Gradle Build Scans for the core publication, dependency submission, and reusable check workflows to improve build observability and debugging.

Specific changes include:
- **`publish-core.yml`**: Configured `gradle/actions/setup-gradle` to enable Build Scan publishing and accepted the terms of use.
- **`reusable-check.yml`**: Added the `--scan` flag to Gradle execution commands for code style analysis, unit tests, and KMP JVM compilation.
- **`dependency-submission.yml`**: Updated the `build-scan-terms-of-use-url` to the current Gradle terms of service URL.

Signed-off-by: James Rich <[email protected]>
…dules

This commit performs a widespread refactoring to improve code conciseness and leverage Kotlin's null safety more effectively. It primarily focuses on removing redundant null-coalescing operators (`?:`) where the underlying property is already non-nullable or has a sensible default, and simplifying logic in UI components and data managers.

Specific changes include:

- **Settings Feature**:
    - Removed redundant `?: 0`, `?: false`, and `?: ""` defaults in various configuration screens (Audio, Display, Telemetry, Serial, User, Network, MQTT, etc.) where form state values are already guaranteed to be non-null.
    - Updated `DeviceConfigScreen` and `DisplayConfigScreen` to suppress deprecation warnings where necessary.
    - Simplified logic for interval dropdown selections by removing unnecessary null checks.

- **Node Feature**:
    - Refactored `TracerouteLog`, `NeighborInfoLog`, and `NodeDetailActions` to remove redundant null checks on `long_name` and `short_name`.
    - Cleaned up metric displays (Pax, Power, Signal, Device, Environment) by removing unnecessary null safety operators on timestamps and telemetry values.
    - Updated `NodeStatusIcons` to explicitly set `TooltipAnchorPosition.Above`.
    - Fixed a permission annotation in `AndroidPhoneLocationProvider`.

- **Core Modules**:
    - **Data**: Simplified property access in `NodeManagerImpl` and `MqttManagerImpl`.
    - **Model**: Removed redundant null checks in `Node`, `NodeInfo`, and `Channel` models. Updated `hasPKC` and `mismatchKey` logic for clarity.
    - **UI**: Refactored `SecurityIcon`, `ProtoExtensions`, and `EditListPreference` to streamline property access and remove unnecessary null handling.
    - **Database**: Cleaned up key existence checks in `NodeInfoDao` and simplified name matching logic.

- **Build Logic**:
    - Removed the deprecated `android.newDsl=false` property from `gradle.properties`.
    - Migrated `ExportDataUseCase` to use `kotlin.time.Instant` instead of `kotlinx.datetime.Instant`.

Signed-off-by: James Rich <[email protected]>
…gation

This commit introduces a dedicated provider for the AboutLibraries JSON data to decouple resource loading from the navigation logic and suppresses linting warnings in complex configuration screens.

Specific changes include:
- **Infrastructure**: Introduced `AboutLibrariesJsonProvider` in the `app` module to handle loading the `aboutlibraries.json` resource from the classpath with graceful error handling and logging via Kermit.
- **Navigation**: Updated `SettingsNavigation.kt` to use the new `AboutLibrariesJsonProvider` for the `AboutScreen`, removing the direct dependency on Android-specific `LocalContext` and raw resources.
- **Linting**: Added `LongMethod` suppression to several configuration screens (`DisplayConfigScreen`, `DeviceConfigScreen`, `CannedMessageConfigScreen`) to allow for complex Composable structures.
- **Maintenance**: Updated copyright headers for 2026 in new files.

Signed-off-by: James Rich <[email protected]>
This commit refactors how test dependencies are managed across Kotlin Multiplatform (KMP) modules by introducing centralized configuration in the build logic. It also updates several unit and integration tests to align with recent API changes and improved mocking patterns.

Key changes include:

- **Build Logic & Infrastructure:**
  - Introduced `configureKmpTestDependencies()` in `KotlinAndroid.kt` to automatically provide `kotlin("test")` to `commonTest` and `androidHostTest` source sets.
  - Updated `KmpLibraryConventionPlugin` to apply these shared test dependencies, reducing boilerplate in individual `build.gradle.kts` files.
  - Updated `AGENTS.md` documentation to reflect that test framework dependencies are now managed via the convention plugin.

- **Test Refactoring & Mocking:**
  - Updated `MessageViewModelTest` and `NodeListViewModelTest` with explicit type hints and proper flow mocking for `RadioConfigRepository`, `ServiceRepository`, and other dependencies to improve type safety and test stability.
  - Fixed parameter names and types in `SettingsViewModelTest` and `TestDataFactory` (e.g., adding `setLocaleUseCase` and `lastHeard`).
  - Adjusted `NodeErrorHandlingTest` to match `FakeNodeRepository` behavior where a fallback node is returned instead of null.
  - Renamed `nodeRepository.clear()` to `clearNodeDB()` in `MessagingIntegrationTest` to match the updated repository API.

- **Dependency Cleanup:**
  - Removed manual `kotlin("test")` declarations from various `build.gradle.kts` files in `core` and `feature` modules, as they are now handled by the convention plugin.

Signed-off-by: James Rich <[email protected]>
This commit updates several Android convention plugins to use more specific extension types and adds documentation explaining the architectural rationale behind the current plugin structure.

Specific changes include:
- Updated `AndroidApplicationComposeConventionPlugin` to use `ApplicationExtension` instead of `CommonExtension`.
- Updated `AndroidLibraryComposeConventionPlugin` to use `LibraryExtension` instead of `CommonExtension`.
- Added KDoc to Compose and Flavor convention plugins to clarify why separate application and library plugins are maintained despite implementation similarities.
- Cleaned up unused imports and added missing Gradle DSL imports in `KotlinAndroid.kt`.

Signed-off-by: James Rich <[email protected]>
… plugin

This commit refactors how shared code between JVM and Android targets is handled across the project. It replaces manual `sourceSets` configuration with a new convention plugin that leverages Kotlin's `HierarchyTemplate` DSL to safely define a `jvmAndroid` group.

Specific changes include:
- **Build Logic**: Created `KmpJvmAndroidConventionPlugin` and associated logic in `KotlinAndroid.kt` to automate the creation of the `jvmAndroid` source set hierarchy.
- **Convention Registration**: Registered the `meshtastic.kmp.jvm.android` plugin ID in `build-logic/convention/build.gradle.kts`.
- **Module Migration**: Applied the new plugin and removed boilerplate `dependsOn` configurations in `core:ui`, `core:network`, `core:common`, and `core:model`.
- **Cleanup**: Removed redundant `kotlin("test")` dependencies from various `build.gradle.kts` files as they are now handled by the base KMP library convention.
- **Documentation**: Updated `AGENTS.md` to instruct developers to use the new convention plugin instead of hand-wiring source set edges.

Signed-off-by: James Rich <[email protected]>
This commit updates the static mock path in `MeshConnectionManagerImplTest` to reflect the relocation of the `getString` utility.

Specific changes:
- Updated `mockkStatic` and `unmockkStatic` calls to reference `org.meshtastic.core.resources.GetStringKt` instead of `org.meshtastic.core.resources.ContextExtKt`.

Signed-off-by: James Rich <[email protected]>
This commit removes Android-specific logging mocks from the `MeshDataHandlerTest` setup. This cleanup ensures the `commonTest` suite remains platform-agnostic and avoids unnecessary dependencies on the Android framework.

Specific changes include:
- Removed `mockkStatic(android.util.Log::class)` and the associated stubs for `Log.d`, `Log.i`, `Log.w`, and `Log.e` in the `setUp` method.
- Removed the unused `io.mockk.mockkStatic` import.

Signed-off-by: James Rich <[email protected]>
…ViewModel

This commit updates several integration and unit tests in the `firmware` and `map` features to align with recent ViewModel API changes and dependency updates.

Specific changes include:

- **Firmware Feature Tests:**
    - Updated `FirmwareUpdateViewModel` instantiation in tests to include missing mocked dependencies: `FirmwareUpdateManager`, `FirmwareUsbManager`, and `FirmwareFileHandler`.
    - Migrated test assertions from `viewModel.updateState` to the renamed `viewModel.state`.
    - Refactored `myNodeInfo` test checks to access the value directly from `nodeRepository` instead of the ViewModel.
    - Simplified mock configurations by removing redundant `every { ... } returns emptyFlow()` calls on relaxed mocks.

- **Map Feature Tests:**
    - Cleaned up `BaseMapViewModelTest` and `MapFeatureIntegrationTest` by removing unused `emptyFlow` imports and simplifying `packetRepository` mocking logic.

Signed-off-by: James Rich <[email protected]>
This commit enhances the test coverage and reliability by providing more realistic mock data for `BaseMapViewModel`, `FirmwareUpdateViewModel`, and related integration tests. Previously, many mocks were using purely relaxed behavior without defined flows, which could lead to unexpected nullability issues or silent failures during testing.

Specific changes include:
- **Map Feature Tests**:
    - Defined default `MutableStateFlow` values for `MapPrefs` properties including `showOnlyFavorites`, `showWaypointsOnMap`, `showPrecisionCircleOnMap`, and filters.
    - Mocked `packetRepository.getWaypoints()` to return an empty flow instead of a default null/relaxed value.
- **Firmware Feature Tests**:
    - Initialized `nodeRepository` with concrete `MyNodeInfo` and `Node` mocks, including hardware model (`TBEAM`), firmware version (`2.5.0`), and node numbers.
    - Configured `radioPrefs`, `firmwareReleaseRepository`, and `firmwareUpdateManager` with appropriate flows (e.g., `devAddr`, `stableRelease`, `dfuProgressFlow`).
    - Added successful `Result` returns for `deviceHardwareRepository` and dismissed status for `bootloaderWarningDataSource`.
- **Test Logic**:
    - Updated `testMyNodeInfoAccessible` in `FirmwareUpdateViewModelTest` to reflect that `myNodeInfo` is now properly initialized in the test setup.

Signed-off-by: James Rich <[email protected]>
Signed-off-by: James Rich <[email protected]>
@jamesarich jamesarich added this pull request to the merge queue Mar 12, 2026
Merged via the queue into main with commit ac6bb54 Mar 12, 2026
6 checks passed
@jamesarich jamesarich deleted the feat/kmp-jvm-desktop branch March 12, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant