feat(map): replace Google Maps + OSMDroid with MapLibre Compose Multiplatform#5097
Draft
jamesarich wants to merge 22 commits into
Draft
feat(map): replace Google Maps + OSMDroid with MapLibre Compose Multiplatform#5097jamesarich wants to merge 22 commits into
jamesarich wants to merge 22 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
5 tasks
Collaborator
Author
|
Opened some PRs to potentially help unblock some Desktop feature parity. |
Open
7 tasks
…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>
1446224 to
c222cfb
Compare
- 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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