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