Skip to content

Feature/swarm mode - #101

Merged
diegurrio merged 3 commits into
feature/android_appfrom
feature/swarm_mode
Jun 14, 2026
Merged

Feature/swarm mode#101
diegurrio merged 3 commits into
feature/android_appfrom
feature/swarm_mode

Conversation

@diegurrio

Copy link
Copy Markdown
Collaborator

Summary

This PR allows a user to open multiple logs at the same time to visualize each log opened in swarm mode. The change from drone to drone we're using a tap and hold gesture to make use of our menu wheel.

Video Example

Screen_recording_20260612_105640.mp4

Next steps

I want to refine the log picker screen a bit so you can tap and hold to select. Currently the tap and hold gesture on that screen opens up a dialog which was the standar pattern when we could only open a single file.

Staging now copies the whole batch to tmp files and only renames them live (and prunes stale extras) after every copy succeeds, so a mid-batch I/O failure leaves the inbox exactly as the unchanged .ready token describes. The wheel projection treats a consumed release as ending the snapshot, closing the one-poll reopen window when a fresh release seq arrives paired with a stale OPEN phase. The native loader also resets the gesture machine on session swap, the wheel item swap moved into a SideEffect, and system back now exits library selection mode before leaving the screen.
@diegurrio
diegurrio requested review from Copilot and mrpollo June 12, 2026 16:33
@diegurrio diegurrio closed this Jun 12, 2026
@diegurrio diegurrio reopened this Jun 12, 2026
@diegurrio
diegurrio changed the base branch from main to feature/android_app June 12, 2026 16:36
@diegurrio

Copy link
Copy Markdown
Collaborator Author

Hry @MikePehel take a look. Let me know if you want an .apk

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands Hawkeye to support multi-log “swarm mode” and related UX/telemetry improvements, including an Android Compose shell that can stage and launch multiple logs together and a native wheel-gesture state machine for fast drone switching.

Changes:

  • Add a pure C tap-and-hold wheel gesture state machine plus unit tests, and bridge it to Android via a lock-free JNI snapshot/control surface.
  • Add live-session marker parsing (inbox/.live = <millis> <port>) with tests, and improve MAVLink telemetry requests + relative-alt handling to avoid bad origins on reconnect.
  • Introduce/expand Android modular Clean Architecture setup (build-logic conventions, feature modules, Room-backed replay library, DataStore settings, Compose UI, instrumentation/unit tests).

Reviewed changes

Copilot reviewed 45 out of 45 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/test_theme.c Initialize asset path resolution in theme/trail tests.
tests/test_live_marker.c Add unit tests for parsing the .live marker format.
tests/CMakeLists.txt Register new unit tests and link asset-path code into theme tests.
src/wheel_gesture.h Add pure C tap-and-hold gesture state machine for the wheel menu.
src/vehicle.c Switch MTL parsing to Raylib LoadFileText and adjust origin/altitude handling for reconnects.
src/theme.c Resolve theme directory via asset_path() rather than ./themes.
src/mavlink_receiver.h Extend telemetry state with relative-alt fields/validity.
src/mavlink_receiver.c Generalize command sending and request standard telemetry streams + reset relative-alt validity on connect.
src/live_marker.h Add pure parser for the .live inbox marker (<millis> [port]).
src/hud.h Add HUD flags for transport visibility and an extra scale multiplier.
src/hud.c Apply show_transport and scale_mul to HUD layout/scale.
src/asset_path.h Define ASSET_MAX_PATH constant for asset path buffers.
android/settings.gradle.kts Include build-logic and register new Android modules.
android/gradle/wrapper/gradle-wrapper.properties Update Gradle wrapper version.
android/gradle.properties Add opt-in to allow KSP-generated sources via Kotlin source sets.
android/feature/settings/presentation/src/test/kotlin/com/px4/hawkeye/feature/settings/presentation/SettingsViewModelTest.kt Add unit tests for Settings state/actions and port validation behavior.
android/feature/settings/presentation/src/test/kotlin/com/px4/hawkeye/feature/settings/presentation/FakeSettingsRepository.kt Add a fake SettingsRepository for presentation tests.
android/feature/settings/presentation/src/main/res/values/strings.xml Add Settings UI strings.
android/feature/settings/presentation/src/main/kotlin/com/px4/hawkeye/feature/settings/presentation/SettingsViewModel.kt Add Settings ViewModel with validation/persistence behavior.
android/feature/settings/presentation/src/main/kotlin/com/px4/hawkeye/feature/settings/presentation/SettingsState.kt Add Settings state model including raw port input + error.
android/feature/settings/presentation/src/main/kotlin/com/px4/hawkeye/feature/settings/presentation/SettingsAction.kt Add Settings actions (theme/unit/port updates).
android/feature/settings/presentation/src/main/kotlin/com/px4/hawkeye/feature/settings/presentation/di/SettingsPresentationModule.kt Register Settings presentation DI and navigation entry installer.
android/feature/settings/presentation/build.gradle.kts Configure Settings presentation module.
android/feature/settings/domain/src/test/kotlin/com/px4/hawkeye/feature/settings/domain/ThemeResolutionTest.kt Add tests for theme-mode resolution.
android/feature/settings/domain/src/main/kotlin/com/px4/hawkeye/feature/settings/domain/ThemeResolution.kt Add theme-mode resolution helper.
android/feature/settings/domain/src/main/kotlin/com/px4/hawkeye/feature/settings/domain/ThemeMode.kt Add theme-mode enum.
android/feature/settings/domain/src/main/kotlin/com/px4/hawkeye/feature/settings/domain/SettingsRepository.kt Add SettingsRepository contract.
android/feature/settings/domain/src/main/kotlin/com/px4/hawkeye/feature/settings/domain/DistanceUnit.kt Add distance-unit enum.
android/feature/settings/domain/src/main/kotlin/com/px4/hawkeye/feature/settings/domain/AppSettings.kt Add AppSettings domain model.
android/feature/settings/domain/build.gradle.kts Configure Settings domain module.
android/feature/settings/data/src/test/kotlin/com/px4/hawkeye/feature/settings/data/SettingsParsingTest.kt Add tests for settings parsing helpers.
android/feature/settings/data/src/main/kotlin/com/px4/hawkeye/feature/settings/data/di/SettingsDataModule.kt Provide SettingsRepository via DataStore implementation.
android/feature/settings/data/src/main/kotlin/com/px4/hawkeye/feature/settings/data/DataStoreSettingsRepository.kt Implement settings persistence using DataStore Preferences.
android/feature/settings/data/build.gradle.kts Configure Settings data module dependencies.
android/feature/replay/presentation/src/test/kotlin/com/px4/hawkeye/feature/replay/presentation/LibraryEntryUiMapperTest.kt Add tests for size/date formatting.
android/feature/replay/presentation/src/test/kotlin/com/px4/hawkeye/feature/replay/presentation/FakeReplayLibraryRepository.kt Add fake replay library repository for presentation tests.
android/feature/replay/presentation/src/main/res/values/strings.xml Add Replay library strings (multi-select + swarm cap).
android/feature/replay/presentation/src/main/kotlin/com/px4/hawkeye/feature/replay/presentation/ReplayLibraryViewModel.kt Add Replay library ViewModel (import, select, stage, launch).
android/feature/replay/presentation/src/main/kotlin/com/px4/hawkeye/feature/replay/presentation/ReplayLibraryState.kt Add Replay library UI state models.
android/feature/replay/presentation/src/main/kotlin/com/px4/hawkeye/feature/replay/presentation/ReplayLibraryEvent.kt Add Replay library events (picker, launch, error).
android/feature/replay/presentation/src/main/kotlin/com/px4/hawkeye/feature/replay/presentation/ReplayLibraryAction.kt Add Replay library actions including multi-select play-together.
android/feature/replay/presentation/src/main/kotlin/com/px4/hawkeye/feature/replay/presentation/LibraryEntryUiMapper.kt Map domain library entries to UI (size/date formatting).
android/feature/replay/presentation/src/main/kotlin/com/px4/hawkeye/feature/replay/presentation/di/ReplayPresentationModule.kt Register Replay presentation DI.
android/feature/replay/presentation/build.gradle.kts Configure Replay presentation module.
android/feature/replay/data/src/test/kotlin/com/px4/hawkeye/feature/replay/data/FakeReplayLibraryDao.kt Add in-memory DAO fake mirroring Room ordering.
android/feature/replay/data/src/test/kotlin/com/px4/hawkeye/feature/replay/data/FakeReplayFileManager.kt Add fake file manager for repository tests.
android/feature/replay/data/src/main/kotlin/com/px4/hawkeye/feature/replay/data/RoomReplayLibraryRepository.kt Implement Room+filesystem replay library repository (import/delete/stage).
android/feature/replay/data/src/main/kotlin/com/px4/hawkeye/feature/replay/data/ReplayFileManager.kt Define platform-abstract file manager contract.
android/feature/replay/data/src/main/kotlin/com/px4/hawkeye/feature/replay/data/LibraryEntryMappers.kt Add mappers between Room entities and domain models.
android/feature/replay/data/src/main/kotlin/com/px4/hawkeye/feature/replay/data/di/ReplayDataModule.kt Provide Room DB, DAO, file store, and repository via DI.
android/feature/replay/data/src/main/kotlin/com/px4/hawkeye/feature/replay/data/db/ReplayLibraryDao.kt Add Room DAO for replay library entries.
android/feature/replay/data/src/main/kotlin/com/px4/hawkeye/feature/replay/data/db/ReplayDatabase.kt Add Room database definition for replay library.
android/feature/replay/data/src/main/kotlin/com/px4/hawkeye/feature/replay/data/db/LibraryEntryEntity.kt Add Room entity representing a library entry.
android/feature/replay/data/src/main/kotlin/com/px4/hawkeye/feature/replay/data/AndroidReplayFileManager.kt Implement SAF-based file import/name resolution for Android.
android/feature/replay/data/schemas/com.px4.hawkeye.feature.replay.data.db.ReplayDatabase/1.json Add Room exported schema.
android/feature/replay/data/build.gradle.kts Configure replay data module and Room/KSP schema output.
android/feature/live/presentation/src/test/kotlin/com/px4/hawkeye/feature/live/presentation/LiveSetupViewModelTest.kt Add tests for Live setup endpoint building and events.
android/feature/live/presentation/src/test/kotlin/com/px4/hawkeye/feature/live/presentation/FakeDeviceIpProvider.kt Add fake device IP provider for tests.
android/feature/live/presentation/src/main/res/values/strings.xml Add Live setup screen strings.
android/feature/live/presentation/src/main/kotlin/com/px4/hawkeye/feature/live/presentation/LiveSetupViewModel.kt Add Live setup ViewModel (device IP + launch event).
android/feature/live/presentation/src/main/kotlin/com/px4/hawkeye/feature/live/presentation/LiveSetupState.kt Add Live setup UI state (endpoint string).
android/feature/live/presentation/src/main/kotlin/com/px4/hawkeye/feature/live/presentation/LiveSetupEvent.kt Add Live setup events.
android/feature/live/presentation/src/main/kotlin/com/px4/hawkeye/feature/live/presentation/LiveSetupAction.kt Add Live setup actions.
android/feature/live/presentation/src/main/kotlin/com/px4/hawkeye/feature/live/presentation/di/LivePresentationModule.kt Register parameterized Live setup ViewModel via Koin.
android/feature/live/presentation/build.gradle.kts Configure Live presentation module.
android/feature/live/domain/src/main/kotlin/com/px4/hawkeye/feature/live/domain/LiveConnectionState.kt Add connection state enum for live overlays.
android/feature/live/domain/build.gradle.kts Configure Live domain module.
android/feature/live/data/src/main/kotlin/com/px4/hawkeye/feature/live/data/di/LiveDataModule.kt Provide DeviceIpProvider via DI.
android/feature/live/data/src/main/kotlin/com/px4/hawkeye/feature/live/data/AndroidDeviceIpProvider.kt Implement permission-free IPv4 discovery using NetworkInterface.
android/feature/live/data/build.gradle.kts Configure Live data module.
android/feature/home/presentation/src/test/kotlin/com/px4/hawkeye/feature/home/presentation/UsesWidePaneTest.kt Add tests for responsive breakpoint helper.
android/feature/home/presentation/src/test/kotlin/com/px4/hawkeye/feature/home/presentation/HomeViewModelTest.kt Add tests for Home navigation/events and recents.
android/feature/home/presentation/src/test/kotlin/com/px4/hawkeye/feature/home/presentation/FakeReplayLibraryRepository.kt Add fake replay library repo for Home tests.
android/feature/home/presentation/src/main/res/values/strings.xml Add Home screen strings.
android/feature/home/presentation/src/main/res/layout/home_video_player.xml Add Media3 PlayerView layout for background video.
android/feature/home/presentation/src/main/kotlin/com/px4/hawkeye/feature/home/presentation/RecentFlightUiMapper.kt Add mapping for recents subtitle/date.
android/feature/home/presentation/src/main/kotlin/com/px4/hawkeye/feature/home/presentation/HomeViewModel.kt Add Home ViewModel consuming library recents and emitting events.
android/feature/home/presentation/src/main/kotlin/com/px4/hawkeye/feature/home/presentation/HomeVideoBackground.kt Add lifecycle-aware looping background video composable.
android/feature/home/presentation/src/main/kotlin/com/px4/hawkeye/feature/home/presentation/HomeState.kt Add Home UI state models.
android/feature/home/presentation/src/main/kotlin/com/px4/hawkeye/feature/home/presentation/HomeEvent.kt Add Home events (navigate/connect/play recent/error).
android/feature/home/presentation/src/main/kotlin/com/px4/hawkeye/feature/home/presentation/HomeAction.kt Add Home actions.
android/feature/home/presentation/src/main/kotlin/com/px4/hawkeye/feature/home/presentation/di/HomePresentationModule.kt Register Home ViewModel via DI.
android/feature/home/presentation/build.gradle.kts Configure Home presentation module dependencies.
android/core/presentation/src/main/res/values/strings.xml Add shared error strings for UI mapping.
android/core/presentation/src/main/kotlin/com/px4/hawkeye/core/presentation/UiText.kt Add UiText abstraction for dynamic vs resource strings.
android/core/presentation/src/main/kotlin/com/px4/hawkeye/core/presentation/ReplayPlaybackLauncher.kt Add seam for launching native renderer in replay mode.
android/core/presentation/src/main/kotlin/com/px4/hawkeye/core/presentation/ObserveAsEvents.kt Add helper to collect event flows with lifecycle.
android/core/presentation/src/main/kotlin/com/px4/hawkeye/core/presentation/LivePlaybackLauncher.kt Add seam for launching native renderer in live mode.
android/core/presentation/src/main/kotlin/com/px4/hawkeye/core/presentation/DataErrorToUiText.kt Map domain data errors to UI strings.
android/core/presentation/build.gradle.kts Configure core presentation module.
android/core/navigation/src/test/kotlin/com/px4/hawkeye/core/navigation/NavBackStacksTest.kt Add tests for multi-stack shell navigation behavior.
android/core/navigation/src/main/kotlin/com/px4/hawkeye/core/navigation/NavKey.kt Define top-level destinations and navigation keys.
android/core/navigation/src/main/kotlin/com/px4/hawkeye/core/navigation/NavBackStacks.kt Add snapshot-state-backed per-tab back stacks.
android/core/navigation/src/main/kotlin/com/px4/hawkeye/core/navigation/EntryProviderInstaller.kt Add DI seam for feature NavEntry installers.
android/core/navigation/src/main/kotlin/com/px4/hawkeye/core/navigation/di/NavigationModule.kt Add shell navigation DI module placeholder and documentation.
android/core/navigation/build.gradle.kts Configure navigation module and serialization/plugin deps.
android/core/domain/src/test/kotlin/com/px4/hawkeye/core/domain/LivePortTest.kt Add unit tests for listen-port validation.
android/core/domain/src/main/kotlin/com/px4/hawkeye/core/domain/Result.kt Add a typed Result + helpers (map, onSuccess, onFailure).
android/core/domain/src/main/kotlin/com/px4/hawkeye/core/domain/ReplayLibraryRepository.kt Define replay library repository contract in core domain.
android/core/domain/src/main/kotlin/com/px4/hawkeye/core/domain/LivePort.kt Add default live port, range, and validation function.
android/core/domain/src/main/kotlin/com/px4/hawkeye/core/domain/LibraryEntry.kt Add core domain model for imported replay logs.
android/core/domain/src/main/kotlin/com/px4/hawkeye/core/domain/Error.kt Add marker interface for domain errors.
android/core/domain/src/main/kotlin/com/px4/hawkeye/core/domain/DeviceIpProvider.kt Add provider interface for LAN IP lookup.
android/core/domain/src/main/kotlin/com/px4/hawkeye/core/domain/DataError.kt Add domain data error types.
android/core/domain/build.gradle.kts Configure core domain module.
android/core/design-system/src/test/kotlin/com/px4/hawkeye/core/designsystem/wheel/WheelMenuGeometryTest.kt Add geometry tests for wheel slice selection and clamping.
android/core/design-system/src/test/kotlin/com/px4/hawkeye/core/designsystem/DronePaletteTest.kt Add tests verifying 16-color palette and spot-check against native theme.
android/core/design-system/src/main/kotlin/com/px4/hawkeye/core/designsystem/wheel/WheelMenuItem.kt Add wheel menu item/style models and defaults.
android/core/design-system/src/main/kotlin/com/px4/hawkeye/core/designsystem/wheel/WheelMenuGestures.kt Add reference Compose gesture producer for wheel menu.
android/core/design-system/src/main/kotlin/com/px4/hawkeye/core/designsystem/wheel/WheelMenuGeometry.kt Add pure slice math used by wheel menu + tests.
android/core/design-system/src/main/kotlin/com/px4/hawkeye/core/designsystem/Theme.kt Add Material3 theme setup and glass-surface token.
android/core/design-system/src/main/kotlin/com/px4/hawkeye/core/designsystem/DronePalette.kt Add shared drone palette matching native theme defaults.
android/core/design-system/src/main/kotlin/com/px4/hawkeye/core/designsystem/DesignTokens.kt Centralize spacing/alpha/scrim/shadow tokens.
android/core/design-system/build.gradle.kts Configure design-system compose module and test deps.
android/build.gradle.kts Expand top-level plugin aliases for multi-module project.
android/build-logic/settings.gradle.kts Define build-logic composite build and version catalog wiring.
android/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt Add JVM library convention plugin (JUnit5, assertk, turbine, coroutines test).
android/build-logic/convention/src/main/kotlin/com/px4/hawkeye/buildlogic/Libs.kt Add helper accessors for version catalog in convention plugins.
android/build-logic/convention/src/main/kotlin/com/px4/hawkeye/buildlogic/AndroidCompose.kt Add common Compose configuration/dependencies for convention plugins.
android/build-logic/convention/src/main/kotlin/com/px4/hawkeye/buildlogic/AndroidCommon.kt Centralize compile/min/target SDK and Java compatibility config.
android/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt Add Android library convention plugin.
android/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt Add Android library + Compose convention plugin.
android/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt Add feature module convention plugin (Compose + Koin + lifecycle deps).
android/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt Add Android app convention plugin.
android/build-logic/convention/build.gradle.kts Register convention plugins as a Gradle plugin bundle.
android/app/src/test/java/com/px4/hawkeye/android/shell/NavSuiteLayoutTypeTest.kt Add tests for nav suite layout selection logic.
android/app/src/test/java/com/px4/hawkeye/android/render/transport/TransportViewModelTest.kt Add tests for transport polling and actions.
android/app/src/test/java/com/px4/hawkeye/android/render/transport/FakeReplayController.kt Add fake replay controller for transport tests.
android/app/src/test/java/com/px4/hawkeye/android/render/swarm/FakeSwarmController.kt Add fake swarm controller for wheel tests.
android/app/src/test/java/com/px4/hawkeye/android/render/RenderSessionMarkerTest.kt Add tests for writing .live marker file.
android/app/src/test/java/com/px4/hawkeye/android/render/LiveStatusDecodeTest.kt Add tests for decoding native live status float array.
android/app/src/test/java/com/px4/hawkeye/android/render/live/LiveStatusViewModelTest.kt Add tests for live status polling/view model behavior.
android/app/src/main/res/values/themes.xml Simplify activity theme (Compose handles UI theming).
android/app/src/main/res/values/strings.xml Add shell/overlay strings (nav, transport, live status, swarm wheel).
android/app/src/main/res/values/colors.xml Remove legacy MaterialComponents color resources.
android/app/src/main/res/values-night/themes.xml Remove legacy night theme override.
android/app/src/main/res/mipmap-anydpi/ic_launcher.xml Point monochrome layer to a dedicated themed-icon asset.
android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml Point monochrome layer to a dedicated themed-icon asset.
android/app/src/main/res/drawable/ic_launcher_monochrome.xml Add monochrome adaptive icon vector for Android themed icons.
android/app/src/main/res/drawable/ic_launcher_foreground.xml Remove legacy launcher foreground vector.
android/app/src/main/java/com/px4/hawkeye/android/shell/ShellViewModel.kt Add ViewModel to keep nav back stacks across config changes.
android/app/src/main/java/com/px4/hawkeye/android/replay/AndroidReplayPlaybackLauncher.kt Implement replay launcher seam to start renderer with drone labels.
android/app/src/main/java/com/px4/hawkeye/android/render/transport/TransportViewModel.kt Add transport overlay ViewModel (poll status, forward actions).
android/app/src/main/java/com/px4/hawkeye/android/render/transport/TransportState.kt Add transport overlay state model.
android/app/src/main/java/com/px4/hawkeye/android/render/transport/TransportScreen.kt Add Compose UI for transport overlay bar.
android/app/src/main/java/com/px4/hawkeye/android/render/transport/TransportAction.kt Define transport actions.
android/app/src/main/java/com/px4/hawkeye/android/render/SwarmController.kt Add swarm controller interface + JNI-backed implementation + snapshot model.
android/app/src/main/java/com/px4/hawkeye/android/render/swarm/SwarmWheelViewModel.kt Add swarm wheel overlay ViewModel (poll native gesture, build items, select).
android/app/src/main/java/com/px4/hawkeye/android/render/swarm/SwarmWheelState.kt Add swarm wheel overlay state + item UI model.
android/app/src/main/java/com/px4/hawkeye/android/render/swarm/SwarmWheelAction.kt Define swarm wheel actions.
android/app/src/main/java/com/px4/hawkeye/android/render/RenderMode.kt Add render-mode enum (REPLAY vs LIVE).
android/app/src/main/java/com/px4/hawkeye/android/render/RendererLauncher.kt Centralize renderer launch + .live marker writing for live mode.
android/app/src/main/java/com/px4/hawkeye/android/render/NativeReplayController.kt Add replay controller JNI surface + status model.
android/app/src/main/java/com/px4/hawkeye/android/render/NativeLiveStatusController.kt Add live status JNI surface + decoder + model.
android/app/src/main/java/com/px4/hawkeye/android/render/live/LiveStatusViewModel.kt Add ViewModel polling native live status.
android/app/src/main/java/com/px4/hawkeye/android/render/live/LiveStatusScreen.kt Add Compose UI for live connection status strip.
android/app/src/main/java/com/px4/hawkeye/android/MainActivity.kt Add Compose launcher activity using settings-driven theme mode.
android/app/src/main/java/com/px4/hawkeye/android/live/AndroidLivePlaybackLauncher.kt Implement live launcher seam (port + resolved device IP).
android/app/src/main/java/com/px4/hawkeye/android/HawkeyeApp.kt Start Koin only in main process to avoid Room contention with renderer process.
android/app/src/main/java/com/px4/hawkeye/android/di/AppModule.kt Wire app-level DI (shell VM + launchers).
android/app/src/main/cpp/swarm_jni.c Implement JNI bridge and publish/take functions for swarm control.
android/app/src/main/cpp/swarm_control.h Define lock-free snapshot/request bridge for swarm wheel overlay.
android/app/src/main/cpp/replay_control.h Extend/define lock-free replay/live status bridge header.
android/app/src/main/cpp/CMakeLists.txt Expand native build sources to include replay, HUD, MAVLink, and JNI bridges.
android/app/src/main/AndroidManifest.xml Add INTERNET permission, introduce MainActivity launcher, isolate renderer process/activity.
android/app/src/androidTest/java/com/px4/hawkeye/android/ShellNavigationTest.kt Add instrumentation test for shell navigation + rotation resilience.
android/app/src/androidTest/java/com/px4/hawkeye/android/SettingsPortFieldTest.kt Add instrumentation test verifying port-field error rendering.
android/app/src/androidTest/java/com/px4/hawkeye/android/LiveStatusScreenTest.kt Add instrumentation tests for live status strip text per state.
android/app/src/androidTest/java/com/px4/hawkeye/android/LiveSetupScreenTest.kt Add instrumentation tests for live setup screen rendering/actions.
android/app/src/androidTest/java/com/px4/hawkeye/android/LibraryRecentsMultiItemTest.kt Add device test seeding Room DAO and verifying Home/Replay recents behavior.
android/app/build.gradle.kts Switch to convention plugin and add module dependencies/test setup.
.gitignore Ignore an additional Android docs path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@MikePehel

Copy link
Copy Markdown
Contributor

Hry @MikePehel take a look. Let me know if you want an .apk

Yeah, send me a .apk.

@diegurrio
diegurrio merged commit 340db51 into feature/android_app Jun 14, 2026
@diegurrio
diegurrio deleted the feature/swarm_mode branch June 14, 2026 15:37
diegurrio added a commit that referenced this pull request Aug 8, 2026
* [Android] Introduce camera controls and fix asset loading on Android.

* [Android] finish replay prototype. Add confirmation and warning dialogs when a .ulg file is opened.

* Review feedback

* Merge conflicts after rebasing with main.

* Add HUD bar to Android

* Add UI scale factor for the HUD

* Introduce a proper mobile application architecture that can be used to maintain and scale the Android app easily.

* Review feedback

* Add Compose application shell with Navigation 3 and Material 3

Replaces booting directly into the native renderer with a Compose shell that
becomes the launcher and surfaces the app's modes.

- core:navigation module: type-safe navigation keys, a multi-back-stack
  holder, and a per-feature entry-provider seam wired through Koin
- MainActivity hosting an adaptive navigation suite (bottom bar on phones,
  side rail in landscape) built on Jetpack Navigation 3, with a Home hub and
  a Settings destination
- feature:home: a hub screen with mode cards
- feature:settings: theme (System/Light/Dark) and distance units persisted
  with DataStore; the selected theme drives the app's light and dark appearance
- RenderSession and RendererLauncher: a typed handoff from the shell to the
  native renderer; the existing share/open .ulg intent path is unchanged
- Directional slide transitions between destinations
- Material 3 theming with a dark heads-up display palette

Replay and simulator screens are present as clearly labeled placeholders for
now. The shell rotates freely; the native renderer stays landscape.

* Add a looping video background and brand styling to the Home screen

- Play a muted, looping background video behind the Home screen and its nav
  bar (scoped to the Home tab) using AndroidX Media3 / ExoPlayer; the asset is
  bundled as a trimmed ~57s clip.
- Apply the Dronecode Foundation palette: green primary, cyan secondary.
- Add a theme-aware translucent glass surface (one reusable token) for the nav
  bar and the mode cards so the video shows through: a light glass in light
  mode and a dark glass in dark mode.
- Add a subtle drop shadow to the title for legibility over the video.

* Address review feedback: design tokens, ViewModel nav, string resources

- core:design-system: add reusable tokens (HawkeyeDimens spacing, HawkeyeAlpha
  opacities, ScrimColor, MediaTitleShadow) and route glassSurface through them;
  replace inline dp/alpha/color/shadow literals across the Home, shell, and
  Settings screens with the named tokens.
- Hoist the navigation back stacks into a ShellViewModel (injected via Koin)
  instead of remember{}, so the selected tab and back stacks survive rotation.
- Move user-facing strings to string resources (home, settings, app shell) and
  map ThemeMode/DistanceUnit to string resources; use stringResource throughout.
- Thread HomeState into HomeScreen and add a Settings screen preview, matching
  the MVI Root/Screen contract.
- Make the video shutter color theme-aware (set from the theme in code) instead
  of a hard-coded hex in the layout XML.
- Update README deploy/test commands to target .MainActivity and
  .IntentRouterActivity now that HawkeyeActivity is exported=false.

* Finish replay implementation

* Fix home screen on landscape mode, new launcher icon and other miscellaneous fixes

* Compile the MAVLink backend into the Android renderer lib

* Select live MAVLink mode at renderer startup from an inbox marker

* Add live render mode, session marker, and launcher seam

* Launch the renderer in live mode from the Connect action

* Keep live sessions in a waiting state when MAVLink bind fails

Commit to live mode as soon as the live inbox token wins, before the
MAVLink bind attempt. On bind failure the renderer now stays in the
live waiting state instead of falling through to the inbox replay load,
which would have run with both the native bar and the Compose transport
overlay suppressed and left playback uncontrollable.

Also drop launchMode=singleTop from HawkeyeActivity so every launch is a
fresh instance with a fresh Intent, making the cached isLiveMode safe
without relying on the absence of an onNewIntent reuse path.

* Add a live connection UI with a configurable port and real-device telemetry

Replace the one-tap, hardcoded-port live launch with a proper feature:live
module. Home Connect now opens a Live setup screen that shows the device IP and
the endpoint to point PX4 at, then launches the renderer. The listen port is a
Settings preference (validated, persisted via DataStore) and is carried to the
native renderer through the inbox marker, which now encodes the millis token
and the port and stays backward compatible with a bare token.

Surface live connection state in the renderer: a lock-free status snapshot
(waiting/connected/lost, sysid, port) is published from the render thread and
read over JNI, and HawkeyeActivity shows a status strip in live mode instead of
suppressing the overlay.

Teach the MAVLink receiver standard telemetry so a real vehicle (and non-SIH
SITL) renders, not just the HIL simulation stream: decode ATTITUDE_QUATERNION
and GLOBAL_POSITION_INT into the same state fields as HIL, and request those
streams on connect via SET_MESSAGE_INTERVAL. This is shared with desktop.

* Add test coverage for the live UI and move overlay polling into the ViewModels

Address the code review of the live connection feature.

Tests (all offline, no SITL or device):
- core:domain LivePortTest for validateListenPort (boundaries, whitespace,
  non-numeric, negative).
- Extract a pure decodeLiveStatus and cover the size guard, state mapping and
  fallback; LiveStatusDecodeTest.
- LiveStatusViewModelTest with a fake controller; a Settings reseed-guard case
  so an in-progress invalid port survives a later settings emission.
- Extract the live-marker parser into src/live_marker.h and add a host test
  (token + port, legacy bare token, out-of-range fallback, garbage, NULL),
  wired into ctest.
- Instrumented Compose tests with faked state: LiveSetupScreen, LiveStatusScreen
  per connection state, and the Settings port-field error.

Move the native-status polling loop out of the Root composables and into the
ViewModels (viewModelScope) for both the replay transport and the live status
overlays, so the ViewModel owns state and cadence and the Root just collects
with collectAsStateWithLifecycle. Both overlays now follow the same pattern.

Wire the previously dead OnRefreshIp action to a Retry affordance shown when the
device IP can't be resolved.

* Fix live-mode altitude when connecting mid-flight

Connecting to a live session while the vehicle was airborne rendered the drone on
the floor. Two causes, both fixed:

- vehicle_update latched the origin to the first telemetry sample, so reconnecting
  mid-flight made the current airborne position the origin and relative altitude
  computed to zero. It now latches the origin to HOME (the takeoff/ground point)
  when HOME_POSITION is available.

- HOME_POSITION is only sent on request and is not in the periodic GCS stream, so a
  forwarded or listen-only client never receives it. On the standard-telemetry path,
  take altitude from GLOBAL_POSITION_INT.relative_alt (height above home, always
  streamed); HIL has no relative_alt and falls back to MSL minus the origin.

Shared with desktop.

* Make the live-connection copy vehicle-agnostic

The live path now decodes standard PX4 telemetry (ATTITUDE_QUATERNION +
GLOBAL_POSITION_INT), so it connects to a real vehicle, not just the SIH
simulator. Reword the Home card, the Live screen title, and the instruction
from "simulator" to "vehicle" so the copy fits both. Update the two
instrumented-test constants that assert on the card text.

* Add back buttons to the Replay and Live top bars

Both pushed screens now show an M3 back arrow (Icons.AutoMirrored.Filled.ArrowBack)
in the top app bar, wired to pop the shell back stack, so they are consistent
(Replay previously had no back affordance). Register Replay's NavEntry in AppShell
like Home and Live so it can supply onBack; the feature module now just provides
the ViewModel. Add the BOM-managed material-icons-core dependency to the Compose
convention plugin for the icon.

* Use the primary color for the Replay Open file button

The Open file ExtendedFloatingActionButton used the default tonal container.
Set its container to the theme primary with onPrimary content.

* Address PR review: real test assertions and reset relative_alt validity

- LiveSetupScreenTest used kotlin assert(), which is a no-op unless JVM assertions
  are enabled (they are not on Android instrumented runs), so the action-dispatch
  checks could never fail. Switch to JUnit assertTrue.
- Reset state.relative_alt_valid on each new MAVLink connection so a HIL-only
  reconnect can't reuse a stale relative altitude from a prior standard-telemetry
  session.

* Feature/wheel menu (#100)

* Add tap-and-hold gesture state machine for the wheel menu

* Add wheel menu slice geometry to the design system

* Add the gesture-agnostic wheel menu state driver

* Add the reusable HUD Ring wheel menu composable

* Add the wheel menu gesture producer and instrumented tests

* Cancel the wheel long press when the pointer drifts past touch slop

* Harden the wheel gesture producer against cancellation and stale callbacks

Treat a tracked change that arrives already consumed as a cancel so a
system ACTION_CANCEL or a competing handler closes the wheel without
selecting. Read onSelected through rememberUpdatedState inside a
composed modifier so the latest composed callback is always invoked.
Bail out of opening when the long press was retargeted to another
pointer or a second finger is down, matching the C machine's
PENDING to REJECTED transition.

Instrumented tests now assert the hover before release and derive the
long-press wait from the device ViewConfiguration instead of a
hardcoded constant.

* Guard wheel state edge cases and defer widget reads to the draw phase

Clear the hover when items are swapped while the wheel is open and
guard release() against an index past the current list, so a submenu
swap can never report a stale selection. Resolve no hover while the
hit geometry is unsynced (dead zone radius still zero). Rename the
rememberWheelMenuState parameter to initialItems and document the
seam method contracts.

In the widget, move the items, center, and hover reads inside the
Canvas draw lambda so hover changes invalidate only the draw phase,
extract the px-radii sync into SyncWheelHitGeometry, size the text
measurer cache to the item count, measure slice labels single line,
and expose select hint and hovered label through semantics.

* Pin wheel geometry boundaries and extend the C gesture test coverage

Cover the wrap-around region just left of twelve o'clock, the exact
dead-zone radius boundary, and the degenerate clampCenter case where
the surface is smaller than the wheel. On the C state machine, cover
a second finger during PENDING, a move of exactly the slop distance,
and a second full open and release cycle on the same struct.

* Address PR review: clamp the full wheel rim on-screen and align the CMake m-link guard

* Feature/swarm mode (#101)

* Swarm mode

* Address review: atomic batch staging and wheel projection hardening

Staging now copies the whole batch to tmp files and only renames them live (and prunes stale extras) after every copy succeeds, so a mid-batch I/O failure leaves the inbox exactly as the unchanged .ready token describes. The wheel projection treats a consumed release as ending the snapshot, closing the one-poll reopen window when a fresh release seq arrives paired with a stale OPEN phase. The native loader also resets the gesture machine on session swap, the wheel item swap moved into a SideEffect, and system back now exits library selection mode before leaving the screen.

* Fix submodule option for Android builds

* Add long-press multi-select and batch delete to the replay library (#102)

Tap-and-hold a log now enters selection mode and selects that row, matching the familiar Android pattern. The Select button stays as the alternate entry point. In selection mode the top bar shows a Delete action that removes every selected log behind a confirmation dialog; opening is unchanged on the Play together FAB.

Adds ReplayLibraryRepository.deleteAll, which resolves all ids first so an unknown id fails the batch before any file is removed. Replaces the single-file long-press delete dialog with the selection-driven flow.

Covered by ViewModel unit tests, RoomReplayLibraryRepository tests, and a new on-device ReplayLibrarySelectionTest.

* Feature/new flight view (#103)

* Update AGP

* Add view switching via nested wheel menu

Bring the desktop renderer's cameras and orthographic views to Android, reachable through a nested radial wheel: Chase, FPV and Free-track cameras, the six ortho views (Top, Bottom, Front, Back, Left, Right), and the 3-up ortho side panel.

Selecting a view drives the shared scene_update_camera with the selected drone's position and rotation; touch maps to each view's params (orbit for chase, gimbal for FPV, pan and zoom for ortho), while free-track keeps the existing orbit handler. View switching crosses the JVM boundary through a lock-free control surface (view_control) and JNI, mirroring the swarm controller.

The wheel is a general nested menu. A single-drone session shows the view types directly; a swarm nests them under two branches whose slices read Views and Drones (with the fuller Change View and Select Drone echoed in the hub), the latter reusing the drone-selection path. Drilling in is a dwell on a parent slice; there is no go-back gesture, so a submenu is left by selecting a leaf or releasing in the hub to cancel. The native gesture machine is unchanged, and the wheel arms in single-drone, multi-drone and live sessions.

The 3-up panel takes a top inset, fed the measured media-bar height, so its top view is no longer hidden behind the transport bar.

* Implement deleteAll in the home library test fake

Commit 419ca08 added deleteAll to ReplayLibraryRepository and updated the real implementation and the replay module's fake, but missed FakeReplayLibraryRepository in feature:home:presentation. That left the home module's unit tests unable to compile (aborting the aggregate unit-test run). Implement the missing override so they compile and pass.

* Publish an Android APK with every release (#105)

Add an android-apk job to the release workflow so a v* tag produces an
APK alongside the existing tarball, bottle, .deb, and Windows zip.

The APK is unsigned: there is no signing config or keystore secret in the
repo, so the asset is named -unsigned and has to be signed with your own
key before it will install.

Version now derives from the tag. CI passes -PhawkeyeVersionName and
Gradle computes versionCode as major*1000000 + minor*1000 + patch,
mirroring the -DHAWKEYE_VERSION the desktop jobs already pass to CMake.
Parsing is strict so a malformed tag fails the build instead of silently
shipping a version code lower than the previous release.

Move APK verification into android/scripts/verify-release-apk.sh, run by
both the release job and a new release-build job in android CI, so the
tag-time path is exercised on every push to main rather than first
running on a live tag. Extract the shared toolchain setup into a
composite action so the NDK and CMake versions are pinned in one place.

Widen the android CI path filter to the repo-root src, lib, and asset
directories, which the native library and assets actually depend on.

Document all of it: a new Releasing page, an Android section on the
install page, release build and signing steps in the android README, and
Android troubleshooting entries. Also correct the .deb install command,
which globbed hawkeye-*.deb against an artifact named hawkeye_*.deb, and
rewrite the stale "Loading flight logs" section, which described an
IntentRouterActivity and VIEW/SEND intent handling that do not exist.

* Fix Android CI failing on the sdkmanager pipeline

A composite action's `shell: bash` runs with -o pipefail, unlike a
workflow run step, which uses plain `bash -e`. Moving the toolchain
install into the composite action therefore changed the shell semantics:
sdkmanager stops reading once licenses are accepted, `yes` dies on
SIGPIPE with 141, and pipefail turned that into a job failure.

Disable pipefail for that one pipeline. sdkmanager's own exit status is
still checked by -e.

Fix the same class of bug in verify-release-apk.sh, where grep -q exits
on the first match and could kill the writing side of the pipe. It works
today only because the APK listing is 37KB and fits in the pipe buffer;
adding an ABI or more assets would have made the script report present
entries as missing and fail every release. Use a herestring instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants