Files
Meshtastic-Android/desktop
James Rich eb3a27a3d3 feat(auto): append outgoing reply to MessagingStyle for brief confirmation
Before cancelling a conversation notification in response to an inline
reply, post one final update that appends the outgoing text to the
MessagingStyle history, attributed to the local user. This gives
assistants such as Android Auto a tick to observe the sent message in
the notification's message history and surface a 'reply sent' style
confirmation before markConversationRead cancels the notification.

Extract the 'me' Person construction into buildMePerson() and share it
between showGroupSummary and createConversationNotification. The
conversation builder now optionally takes an extraOutgoingMessage which
is appended to the MessagingStyle (actions and when-timestamp continue
to be anchored on the last incoming message).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 09:44:08 -05:00
..

:desktop — Meshtastic Desktop

A Compose Desktop application target — the first full non-Android target for the shared KMP module graph. This module serves as:

  1. First multi-target milestone — Proves the KMP architecture supports real application targets beyond Android.
  2. Build smoke-test — Validates that all core:* KMP modules compile and link on a JVM Desktop target.
  3. Shared navigation proof — Uses the same Navigation 3 routes from core:navigation and the same NavDisplay + entryProvider pattern as the Android app, proving the shared backstack architecture works cross-target.
  4. Desktop app scaffold — A working Compose Desktop application with a NavigationRail for top-level destinations and placeholder screens for each feature.

Quick Start

# Run the desktop app
./gradlew :desktop:run

# Run tests
./gradlew :desktop:test

# Package native distribution (DMG/MSI/DEB) — debug (no ProGuard)
./gradlew :desktop:packageDistributionForCurrentOS

# Package native distribution (DMG/MSI/DEB) — release (ProGuard minified)
./gradlew :desktop:packageReleaseDistributionForCurrentOS

ProGuard / Minification

Release builds use ProGuard for tree-shaking (unused code removal), significantly reducing distribution size. Obfuscation is disabled since the project is open-source. Rules are aligned with the Android R8 rules in app/proguard-rules.pro — both targets share the same anti-class-merging philosophy.

Configuration:

  • build.gradle.ktsbuildTypes.release.proguard block enables ProGuard with optimize.set(true) and obfuscate.set(false).
  • proguard-rules.pro — Keep-rules for reflection/JNI-sensitive dependencies (Koin, kotlinx-serialization, Wire protobuf, Room KMP androidx.room3, Ktor, Kable BLE, Coil, SQLite JNI, Compose Multiplatform resources) and an anti-merge rule for Compose animation classes.

Key rules:

  • Compose animation anti-merge (-keep class androidx.compose.animation.** { *; }) — Prevents ProGuard's optimizer from incorrectly tree-shaking or merging animation class hierarchies (e.g. EnterTransition/ExitTransition into *Impl), which causes animations to silently snap. Same rule as Android.
  • Room KMP — Uses androidx.room3 package path (Room KMP 3.x).

Troubleshooting ProGuard issues:

  • If the release build crashes at runtime with ClassNotFoundException or NoSuchMethodError, a library is loading classes via reflection that ProGuard stripped. Add a -keep rule in proguard-rules.pro and the corresponding rule in app/proguard-rules.pro to keep both targets aligned.
  • To debug which classes ProGuard removes, temporarily add -printusage proguard-usage.txt to the rules file and inspect the output in desktop/proguard-usage.txt.
  • To see the full mapping of optimizations applied, add -printseeds proguard-seeds.txt.
  • Run ./gradlew :desktop:runRelease for a quick smoke-test of the minified app before packaging.

Architecture

The module depends on the JVM variants of KMP modules:

  • core:common, core:model, core:di, core:navigation, core:repository
  • core:domain, core:data, core:database, core:datastore, core:prefs
  • core:network, core:resources, core:ui

Navigation: Uses JetBrains multiplatform forks of Navigation 3 (org.jetbrains.androidx.navigation3:navigation3-ui) and Lifecycle (org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose, lifecycle-runtime-compose). A unified SavedStateConfiguration with polymorphic SerializersModule is provided centrally by core:navigation for non-Android NavKey serialization. Desktop utilizes the exact same navigation graph wiring (settingsGraph, nodesGraph, contactsGraph, connectionsGraph) directly from the commonMain of their respective feature modules, maintaining full UI parity.

Coroutines: Requires kotlinx-coroutines-swing for Dispatchers.Main on JVM/Desktop. Without it, any code using lifecycle.coroutineScope or Dispatchers.Main (e.g., NodeRepositoryImpl, RadioConfigRepositoryImpl) will crash at runtime.

DI: A Koin DI graph is bootstrapped in Main.kt with platform-specific implementations injected.

UI: JetBrains Compose for Desktop with Material 3 theming. Desktop acts as a thin host shell, delegating almost entirely to fully shared KMP UI modules. Includes native macOS notification support (via TrayState and bundleID identification) and a monochrome SVG tray icon for a native look and feel.

Notifications: Implements the common NotificationManager interface via DesktopNotificationManager. Repository-level notifications (messages, node events, alerts) are collected in Main.kt and forwarded to the system tray. macOS requires a consistent bundleID (configured in build.gradle.kts) and the NSUserNotificationAlertStyle key in Info.plist for notifications to appear correctly in the distributable.

Localization: Desktop exposes a language picker, persisting the selected BCP-47 tag in UiPreferencesDataSource.locale. Main.kt applies the override to the JVM default Locale and uses a staticCompositionLocalOf-backed recomposition trigger so Compose Multiplatform stringResource() calls update immediately without recreating the Navigation 3 backstack.

Key Files

File Purpose
Main.kt App entry point — Koin bootstrap, Compose Desktop window, theme + locale application
DemoScenario.kt Offline demo data for testing without a connected device
ui/DesktopMainScreen.kt Navigation 3 shell — NavigationRail + NavDisplay
navigation/DesktopNavigation.kt Nav graph entry registrations for all top-level destinations (delegates to shared feature graphs)
radio/DesktopRadioTransportFactory.kt Provides TCP, Serial/USB, and BLE transports
notification/DesktopMeshServiceNotifications.kt Real implementation of notification triggers for Desktop
DesktopNotificationManager.kt Bridge between repository notifications and Compose TrayState
radio/DesktopMeshServiceController.kt Mesh service lifecycle — orchestrates want_config handshake chain
radio/DesktopMessageQueue.kt Message queue for outbound mesh packets
di/DesktopKoinModule.kt Koin module with stub implementations
di/DesktopPlatformModule.kt Platform-specific Koin bindings
stub/NoopStubs.kt No-op implementations for all repository interfaces

What This Validates

Module What's Tested
core:common Base64Factory, NumberFormatter, UrlUtils, DateFormatter, CommonUri
core:model DeviceVersion, Capabilities, SfppHasher, platformRandomBytes, getShortDateTime, Channel.getRandomKey
core:ui Shared Compose components compile and render on Desktop
Build graph All core modules compile and link without Android SDK

Roadmap

  • Implement real navigation with shared core:navigation routes (Navigation 3 shell)
  • Adopt JetBrains multiplatform forks for lifecycle and navigation3
  • Implement native macOS/Desktop notification support with TrayState and system tray
  • Wire feature:settings composables into the nav graph (first real feature — ~30 screens)
  • Wire feature:node composables into the nav graph (node list with shared ViewModel + NodeItem)
  • Wire feature:messaging composables into the nav graph (contacts list with shared ViewModel)
  • Add JetBrains Material 3 Adaptive ListDetailPaneScaffold to node and messaging screens
  • Implement TCP transport (DesktopRadioTransportFactory) with auto-reconnect and backoff retry
  • Implement mesh service controller (DesktopMeshServiceController) with full want_config handshake
  • Create connections screen using shared feature:connections with dynamic transport detection
  • Replace 5 placeholder config screens with real desktop implementations (Device, Position, Network, Security, ExtNotification)
  • Add desktop language picker backed by shared UiPreferencesDataSource.locale with live translation updates
  • Wire remaining feature:* composables (map) into the nav graph
  • Move remaining node detail and message composables from androidMain to commonMain
  • Add serial/USB transport for direct radio connection on Desktop
  • Add BLE transport (via Kable) for direct radio connection on Desktop
  • Add MQTT transport for cloud-connected operation
  • Package as native distributions (DMG, MSI, DEB) via CI release pipeline