ReviewOS

also looking at this

craft-native/craft

feat(maps): add key-free native Android maps and shared map controls

#363
Merged chris wants to merge codex/wildloop-native-map-providers into main
28 files +1857 -481

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/android/templates/CraftNativeMapLibre.kt.templatemodified+32-5
Changes to packages/android/templates/CraftNativeMapLibre.kt.template
@@ -10,6 +10,9 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
1010 private var styleVersion = 0
1111 private var readyStyle: Style? = null
1212 private var initialCameraKey: String? = null
13 private var reportedRegion: LatLngBounds? = null
14 private var reportedCamera: CameraPosition? = null
15 private var reportedViewport: Pair<Int, Int>? = null
1316 private var userChangingCamera = false
1417 private var geometryPoints = emptyList<LatLng>()
1518 private val markerHandlers = mutableMapOf<String, String>()
@@ -73,10 +76,16 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
7376 val bounds = nativeMap.projection.visibleRegion.latLngBounds
7477 val camera = nativeMap.cameraPosition
7578 val center = camera.target
76 if (center != null) onRegionChangeComplete?.invoke(JSONObject()
77 .put("center", JSONObject().put("latitude", center.latitude).put("longitude", center.longitude))
78 .put("span", JSONObject().put("latitudeDelta", bounds.latitudeNorth - bounds.latitudeSouth).put("longitudeDelta", bounds.longitudeEast - bounds.longitudeWest))
79 .put("heading", camera.bearing).put("pitch", camera.tilt))
79 if (center != null) {
80 val event = JSONObject()
81 .put("center", JSONObject().put("latitude", center.latitude).put("longitude", center.longitude))
82 .put("span", JSONObject().put("latitudeDelta", bounds.latitudeNorth - bounds.latitudeSouth).put("longitudeDelta", bounds.longitudeEast - bounds.longitudeWest))
83 .put("heading", camera.bearing).put("pitch", camera.tilt)
84 reportedRegion = region(event)
85 reportedCamera = camera
86 reportedViewport = width to height
87 onRegionChangeComplete?.invoke(event)
88 }
8089 }
8190 }
8291 applyPending()
@@ -123,7 +132,10 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
123132 val explicit = region(pendingProps.optJSONObject("region"))
124133 val initial = region(pendingProps.optJSONObject("initialRegion"))
125134 if (explicit != null) {
126 moveCamera(explicit, 0)
135 // A measured region is a bounding envelope, especially with
136 // padding or rotation. Refitting its unchanged echo would zoom
137 // out after every gesture. New requests still move the camera.
138 if (!isCurrentReportedRegion(explicit)) moveCamera(explicit, 0)
127139 initialCameraKey = null
128140 } else if (initial != null) {
129141 val key = "region:${pendingProps.optJSONObject("initialRegion")}"
@@ -298,6 +310,20 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
298310 }
299311 }
300312
313 private fun isCurrentReportedRegion(requested: LatLngBounds): Boolean {
314 val reported = reportedRegion ?: return false
315 val measured = reportedCamera ?: return false
316 val current = map?.cameraPosition ?: return false
317 val measuredTarget = measured.target ?: return false
318 val currentTarget = current.target ?: return false
319 fun same(a: Double, b: Double): Boolean = abs(a - b) < 0.000000001
320 return reportedViewport == (width to height) &&
321 same(requested.latitudeNorth, reported.latitudeNorth) && same(requested.latitudeSouth, reported.latitudeSouth) &&
322 same(requested.longitudeEast, reported.longitudeEast) && same(requested.longitudeWest, reported.longitudeWest) &&
323 abs(current.zoom - measured.zoom) < 0.000001 &&
324 same(currentTarget.latitude, measuredTarget.latitude) && same(currentTarget.longitude, measuredTarget.longitude)
325 }
326
301327 private fun applyOrientation() {
302328 val nativeMap = map ?: return
303329 val camera = nativeMap.cameraPosition
@@ -351,6 +377,7 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa
351377 if (lifecycleStarted) { mapView.onPause(); mapView.onStop(); lifecycleStarted = false }
352378 mapView.onDestroy()
353379 readyStyle = null; map = null
380 reportedRegion = null; reportedCamera = null; reportedViewport = null
354381 }
355382
356383 private fun coordinates(value: JSONArray?): List<LatLng> {