Commit Graph

6836 Commits

Author SHA1 Message Date
James Rich
aed1c42286 chore(map): bump MapLibre Compose to 0.13.0, light up desktop base map, drop Google Maps leftovers
Rebased onto main and dusted off the MapLibre Compose Multiplatform branch.

- Bump maplibre-compose 0.12.1 -> 0.13.0 and migrate the redesigned location API
  (BearingUpdate.TRACK_LOCATION -> TRACK_AUTOMATIC; LocationPuck locationState -> location).
- desktopApp: add a host-detecting maplibre-native-bindings-jni runtime backend so the
  desktop base map renders (Metal on macOS arm64, OpenGL on Linux/Windows amd64). Without it
  Gradle links no native renderer and the map canvas is black.
- Gate Compose map overlays off on the JVM target via a mapOverlaysSupported expect/actual flag
  (true Android/iOS, false desktop). maplibre-compose 0.13.0 stubs the desktop layers/sources
  API with TODO(), so composing markers/waypoints/tracks/traceroute threw NotImplementedError
  and tore down the window. Desktop now renders base-map-only; overlays auto-enable when upstream
  implements desktop layers.
- Remove orphaned Google Maps leftovers the rebase carried over from main's #5702/#5709 marker
  work (MarkerBitmapRenderer.kt + play-services-maps); this branch replaces Google Maps entirely.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 13:30:52 -05:00
James Rich
b7cdc26a6e fix(map): pulse indicator fires on new packet, not permanent online status
Change the pulsing ring from showing for all online nodes to only
nodes heard within the last 5 seconds. This correctly indicates when
a new packet arrives rather than acting as a static online badge.

- Add 'recently_heard' boolean property to GeoJSON features
- Use Clock.System.now().epochSeconds with periodic tick (1s) to
  expire stale pulse states
- Filter pulse layer on 'recently_heard' instead of 'is_online'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 12:10:53 -05:00
James Rich
1e6a4235eb feat(map): add pulsing online indicator and satellite tile style
- Add animated pulsing ring behind online nodes using Compose
  InfiniteTransition (expanding radius + fading opacity)
- Add Satellite map style using free Esri World Imagery raster tiles
- Use BaseStyle.Json for inline raster style definition
- Derive baseStyle from selectedMapStyle (single source of truth)
- Update MapStyleTest to verify both Uri and Json style variants
- Update MapViewModelTest to use toBaseStyle() assertions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 12:10:53 -05:00
James Rich
34ec760412 fix(map): align with Meshtastic design standards and M3 best practices
- Replace hardcoded hex colors with MaterialTheme.colorScheme tokens
  (primary, onPrimary, onSurfaceVariant, surface) so map layers respect
  light/dark mode transitions
- Add TooltipBox with PlainTooltip to MapButton for desktop hover
  accessibility (design standard §4: tooltips for icon-only buttons)
- Set explicit containerColor and scrimColor on NodeInfoSheet's
  ModalBottomSheet for M3 compliance
- Import MaterialTheme in MaplibreMapContent, TracerouteLayers, and
  InlineMap to read semantic color tokens in composable scope

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 12:10:53 -05:00
James Rich
883bb6ae48 feat(map): add node info bottom sheet on map tap
Show a compact bottom sheet with node name, last heard, battery, and
signal info when tapping a node marker. Users can tap 'View Details' to
navigate to the full node detail screen, preserving map context.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 12:10:53 -05:00
James Rich
4607c3f223 feat(map): inline map polish & location permission feedback
- InlineMap: add short name SymbolLayer label above marker
- InlineMap: adaptive zoom (zoom out for imprecise positions >500m)
- MapScreen: show snackbar when tapping location button without
  permission instead of silently doing nothing
- Add map_location_unavailable string resource

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 12:10:53 -05:00
James Rich
305ff2321f feat(map): add line gradient, zoom buttons, camera padding
- NodeTrackLayers: replace flat color with lineProgress() gradient
  (faded blue → vivid blue showing position age)
- MapControlsOverlay: add +/- zoom buttons in secondary toolbar
  (improves desktop/accessibility where pinch isn't natural)
- MapScreen: add padding to zoom-to-fit-all camera animation
  to avoid UI controls overlap
- Add lineProgress() expression helper (line-progress MapLibre expr)
- Add MeshtasticIcons.Remove (minus) icon

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 12:10:53 -05:00
James Rich
653955406e feat(map): add error/empty states, filter badge, and waypoint feedback
- Wire onMapLoadFail to Snackbar error message
- Add MapEmptyState overlay when no nodes have position data
- Add active filter count badge on filter button (BadgedBox)
- Show snackbar confirmation on waypoint send/delete
- Add string resources: map_empty_state, map_load_error,
  map_showing_filtered, waypoint_sent, waypoint_deleted

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 12:10:16 -05:00
James Rich
18dac2eb09 fix(map): resolve rebase issues and detekt violations
- Remove phantom dd-sdk-android-compose dependency (not in version catalog)
- Deduplicate strings.xml (29 duplicate entries from rebase conflicts)
- Add missing waypoint_lock_to_my_node string resource
- Fix license header year format (2025-2026 → 2026)
- Rename past-tense lambda params to present tense (detekt)
- Use rememberUpdatedState for lambdas in LaunchedEffect
- Fix composable parameter ordering (non-defaults before modifier)
- Cap cluster zoom increment at max zoom level (24)
- Suppress ModifierMissing on OfflineMapContent expect/actual

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 12:09:07 -05:00
James Rich
177f97abdd feat(map): online status indicators, zoom-to-fit-all, GeoJSON enrichment
- Add is_online and battery_level properties to node GeoJSON features
- Node marker strokes now show green (online) or gray (offline) using
  switch/condition expressions on the is_online boolean property
- Node labels display a colored status dot (●) via format/span rich text
- Add 'Zoom to Fit All Nodes' action in filter dropdown menu, computing
  bounding box from filteredNodes and animating camera with animateTo()
- Add 4 new GeoJSON converter tests for is_online and battery_level
2026-06-02 12:09:07 -05:00
James Rich
c76aa0856d refactor(map): DRY constants, shared bounding box, i18n fix, CI test fixes
- Extract NODE_MARKER_RADIUS, MARKER_STROKE_WIDTH, PRECISION_CIRCLE_STROKE_ALPHA
  to MapConstants.kt — eliminates duplicates across MaplibreMapContent, InlineMap,
  and TracerouteLayers
- Extract computeBoundingBox() utility — deduplicates identical code in
  NodeTrackMap and TracerouteMap
- Replace hardcoded "Unknown" in TracerouteLayers with stringResource(Res.string.unknown)
- Add ioDispatcher constructor parameter to BaseMapViewModel/MapViewModel — tests
  pass testDispatcher directly, eliminating flaky delay(100) race conditions
- Remove dead manualDestNum flow from NodeMapViewModel, simplify destNumFlow
- Tighten visibility: TracerouteNodeSelection, GeoJsonConverters, MapConstants,
  MapLayerItem/LayerType → internal
- Remove redundant elvis operators on non-null proto fields (build warnings)
- Fix assert() → assertTrue() in MapStyleTest for Kotlin/Native compatibility
- Remove unnecessary !! assertions in GeoJsonConvertersTest
- Add computeBoundingBox tests (null for <2 positions, correct bounds for 3+)
2026-06-02 12:09:07 -05:00
James Rich
0b16123be3 feat(map): material3 controls + latitude-aware precision circles
- Add DisappearingScaleBar overlay (bottom-start) that auto-shows on zoom
  change and hides after 3 seconds, using CameraState.metersPerDpAtTarget
- Add ExpandingAttributionButton overlay (bottom-end) for tile provider
  attribution display (legal compliance), auto-dismisses on map gesture
- Thread StyleState from MapScreen → MaplibreMapContent → MaplibreMap to
  provide source attribution data for the attribution button
- Use LocationPuckDefaults.colors() for Material 3 themed location puck
  (derives colors from MaterialTheme.colorScheme instead of hardcoded blue)
- Replace hardcoded METERS_PER_PIXEL_ZOOM15 equatorial constant in InlineMap
  with CameraState.metersPerDpAtTarget for latitude-aware precision circles
2026-06-02 12:09:07 -05:00
James Rich
a3c32f180f fix(map): audit best practices — OnlyLogo ornaments, always-visible location puck, rounded line caps
- OrnamentOptions.AllEnabled → OnlyLogo since custom MapControlsOverlay
  already provides compass and controls (avoids duplicate native ornaments)
- Location puck now visible whenever location is available, not only when
  tracking is enabled (standard map UX — blue dot always shows position)
- Add LineCap.Round + LineJoin.Round to all route and track LineLayer
  instances for smooth corners instead of jagged defaults
2026-06-02 12:09:07 -05:00
James Rich
a0deb90dec refactor(map): polish — DRY coordinate helpers, tighter visibility, dead code removal, null safety
- Extract toGeoPositionOrNull() into MapConstants.kt, replacing 8 duplicated
  coordinate-conversion patterns across GeoJsonConverters, TracerouteLayers,
  TracerouteMap, NodeTrackMap, InlineMap, and MapScreen
- Extract typedFeatureCollection() helper to centralize the single unavoidable
  UNCHECKED_CAST, eliminating 9 scattered @Suppress annotations
- Fix hardcoded style URI in InlineMap — now uses MapStyle.OpenStreetMap.toBaseStyle()
- Tighten visibility: internal on MapButton, NodeTrackLayers, TracerouteLayers;
  private on BaseMapViewModel.nodes
- Fix null safety: replace waypoint!!.id with safe mapNotNull pattern
- Remove dead code: getUser(), myId (BaseMapViewModel); mapStyleId, applicationId,
  setDestNum, mapPrefs (NodeMapViewModel)
- Remove redundant empty onFrame={} in MaplibreMapContent
- Rename COORDINATE_PRECISION to FORMAT_DECIMAL_FACTOR in EditWaypointDialog
- Update stale KDoc on BaseMapViewModel and MapButton; add KDoc on FeatureMapModule,
  LayerType, MapLayerItem, MapNavigation.mapGraph
- Add 11 new tests: toGeoPositionOrNull (4), typedFeatureCollection (1),
  convertIntToEmoji fallback (1), combined filters (1), MapStyle.toBaseStyle (3),
  MapStyle defaults (1)
2026-06-02 12:09:07 -05:00
James Rich
ec6ebce04d refactor(map): architectural improvements — DRY, UDF, dead code, test coverage
- Extract COORDINATE_SCALE to shared MapConstants.kt, removing 6 duplicate
  private const declarations across MapScreen, GeoJsonConverters, InlineMap,
  NodeTrackMap, TracerouteLayers, and TracerouteMap
- Move node filtering from MapScreen composition into BaseMapViewModel as
  filteredNodes StateFlow (testable, avoids composition-time computation)
- Move waypoint construction from MapScreen's inline onSend callback into
  MapViewModel.createAndSendWaypoint() for testability and separation
- Remove unused compassBearing property from MapViewModel (bearing is read
  directly from cameraState.position.bearing in MapScreen)
- Add nodes parameter to TracerouteMap for short name resolution on hop
  markers (was hardcoded to emptyMap, falling back to hex node nums)
- Add GeoJsonConvertersTest with 25 tests covering nodesToFeatureCollection,
  waypointsToFeatureCollection, positionsToLineString, positionsToPointFeatures,
  precisionBitsToMeters, intToHexColor, and convertIntToEmoji
- Expand BaseMapViewModelTest from 5 to 21 tests covering filter toggles,
  preference persistence, mapFilterState composition, filteredNodes with
  favorites/last-heard/any filters, and getNodeOrFallback
- Expand MapViewModelTest from 9 to 12 tests covering createAndSendWaypoint
  with new/edit/locked/no-position scenarios
2026-06-02 12:09:07 -05:00
James Rich
bce998d060 fix(map): fix formatCoord to produce fixed decimal places instead of scientific notation 2026-06-02 12:09:07 -05:00
James Rich
9cdac11ec0 fix(map): address review round 2 — precision circles, traceroute, i18n
- Fix precision circle radius: use zoom-based exponential interpolation
  to convert meters to pixels instead of treating meters as dp values
- Fix InlineMap precision circle: compute pixel radius from meters at
  the fixed zoom-15 display level
- Fix TracerouteLayers: wrap callback in LaunchedEffect to avoid state
  updates during composition; add nodes to remember keys for fresh hop
  labels; use relatedNodeNums.size for accurate total count
- Fix compass bearing: use epsilon comparison (±0.5°) instead of
  exact float equality to prevent flickering near north
- Localize EditWaypointDialog: replace hardcoded English strings with
  stringResource() using existing waypoint_edit/waypoint_new resources
- Format coordinates to 6 decimal places in waypoint position display
2026-06-02 12:09:07 -05:00
James Rich
17dadf79c6 fix(map): address code review findings — precision, naming, icons, i18n
- Fix Int.toFloat() precision loss in track point filter by storing
  time as string in GeoJSON and using string-based equality comparison
- Rename MapStyle enum values to match actual tile styles: Satellite→Light
  (Positron), Hybrid→RoadMap (Americana), with updated string resources
- Reset bearingUpdate to IGNORE when gesture cancels location tracking
- Use LocationOn icon for ALWAYS_NORTH tracking mode instead of
  misleading LocationDisabled
- Remove dead isOfflineManagerAvailable() expect/actual declarations
- Replace hardcoded English strings in offline map UI with
  stringResource() calls backed by core:resources entries
2026-06-02 12:09:07 -05:00
James Rich
2725a5f3ae fix(map): delete leftover fdroid OSMDroid cluster Java files breaking lint
The MarkerClusterer, RadiusMarkerClusterer, and StaticCluster Java files
under app/src/fdroid/java/ were missed during the MapLibre migration and
still referenced the removed osmdroid dependency, causing lintFdroidDebug
to fail on CI.
2026-06-02 12:09:07 -05:00
James Rich
6167d32d26 feat(map): add maplibre-compose API enhancements — scale bar, bearing tracking, gestures, hillshade, offline tiles, map styles
Leverage underused maplibre-compose 0.12.1 APIs to improve UX parity:

- OrnamentOptions: enable built-in scale bar on all map screens
- GestureOptions: per-screen gesture control (Standard, PositionLocked,
  RotationLocked, ZoomOnly) based on tracking state
- BearingUpdate 3-state cycling: Off → Track+Bearing → Track+North → Off
  with CameraMoveReason.GESTURE auto-cancel
- Offline tile downloads: expect/actual OfflineManagerFactory with
  Android/iOS actuals using rememberOfflineManager + OfflinePackListItem
- HillshadeLayer + RasterDemSource: terrain visualization with free AWS
  Terrarium tiles when Terrain style is selected
- Map loading callbacks: onMapLoadFinished/onMapLoadFailed propagated
- Map styles: all 5 styles now use distinct URIs (Liberty, Positron,
  Bright, Americana, Fiord)
- NodeTrackLayers: fix selected highlight filter expression
- LocationProviderFactory: check permissions before calling
  rememberDefaultLocationProvider to prevent PermissionException
2026-06-02 12:09:07 -05:00
James Rich
1fb3f2fe53 chore: ignore Eclipse/Buildship IDE metadata files 2026-06-02 12:09:06 -05:00
James Rich
6b736c401e feat(map): add feature parity — filters, style selector, waypoint dialog, cluster zoom, bounds fitting, location tracking
Wire remaining map feature gaps identified in the parity audit:

- MapFilterDropdown: favorites, waypoints, precision circle toggles and
  last-heard slider matching the old Google/OSMDroid filter UIs
- MapStyleSelector: dropdown with 5 predefined MapStyle entries
- EditWaypointDialog: create, edit, delete waypoints via long-press or
  marker tap, with icon picker and lock toggle
- Cluster zoom-to-expand: tap a cluster circle to zoom +2 levels
  centered on the cluster position
- Bounds fitting: NodeTrackMap and TracerouteMap compute a BoundingBox
  from all positions and animate the camera to fit on first load
- Location tracking: expect/actual rememberLocationProviderOrNull()
  bridges platform GPS into maplibre-compose LocationPuck with
  LocationTrackingEffect for auto-pan and bearing follow
- Per-node marker colors via data-driven convertToColor() expressions
- Waypoint camera animation on deep-link selection
- Compass click resets bearing to north
2026-06-02 12:09:06 -05:00
James Rich
3d65da72e7 feat(map): replace Google Maps + OSMDroid with unified MapLibre Compose Multiplatform
Replace the dual flavor-specific map implementations (Google Maps for google,
OSMDroid for fdroid) with a single MapLibre Compose Multiplatform implementation
in feature:map/commonMain, eliminating ~8,500 lines of duplicated code.

Key changes:
- Add maplibre-compose v0.12.1 dependency (KMP: Android, Desktop, iOS)
- Create unified MapViewModel with camera persistence via MapCameraPrefs
- Create MapScreen, MaplibreMapContent, NodeTrackLayers, TracerouteLayers,
  InlineMap, NodeTrackMap, TracerouteMap, NodeMapScreen in commonMain
- Create MapStyle enum with predefined OpenFreeMap tile styles
- Create GeoJsonConverters for Node/Waypoint/Position to GeoJSON
- Move TracerouteMapScreen from feature:node/androidMain to commonMain
- Wire navigation to use direct imports instead of CompositionLocal providers
- Delete 61 flavor-specific map files (google + fdroid source sets)
- Remove 8 CompositionLocal map providers from core:ui
- Remove SharedMapViewModel (replaced by new MapViewModel)
- Remove dead google-maps and osmdroid entries from version catalog
- Add MapViewModelTest with 10 test cases in commonTest

Baseline verified: spotlessCheck, detekt, assembleGoogleDebug, allTests all pass.
2026-06-02 12:09:06 -05:00
James Rich
5ec4fa7328 fix(map): initialize Maps SDK before building marker bitmap descriptors (#5709)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 23:30:13 +00:00
github-actions[bot]
0cb9c60615 docs: update CHANGELOG.md (#5707) 2026-06-01 17:56:51 -05:00
James Rich
1b661739e3 fix(map): scope cluster-renderer ViewTreeLifecycleOwner to map host view (#5708)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 22:43:22 +00:00
James Rich
813acee716 chore(agents): prune governance cruft and add Claude token guards (#5706)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:03:37 -05:00
github-actions[bot]
f203d776d3 docs: update CHANGELOG.md (#5705) 2026-06-01 14:46:20 -05:00
James Rich
be6f2cfb71 fix(map): remove manual ViewTree lifecycle owner workarounds (#5704) 2026-06-01 19:36:03 +00:00
github-actions[bot]
1854bc8c2b docs: update CHANGELOG.md (#5703)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-01 12:30:23 -05:00
James Rich
60feec646b fix(map): replace MarkerComposable with Canvas-rendered bitmaps (#5702)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 12:29:31 -05:00
James Rich
cc3b88d005 fix(firmware): surface error state when BLE OTA connection attempts are exhausted (#5700)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 17:04:20 +00:00
github-actions[bot]
409cdb7873 docs: update CHANGELOG.md (#5697) 2026-06-01 07:40:20 -05:00
James Rich
c590fe4676 chore: Scheduled updates (Firmware, Hardware, Translations, Graphs) (#5698)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2026-06-01 12:33:22 +00:00
James Rich
baa66e6877 fix: show loading overlay immediately for remote config sub-screens (#5694)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 12:32:52 +00:00
James Rich
63ff12dac3 fix(node): restore view-tree owners on map dispose so node-list popups aren't invisible (#5699)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 12:31:58 +00:00
renovate[bot]
d264b40862 chore(deps): update vico to v3.2.1 (#5696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-31 17:11:39 -07:00
github-actions[bot]
516ebe3cae docs: update CHANGELOG.md (#5692) 2026-05-31 12:40:39 -05:00
Copilot
a36b60e551 feat: Save unsent chat message as draft (#5686)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: James Rich <james.a.rich@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-31 17:31:30 +00:00
github-actions[bot]
fb942c518e docs: update CHANGELOG.md (#5690) 2026-05-31 12:30:58 -05:00
James Rich
c430eacaac Revert "feat: replace LoRa bandwidth text input with constrained dropdown" (#5691) 2026-05-31 12:30:27 -05:00
renovate[bot]
0e068053bc chore(deps): update core/proto/src/main/proto digest to c4540bb (#5688) 2026-05-31 12:27:53 -05:00
James Rich
ccf984e4b1 feat: replace LoRa bandwidth text input with constrained dropdown (#5687)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-31 17:15:31 +00:00
renovate[bot]
38ec47e894 chore(deps): update vico to v3.2.0 (#5689) 2026-05-31 12:18:58 -05:00
github-actions[bot]
d09cb9d786 docs: update CHANGELOG.md (#5685)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-31 07:24:20 -07:00
James Rich
cca7c274b9 fix: address top Crashlytics crashes and non-fatals for build 29320984 (#5684)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-31 14:16:10 +00:00
github-actions[bot]
bc9f163708 docs: update CHANGELOG.md (#5681) 2026-05-31 08:07:17 -05:00
renovate[bot]
93faeabbc6 chore(deps): update vico to v3.2.0-next.6 (#5683) 2026-05-31 08:06:50 -05:00
James Rich
ba163c40a9 chore: Scheduled updates (Firmware, Hardware, Translations, Graphs) (#5682)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2026-05-31 07:38:20 -05:00
James Rich
c82a88b21b fix(ci): make JBR setup-java resilient to GitHub API rate limits (#5680)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-30 20:47:56 -05:00