feat(map): replace Google Maps + OSMDroid with MapLibre Compose Multiplatform#5097
feat(map): replace Google Maps + OSMDroid with MapLibre Compose Multiplatform#5097jamesarich wants to merge 23 commits into
Conversation
391815b to
07114f7
Compare
❌ 3 Tests Failed:
View the top 3 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
bcbc7e2 to
1446224
Compare
|
Opened some PRs to potentially help unblock some Desktop feature parity. |
1446224 to
c222cfb
Compare
…se 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.
…log, 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
… 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
… 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.
- 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
- 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
…scientific notation
… 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
…ead 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)
…ocation 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
- 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
…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+)
- 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
- 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>
- 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>
- 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>
- 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>
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>
- 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>
- 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>
…tatus 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>
…p, 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>
dcd7ddd to
aed1c42
Compare
🖼️ Preview staleness check — advisoryThis PR modifies UI composables but does not update any
Changed UI files: What to check:
Adding previews checklist:
If this PR does not require preview updates (e.g., logic-only change, non-visual refactor), add the |
📄 Docs staleness check — advisoryThis PR modifies user-facing UI source files but does not update any page under
Changed source files: What to check:
New page checklist (if adding a new doc page):
If this PR does not require a doc update (e.g., internal refactor, bug fix, test change), add the
|
Replaces dual map implementations (Google Maps for Google flavor + OSMDroid for F-Droid flavor) with a single MapLibre Compose Multiplatform implementation in
feature:map/commonMain, targeting Android, Desktop, and iOS from shared KMP code.What changed
OfflineManager(Android/iOS)commonTest, JVM + Android host)Dependencies
maplibre-compose0.12.1,maplibre-compose-material30.12.1