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.
This commit is contained in:
James Rich 2026-04-12 18:25:15 -05:00
parent a2763bdfeb
commit 598cae564e
86 changed files with 1653 additions and 8333 deletions

View file

@ -171,6 +171,41 @@ interface MapPrefs {
fun setLastHeardTrackFilter(seconds: Long)
}
/** Reactive interface for map camera position persistence. */
interface MapCameraPrefs {
val cameraLat: StateFlow<Double>
fun setCameraLat(value: Double)
val cameraLng: StateFlow<Double>
fun setCameraLng(value: Double)
val cameraZoom: StateFlow<Float>
fun setCameraZoom(value: Float)
val cameraTilt: StateFlow<Float>
fun setCameraTilt(value: Float)
val cameraBearing: StateFlow<Float>
fun setCameraBearing(value: Float)
val selectedStyleUri: StateFlow<String>
fun setSelectedStyleUri(value: String)
val hiddenLayerUrls: StateFlow<Set<String>>
fun setHiddenLayerUrls(value: Set<String>)
val networkMapLayers: StateFlow<Set<String>>
fun setNetworkMapLayers(value: Set<String>)
}
/** Reactive interface for map consent. */
interface MapConsentPrefs {
fun shouldReportLocation(nodeNum: Int?): StateFlow<Boolean>
@ -238,6 +273,7 @@ interface AppPreferences {
val emoji: CustomEmojiPrefs
val ui: UiPrefs
val map: MapPrefs
val mapCamera: MapCameraPrefs
val mapConsent: MapConsentPrefs
val mapTileProvider: MapTileProviderPrefs
val radio: RadioPrefs