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>
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>
- 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>
- 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>
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>
- 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>
- 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
- 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
- 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
- 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
- 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
- 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
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.
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
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.