@tileflow/react-native 0.0.0-bootstrap.0 → 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +216 -4
  2. package/TileflowNativeAdmission.podspec +34 -0
  3. package/android/build.gradle +43 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/dev/tileflow/reactnative/AdmissionBootstrap.kt +144 -0
  6. package/android/src/main/java/dev/tileflow/reactnative/AdmissionBootstrapOkHttpNetwork.kt +89 -0
  7. package/android/src/main/java/dev/tileflow/reactnative/AdmissionCatalog.kt +137 -0
  8. package/android/src/main/java/dev/tileflow/reactnative/AdmissionDocumentScope.kt +22 -0
  9. package/android/src/main/java/dev/tileflow/reactnative/AdmissionEngine.kt +335 -0
  10. package/android/src/main/java/dev/tileflow/reactnative/AdmissionModuleProvider.kt +68 -0
  11. package/android/src/main/java/dev/tileflow/reactnative/AdmissionOkHttpNetwork.kt +86 -0
  12. package/android/src/main/java/dev/tileflow/reactnative/AdmissionProviderLease.kt +56 -0
  13. package/android/src/main/java/dev/tileflow/reactnative/AdmissionTypes.kt +85 -0
  14. package/android/src/main/java/dev/tileflow/reactnative/AdmissionUrl.kt +100 -0
  15. package/android/src/main/java/dev/tileflow/reactnative/HandlerAdmissionScheduler.kt +16 -0
  16. package/android/src/main/java/dev/tileflow/reactnative/MobileConfiguration.kt +95 -0
  17. package/android/src/main/java/dev/tileflow/reactnative/MobileConfigurationResources.kt +43 -0
  18. package/android/src/main/java/dev/tileflow/reactnative/NativeDocumentOkHttpNetwork.kt +101 -0
  19. package/android/src/main/java/dev/tileflow/reactnative/NativeDocumentRegistry.kt +136 -0
  20. package/android/src/main/java/dev/tileflow/reactnative/NativeSurfaceState.kt +115 -0
  21. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeAdmissionModule.kt +234 -0
  22. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeAdmissionPackage.kt +16 -0
  23. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeConfigurationModule.kt +43 -0
  24. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeDocumentsModule.kt +80 -0
  25. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeSurfaceModule.kt +277 -0
  26. package/android/src/main/res/raw/tileflow_configuration_keep.xml +3 -0
  27. package/dist/contract-DWs-lDPo.d.ts +144 -0
  28. package/dist/index.d.ts +13 -0
  29. package/dist/index.js +6989 -0
  30. package/dist/internal/camera.d.ts +65 -0
  31. package/dist/internal/camera.js +325 -0
  32. package/dist/internal/hosted-binding.d.ts +18 -0
  33. package/dist/internal/hosted-binding.js +277 -0
  34. package/dist/internal/initial-view.d.ts +11 -0
  35. package/dist/internal/initial-view.js +8 -0
  36. package/dist/internal/interactions.d.ts +76 -0
  37. package/dist/internal/interactions.js +794 -0
  38. package/dist/internal/native-admission-bridge.d.ts +29 -0
  39. package/dist/internal/native-admission-bridge.js +351 -0
  40. package/dist/internal/native-admission.d.ts +78 -0
  41. package/dist/internal/native-admission.js +1614 -0
  42. package/dist/internal/native-appearance.d.ts +21 -0
  43. package/dist/internal/native-appearance.js +110 -0
  44. package/dist/internal/native-configuration-bridge.d.ts +9 -0
  45. package/dist/internal/native-configuration-bridge.js +343 -0
  46. package/dist/internal/session.d.ts +125 -0
  47. package/dist/internal/session.js +887 -0
  48. package/dist/internal/source-state.d.ts +11 -0
  49. package/dist/internal/source-state.js +26 -0
  50. package/dist/native-admission-contract-k7LKE1vW.d.ts +89 -0
  51. package/docs/native-admission.md +108 -0
  52. package/docs/native-configuration.md +210 -0
  53. package/docs/native-interactions.md +161 -0
  54. package/docs/native-location.md +148 -0
  55. package/ios/TFAdmissionBootstrap.h +17 -0
  56. package/ios/TFAdmissionBootstrap.mm +168 -0
  57. package/ios/TFAdmissionCatalog.h +13 -0
  58. package/ios/TFAdmissionCatalog.mm +193 -0
  59. package/ios/TFAdmissionDocumentScope.h +7 -0
  60. package/ios/TFAdmissionDocumentScope.mm +45 -0
  61. package/ios/TFAdmissionEngine.h +57 -0
  62. package/ios/TFAdmissionEngine.mm +371 -0
  63. package/ios/TFAdmissionInstallation.h +27 -0
  64. package/ios/TFAdmissionInstallation.mm +117 -0
  65. package/ios/TFAdmissionNetwork.h +16 -0
  66. package/ios/TFAdmissionNetwork.mm +268 -0
  67. package/ios/TFAdmissionURL.mm +116 -0
  68. package/ios/TFDocumentBounds.h +9 -0
  69. package/ios/TFDocumentBounds.mm +19 -0
  70. package/ios/TFMobileConfiguration.h +23 -0
  71. package/ios/TFMobileConfiguration.mm +139 -0
  72. package/ios/TFNativeAdmissionURLProtocol.mm +69 -0
  73. package/ios/TFNativeDocumentRegistry.h +28 -0
  74. package/ios/TFNativeDocumentRegistry.mm +193 -0
  75. package/ios/TFNativeDocumentURL.h +6 -0
  76. package/ios/TFNativeSurfaceState.h +28 -0
  77. package/ios/TFNativeSurfaceState.mm +128 -0
  78. package/ios/TileflowNativeAdmission.h +9 -0
  79. package/ios/TileflowNativeAdmission.mm +184 -0
  80. package/ios/TileflowNativeConfiguration.mm +41 -0
  81. package/ios/TileflowNativeDocuments.h +12 -0
  82. package/ios/TileflowNativeDocuments.mm +105 -0
  83. package/ios/TileflowNativeSurface.mm +403 -0
  84. package/ios/tileflow_native_admission.rb +45 -0
  85. package/package.json +51 -7
  86. package/react-native.config.cjs +13 -0
@@ -0,0 +1,277 @@
1
+ package dev.tileflow.reactnative
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import android.view.View
6
+ import android.view.ViewGroup
7
+ import android.view.ViewTreeObserver
8
+ import com.facebook.react.bridge.Arguments
9
+ import com.facebook.react.bridge.LifecycleEventListener
10
+ import com.facebook.react.bridge.Promise
11
+ import com.facebook.react.bridge.ReactApplicationContext
12
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
13
+ import com.facebook.react.bridge.ReactMethod
14
+ import com.facebook.react.bridge.ReadableMap
15
+ import com.facebook.react.bridge.ReadableType
16
+ import com.facebook.react.common.LifecycleState
17
+ import com.facebook.react.modules.core.DeviceEventManagerModule
18
+ import com.facebook.react.uimanager.UIManagerHelper
19
+ import com.facebook.react.uimanager.common.UIManagerType
20
+ import java.util.UUID
21
+ import kotlin.math.abs
22
+ import org.maplibre.android.maps.MapLibreMap
23
+ import org.maplibre.android.maps.MapView
24
+ import org.maplibre.android.maps.Style
25
+ import org.maplibre.reactnative.components.camera.MLRNCamera
26
+ import org.maplibre.reactnative.components.mapview.MLRNMapView
27
+
28
+ class TileflowNativeSurfaceModule(context: ReactApplicationContext) : ReactContextBaseJavaModule(context), LifecycleEventListener {
29
+ private val handler = Handler(Looper.getMainLooper())
30
+ private val surfaces = linkedMapOf<String, Surface>()
31
+ private val rootRetirements = linkedMapOf<Int, RootRetirement>()
32
+ private var foreground = context.lifecycleState == LifecycleState.RESUMED
33
+ private var invalidated = false
34
+ private var listeners = 0
35
+ init { context.addLifecycleEventListener(this) }
36
+ override fun getName() = "TileflowNativeSurface"
37
+ @ReactMethod fun addListener(name: String) { handler.post { if (name == "TileflowNativeSurfaceEvent") listeners++ } }
38
+ @ReactMethod fun removeListeners(count: Double) { handler.post { listeners = maxOf(0, listeners - count.toInt()) } }
39
+ private fun invalid(): Nothing = throw IllegalStateException("Native surface operation failed.")
40
+ private fun reject(promise: Promise) { promise.reject("NATIVE_SURFACE_UNAVAILABLE", "Native surface operation failed.") }
41
+ private fun action(promise: Promise, work: () -> Any) {
42
+ handler.post { try { promise.resolve(work()) } catch (_: Exception) { reject(promise) } }
43
+ }
44
+ private fun integer(value: Double): Long {
45
+ if (!value.isFinite() || value < 1 || value > 9007199254740991.0 || value != value.toLong().toDouble()) invalid()
46
+ return value.toLong()
47
+ }
48
+ private fun root(tag: Int): View? {
49
+ return try { UIManagerHelper.getUIManager(reactApplicationContext, UIManagerType.FABRIC)?.resolveView(tag) }
50
+ catch (_: Exception) { null }
51
+ }
52
+ private fun mapIn(root: View): MLRNMapView {
53
+ val queue = ArrayDeque<View>(); queue.add(root)
54
+ var found: MLRNMapView? = null
55
+ var visited = 0
56
+ while (queue.isNotEmpty()) {
57
+ if (++visited > 1024) invalid()
58
+ val view = queue.removeFirst()
59
+ if (view is MLRNMapView) {
60
+ if (found != null) invalid()
61
+ found = view
62
+ } else if (view is ViewGroup) {
63
+ if (view.childCount + queue.size > 1024) invalid()
64
+ for (index in 0 until view.childCount) queue.add(view.getChildAt(index))
65
+ }
66
+ }
67
+ return found ?: invalid()
68
+ }
69
+ private fun current(id: String, allowFailed: Boolean = false): Surface {
70
+ val surface = surfaces[id] ?: invalid()
71
+ if (invalidated || surface.retiring || (!allowFailed && !surface.state.active) || root(surface.root.id) !== surface.root || mapIn(surface.root) !== surface.map) invalid()
72
+ return surface
73
+ }
74
+
75
+ @ReactMethod fun attachSurface(tag: Double, promise: Promise) = action(promise) {
76
+ if (invalidated || !foreground || listeners < 1 || surfaces.size >= 16 || tag != tag.toInt().toDouble() || tag < 1) invalid()
77
+ val root = root(tag.toInt()) ?: invalid()
78
+ val map = mapIn(root)
79
+ if (!root.isAttachedToWindow || map.mapLibreMap == null || surfaces.values.any { it.map === map }) invalid()
80
+ val surface = Surface(UUID.randomUUID().toString(), root, map)
81
+ surfaces[surface.id] = surface
82
+ try { surface.attach() } catch (_: Exception) { surface.retire(); throw IllegalStateException("Native surface operation failed.") }
83
+ Arguments.makeNativeMap(mapOf("surface" to surface.id))
84
+ }
85
+ @ReactMethod fun expectStyle(id: String, token: String, promise: Promise) = action(promise) {
86
+ val surface = current(id, allowFailed = true)
87
+ surface.state.expect(token); surface.token = token
88
+ surface.deadline?.let { handler.removeCallbacks(it) }
89
+ val deadline = Runnable { if (!surface.retiring && surface.token == token) surface.state.fail() }
90
+ surface.deadline = deadline; handler.postDelayed(deadline, 30000)
91
+ Arguments.makeNativeMap(mapOf("accepted" to true))
92
+ }
93
+ @ReactMethod fun acknowledgeSurface(id: String, sequence: Double, promise: Promise) = action(promise) {
94
+ if (!AdmissionUrl.validToken(id)) invalid()
95
+ surfaces[id]?.state?.acknowledge(integer(sequence))
96
+ Arguments.makeNativeMap(mapOf("acknowledged" to true))
97
+ }
98
+ @ReactMethod fun commitLayout(id: String, token: String, promise: Promise) {
99
+ handler.post {
100
+ try {
101
+ val surface = current(id)
102
+ if (surface.layoutWaiter != null) invalid()
103
+ val observer = surface.root.viewTreeObserver
104
+ var settled = false
105
+ lateinit var listener: ViewTreeObserver.OnPreDrawListener
106
+ val finish: (Boolean) -> Unit = finish@ { success ->
107
+ if (settled) return@finish
108
+ settled = true
109
+ if (observer.isAlive) observer.removeOnPreDrawListener(listener)
110
+ surface.layoutWaiter = null
111
+ try {
112
+ if (!success || current(id) !== surface || surface.token != token) invalid()
113
+ surface.sampleLayout()
114
+ promise.resolve(Arguments.makeNativeMap(mapOf("layout" to surface.state.commit(token))))
115
+ } catch (_: Exception) { reject(promise) }
116
+ }
117
+ listener = ViewTreeObserver.OnPreDrawListener { finish(true); true }
118
+ surface.layoutWaiter = { finish(false) }
119
+ observer.addOnPreDrawListener(listener)
120
+ surface.root.invalidate()
121
+ } catch (_: Exception) { reject(promise) }
122
+ }
123
+ }
124
+ @ReactMethod fun requestFrame(id: String, token: String, promise: Promise) = action(promise) {
125
+ val surface = current(id)
126
+ if (surface.token != token) invalid()
127
+ surface.style() ?: invalid()
128
+ surface.state.request(token)
129
+ surface.sdk.triggerRepaint()
130
+ Arguments.makeNativeMap(mapOf("requested" to true))
131
+ }
132
+ @ReactMethod fun applyCamera(id: String, sequence: Double, input: ReadableMap, promise: Promise) = action(promise) {
133
+ val surface = current(id)
134
+ val target = view(input)
135
+ val number = integer(sequence)
136
+ val invalidation = surface.state.beginCommand(number)
137
+ if (invalidation == 0L) invalid()
138
+ val cameras = (0 until surface.map.featureCount).mapNotNull { surface.map.getFeatureAt(it)?.toView() as? MLRNCamera }
139
+ if (cameras.size != 1) invalid()
140
+ val command = Arguments.makeNativeMap(target + mapOf("duration" to 0))
141
+ cameras.single().handleImperativeStop(command)
142
+ val actual = surface.view()
143
+ val expectedCenter = target["center"] as List<*>
144
+ val actualCenter = actual["center"] as List<*>
145
+ if (!nearAngular(actualCenter[0] as Double, expectedCenter[0] as Double) ||
146
+ abs(actualCenter[1] as Double - expectedCenter[1] as Double) > 0.000001 ||
147
+ abs(actual["zoom"] as Double - target["zoom"] as Double) > 0.000001 ||
148
+ !nearAngular(actual["bearing"] as Double, target["bearing"] as Double) ||
149
+ abs(actual["pitch"] as Double - target["pitch"] as Double) > 0.000001 || current(id) !== surface) invalid()
150
+ Arguments.makeNativeMap(mapOf("command" to number, "invalidation" to invalidation, "view" to target))
151
+ }
152
+ @ReactMethod fun cancelCamera(id: String, sequence: Double, promise: Promise) = action(promise) {
153
+ if (!AdmissionUrl.validToken(id)) invalid()
154
+ surfaces[id]?.state?.cancelCommand(integer(sequence))
155
+ Arguments.makeNativeMap(mapOf("cancelled" to true))
156
+ }
157
+ private fun nearAngular(a: Double, b: Double): Boolean = abs(((a - b + 540) % 360) - 180) <= 0.000001
158
+ private fun view(input: ReadableMap): Map<String, Any> {
159
+ val keys = input.keySetIterator(); var count = 0
160
+ while (keys.hasNextKey()) if (++count > 4 || keys.nextKey() !in setOf("center", "zoom", "bearing", "pitch")) invalid()
161
+ if (count != 4) invalid()
162
+ val center = input.getArray("center") ?: invalid()
163
+ if (center.size() != 2 || center.getType(0) != ReadableType.Number || center.getType(1) != ReadableType.Number) invalid()
164
+ val longitude = center.getDouble(0); val latitude = center.getDouble(1)
165
+ val zoom = input.getDouble("zoom"); val bearing = input.getDouble("bearing"); val pitch = input.getDouble("pitch")
166
+ if (listOf(longitude, latitude, zoom, bearing, pitch).any { !it.isFinite() } || longitude !in -180.0..180.0 ||
167
+ latitude !in -90.0..90.0 || zoom !in 0.0..24.0 || bearing !in -180.0..180.0 || pitch !in 0.0..85.0) invalid()
168
+ return mapOf("center" to listOf(longitude, latitude), "zoom" to zoom, "bearing" to bearing, "pitch" to pitch)
169
+ }
170
+
171
+ @ReactMethod fun retireSurface(id: String, promise: Promise) {
172
+ handler.post {
173
+ try {
174
+ if (!AdmissionUrl.validToken(id)) invalid()
175
+ val surface = surfaces[id]
176
+ if (surface == null) { promise.resolve(Arguments.makeNativeMap(mapOf("detached" to true))); return@post }
177
+ surface.retire()
178
+ waitForRoot(surface.root, promise)
179
+ } catch (_: Exception) { reject(promise) }
180
+ }
181
+ }
182
+ @ReactMethod fun retireRoot(tag: Double, promise: Promise) {
183
+ handler.post {
184
+ try {
185
+ if (tag < 1 || tag != tag.toInt().toDouble()) invalid()
186
+ val root = root(tag.toInt())
187
+ if (root == null) { promise.resolve(Arguments.makeNativeMap(mapOf("detached" to true))); return@post }
188
+ for (surface in surfaces.values.toList()) if (surface.root === root) surface.retire()
189
+ waitForRoot(root, promise)
190
+ } catch (_: Exception) { reject(promise) }
191
+ }
192
+ }
193
+ private fun waitForRoot(root: View, promise: Promise) {
194
+ if (!root.isAttachedToWindow) { promise.resolve(Arguments.makeNativeMap(mapOf("detached" to true))); return }
195
+ val existing = rootRetirements[root.id]
196
+ if (existing != null) { if (existing.root !== root || existing.waiters.size >= 16) invalid(); existing.waiters.add(promise); return }
197
+ if (rootRetirements.size >= 16) invalid()
198
+ val retirement = RootRetirement(root); retirement.waiters.add(promise); rootRetirements[root.id] = retirement
199
+ root.addOnAttachStateChangeListener(retirement)
200
+ // A deadline can report failed cleanup, never successful detachment.
201
+ handler.postDelayed({ if (rootRetirements[root.id] === retirement) retirement.timeout() }, 30000)
202
+ }
203
+ private inner class RootRetirement(val root: View) : View.OnAttachStateChangeListener {
204
+ val waiters = mutableListOf<Promise>()
205
+ override fun onViewAttachedToWindow(view: View) {}
206
+ override fun onViewDetachedFromWindow(view: View) {
207
+ root.removeOnAttachStateChangeListener(this); rootRetirements.remove(root.id)
208
+ for (promise in waiters.toList()) promise.resolve(Arguments.makeNativeMap(mapOf("detached" to true)))
209
+ waiters.clear()
210
+ }
211
+ fun timeout() { for (promise in waiters.toList()) reject(promise); waiters.clear() }
212
+ }
213
+
214
+ private inner class Surface(val id: String, val root: View, val map: MLRNMapView) : View.OnAttachStateChangeListener {
215
+ val sdk = map.mapLibreMap ?: invalid()
216
+ val state = NativeSurfaceState(id) { event ->
217
+ if (event["kind"] == "render") deadline?.let { handler.removeCallbacks(it); deadline = null }
218
+ if (!invalidated && listeners > 0) reactApplicationContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit("TileflowNativeSurfaceEvent", Arguments.makeNativeMap(event))
219
+ else invalid()
220
+ }
221
+ var token = ""
222
+ var retiring = false
223
+ var deadline: Runnable? = null
224
+ var layoutWaiter: (() -> Unit)? = null
225
+ private var layoutSnapshot: List<Int>? = null
226
+ fun marker() = "__tileflow_native_style_$token"
227
+ fun style(): Style? = sdk.style?.takeIf { it.isFullyLoaded && it.getLayer(marker()) != null }
228
+ fun view(): Map<String, Any> {
229
+ val position = sdk.cameraPosition; val center = position.target ?: invalid()
230
+ val longitude = ((center.longitude + 540) % 360) - 180
231
+ val bearing = ((position.bearing + 540) % 360) - 180
232
+ return mapOf("center" to listOf(longitude, center.latitude), "zoom" to position.zoom, "bearing" to bearing, "pitch" to position.tilt)
233
+ }
234
+ private fun guarded(action: () -> Unit) { if (!retiring) try { action() } catch (_: Exception) { state.fail() } }
235
+ private val loaded = MapView.OnDidFinishLoadingStyleListener { guarded { style()?.let { state.loaded(token, it) } } }
236
+ private val frameStart = MapView.OnWillStartRenderingFrameListener { guarded { sampleLayout(); state.frameStart(style()) } }
237
+ private val frameEnd = MapView.OnDidFinishRenderingFrameListener { fully, _, _ -> guarded { state.frameEnd(style(), fully) } }
238
+ private val cameraStart = MapLibreMap.OnCameraMoveStartedListener { reason -> guarded {
239
+ if (reason == MapLibreMap.OnCameraMoveStartedListener.REASON_API_GESTURE) state.gestureStart(view())
240
+ else state.gestureEnd(view())
241
+ } }
242
+ private val cameraMove = MapLibreMap.OnCameraMoveListener { guarded { state.gestureChange(view()) } }
243
+ private val cameraEnd = MapLibreMap.OnCameraIdleListener { guarded { state.gestureEnd(view()) } }
244
+ private val layout = View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> guarded { sampleLayout() } }
245
+ fun sampleLayout() {
246
+ val snapshot = listOf(root.width, root.height, map.width, map.height, root.left, root.top, if (root.isAttachedToWindow && map.isAttachedToWindow && foreground) 1 else 0)
247
+ if (snapshot == layoutSnapshot) return
248
+ layoutSnapshot = snapshot
249
+ state.layout(snapshot.last() == 1 && snapshot.take(4).all { it > 0 })
250
+ }
251
+ fun attach() {
252
+ root.addOnAttachStateChangeListener(this); root.addOnLayoutChangeListener(layout); map.addOnLayoutChangeListener(layout)
253
+ map.addOnDidFinishLoadingStyleListener(loaded); map.addOnWillStartRenderingFrameListener(frameStart)
254
+ map.addOnDidFinishRenderingFrameListener(frameEnd)
255
+ sdk.addOnCameraMoveStartedListener(cameraStart); sdk.addOnCameraMoveListener(cameraMove); sdk.addOnCameraIdleListener(cameraEnd)
256
+ sampleLayout()
257
+ }
258
+ fun retire() {
259
+ if (retiring) return
260
+ retiring = true; state.close(); deadline?.let { handler.removeCallbacks(it) }; deadline = null
261
+ layoutWaiter?.invoke(); layoutWaiter = null
262
+ root.removeOnLayoutChangeListener(layout); map.removeOnLayoutChangeListener(layout)
263
+ map.removeOnDidFinishLoadingStyleListener(loaded); map.removeOnWillStartRenderingFrameListener(frameStart)
264
+ map.removeOnDidFinishRenderingFrameListener(frameEnd)
265
+ sdk.removeOnCameraMoveStartedListener(cameraStart); sdk.removeOnCameraMoveListener(cameraMove); sdk.removeOnCameraIdleListener(cameraEnd)
266
+ if (!root.isAttachedToWindow) onViewDetachedFromWindow(root)
267
+ }
268
+ override fun onViewAttachedToWindow(view: View) { guarded { sampleLayout() } }
269
+ override fun onViewDetachedFromWindow(view: View) {
270
+ retire(); root.removeOnAttachStateChangeListener(this); surfaces.remove(id)
271
+ }
272
+ }
273
+ override fun onHostPause() { handler.post { foreground = false; for (surface in surfaces.values) surface.sampleLayout() } }
274
+ override fun onHostResume() { handler.post { foreground = true; for (surface in surfaces.values) surface.sampleLayout() } }
275
+ override fun onHostDestroy() { handler.post { invalidated = true; for (surface in surfaces.values.toList()) surface.retire() } }
276
+ override fun invalidate() { reactApplicationContext.removeLifecycleEventListener(this); onHostDestroy(); super.invalidate() }
277
+ }
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources xmlns:tools="http://schemas.android.com/tools"
3
+ tools:keep="@array/tileflow_mobile_configuration" />
@@ -0,0 +1,144 @@
1
+ import { MapProps as MapProps$1 } from '@maplibre/maplibre-react-native';
2
+ import { ReactElement, ReactNode, Ref } from 'react';
3
+ import { ViewProps } from 'react-native';
4
+ import { TileflowNativeInitialView, TileflowNativeInitialViewOptions, TileflowNativeSource, TileflowNativeSourceOptions, TileflowNativeSourceState, TileflowNativeSourceError } from '@tileflow/core/native';
5
+ import { TileflowAnnotation, TileflowInteractionBinding, TileflowInteractionEvent, TileflowResolvedAnnotationTarget, TileflowResolvedPoiFeatureTarget, TileflowInteractionDiagnostic, TileflowAnnotationViewContext } from '@tileflow/interactions';
6
+
7
+ /** Marker composition only. Selection presentation and native handles are not part of this context. */
8
+ type MapMarkerRenderContext<TAnnotation extends TileflowAnnotation = TileflowAnnotation> = Readonly<Pick<TileflowAnnotationViewContext<TAnnotation>, 'annotation' | 'target' | 'content' | 'viewName'>>;
9
+ /** Return marker content; Tileflow supplies the accessible activation wrapper. */
10
+ type MapMarkerRenderer<TAnnotation extends TileflowAnnotation = TileflowAnnotation> = (context: MapMarkerRenderContext<TAnnotation>) => ReactElement;
11
+ /** Mobile reports activation; the application owns any persistent selection. */
12
+ type MapInteractionEvent<TAnnotation extends TileflowAnnotation = TileflowAnnotation> = Readonly<Omit<TileflowInteractionEvent<TAnnotation>, 'target' | 'type'> & {
13
+ target: TileflowResolvedAnnotationTarget<TAnnotation> | TileflowResolvedPoiFeatureTarget;
14
+ type: 'target:activate';
15
+ }>;
16
+ type MapInteractionProps<TAnnotation extends TileflowAnnotation = TileflowAnnotation> = Readonly<{
17
+ annotations?: readonly TAnnotation[];
18
+ interactions?: readonly TileflowInteractionBinding[];
19
+ onInteractionEvent?: (event: MapInteractionEvent<TAnnotation>) => void;
20
+ onInteractionDiagnostic?: (diagnostic: TileflowInteractionDiagnostic) => void;
21
+ renderMarker?: MapMarkerRenderer<TAnnotation>;
22
+ }>;
23
+
24
+ type MapSource = TileflowNativeSource;
25
+ type MapThemeSelection = NonNullable<TileflowNativeSourceOptions['theme']>;
26
+ type MapColorScheme = NonNullable<TileflowNativeSourceOptions['colorScheme']>;
27
+ type MapView = TileflowNativeInitialView;
28
+ /** Portable composition inputs, independent from source acquisition. */
29
+ type MapInitialViewInputs = TileflowNativeInitialViewOptions;
30
+ type MapSourceProps = Readonly<{
31
+ source: MapSource;
32
+ theme?: MapThemeSelection;
33
+ }>;
34
+ type AllowedMapOptions = 'dragPan' | 'touchZoom' | 'doubleTapZoom' | 'doubleTapHoldZoom' | 'touchRotate' | 'touchPitch' | 'compass' | 'compassHiddenFacingNorth' | 'scaleBar';
35
+ type OwnedMapOptions = Exclude<keyof MapProps$1, AllowedMapOptions> | 'container' | 'styleURL' | 'styleUrl' | 'transformRequest' | 'requestTransform' | 'requestHeaders';
36
+ /** A positive allowlist. Style, interception, view and lifecycle remain adapter-owned. */
37
+ type MapOptions = Readonly<Pick<MapProps$1, AllowedMapOptions>> & {
38
+ readonly [Key in OwnedMapOptions]?: never;
39
+ };
40
+ type MapPresentationProps = Readonly<{
41
+ children?: ReactNode;
42
+ style?: ViewProps['style'];
43
+ testID?: ViewProps['testID'];
44
+ mapOptions?: MapOptions;
45
+ }>;
46
+ type TileflowReady = Extract<TileflowNativeSourceState, {
47
+ status: 'ready';
48
+ }>;
49
+ type MapTheme = Readonly<Pick<TileflowReady['theme'], 'name' | 'colorScheme'>>;
50
+ type MapSourceError = Readonly<Pick<TileflowNativeSourceError, 'code' | 'field' | 'kind'>>;
51
+ /** Safe Tileflow identity only: no URL, style body, credentials or native object. */
52
+ type MapSelection = Readonly<{
53
+ map: TileflowReady['map']['name'];
54
+ theme: MapTheme;
55
+ }>;
56
+ /** Source readiness is not renderer/frame readiness. */
57
+ type MapSourceState = Readonly<{
58
+ status: 'loading';
59
+ generation: number;
60
+ }> | (Readonly<{
61
+ status: 'ready';
62
+ generation: number;
63
+ }> & MapSelection) | Readonly<{
64
+ status: 'error';
65
+ generation: number;
66
+ error: MapSourceError;
67
+ }>;
68
+ /** A synchronous read of the last source snapshot. Does not trigger loading or native commands. */
69
+ type MapRef = Readonly<{
70
+ getSourceState(): MapSourceState | undefined;
71
+ }>;
72
+ /** The current style was accepted by the renderer; this alone does not establish a rendered frame. */
73
+ type MapLoadEvent = Readonly<{
74
+ type: 'load';
75
+ generation: number;
76
+ selection: MapSelection;
77
+ }>;
78
+ /** Renderer failures expose no native event, message or remote cause. */
79
+ type MapErrorEvent = Readonly<{
80
+ type: 'source-error';
81
+ generation: number;
82
+ error: MapSourceError;
83
+ }> | Readonly<{
84
+ type: 'renderer-error';
85
+ generation: number;
86
+ }>;
87
+ /** The renderer owner must confirm readiness; a ready manifest alone is insufficient. */
88
+ type MapReadinessChangeEvent = Readonly<{
89
+ type: 'readiness-change';
90
+ generation: number;
91
+ }> & (Readonly<{
92
+ status: 'loading';
93
+ }> | Readonly<{
94
+ status: 'ready';
95
+ }> | Readonly<{
96
+ status: 'error';
97
+ }>);
98
+ /** Theme transitions retain concrete names; source/renderer error details use onError. */
99
+ type MapThemeChangeEvent = Readonly<{
100
+ type: 'theme-change';
101
+ generation: number;
102
+ map: TileflowReady['map']['name'];
103
+ }> & (Readonly<{
104
+ phase: 'preloading' | 'applying';
105
+ targetTheme: MapTheme;
106
+ currentTheme?: MapTheme;
107
+ }> | Readonly<{
108
+ phase: 'ready';
109
+ currentTheme: MapTheme;
110
+ }> | Readonly<{
111
+ phase: 'error';
112
+ targetTheme?: MapTheme;
113
+ currentTheme?: MapTheme;
114
+ }>);
115
+ type MapEventHandlers = Readonly<{
116
+ onLoad?: (event: MapLoadEvent) => void;
117
+ onError?: (event: MapErrorEvent) => void;
118
+ onReadinessChange?: (event: MapReadinessChangeEvent) => void;
119
+ onThemeChange?: (event: MapThemeChangeEvent) => void;
120
+ }>;
121
+ /** Common source/presentation/lifecycle pieces; MapProps adds camera ownership. */
122
+ type MapBaseProps = MapSourceProps & MapPresentationProps & MapEventHandlers & Readonly<{
123
+ ref?: Ref<MapRef>;
124
+ }>;
125
+ /** Canonical user-driven change only. Commands, renderer events and source generations stay private. */
126
+ type MapViewChangeEvent = Readonly<{
127
+ type: 'view-change';
128
+ view: MapView;
129
+ }>;
130
+ /** The ownership mode is fixed for one mounted native instance. */
131
+ type MapCameraProps = Readonly<{
132
+ view: MapView;
133
+ initialView?: never;
134
+ onViewChange: (event: MapViewChangeEvent) => void;
135
+ }> | Readonly<{
136
+ view?: never;
137
+ initialView?: MapInitialViewInputs['view'];
138
+ /** Observation alone does not make the camera controlled. */
139
+ onViewChange?: (event: MapViewChangeEvent) => void;
140
+ }>;
141
+ /** Complete Tileflow source, presentation, mount-stable camera and portable interaction inputs. */
142
+ type MapProps<TAnnotation extends TileflowAnnotation = TileflowAnnotation> = MapBaseProps & MapCameraProps & MapInteractionProps<TAnnotation>;
143
+
144
+ export type { MapView as M, MapViewChangeEvent as a, MapInitialViewInputs as b, MapCameraProps as c, MapProps as d, MapBaseProps as e, MapColorScheme as f, MapErrorEvent as g, MapEventHandlers as h, MapInteractionEvent as i, MapInteractionProps as j, MapLoadEvent as k, MapMarkerRenderContext as l, MapMarkerRenderer as m, MapOptions as n, MapPresentationProps as o, MapReadinessChangeEvent as p, MapRef as q, MapSelection as r, MapSource as s, MapSourceError as t, MapSourceProps as u, MapSourceState as v, MapTheme as w, MapThemeChangeEvent as x, MapThemeSelection as y };
@@ -0,0 +1,13 @@
1
+ import { ReactElement } from 'react';
2
+ import { TileflowAnnotation } from '@tileflow/interactions';
3
+ export { TileflowAnnotation, TileflowInteractionBinding, TileflowInteractionCoordinate, TileflowInteractionDiagnostic, TileflowPoiCategory, TileflowResolvedAnnotationTarget, TileflowResolvedPoiFeatureTarget } from '@tileflow/interactions';
4
+ import { d as MapProps } from './contract-DWs-lDPo.js';
5
+ export { e as MapBaseProps, c as MapCameraProps, f as MapColorScheme, g as MapErrorEvent, h as MapEventHandlers, b as MapInitialViewInputs, i as MapInteractionEvent, j as MapInteractionProps, k as MapLoadEvent, l as MapMarkerRenderContext, m as MapMarkerRenderer, n as MapOptions, o as MapPresentationProps, p as MapReadinessChangeEvent, q as MapRef, r as MapSelection, s as MapSource, t as MapSourceError, u as MapSourceProps, v as MapSourceState, w as MapTheme, x as MapThemeChangeEvent, y as MapThemeSelection, M as MapView, a as MapViewChangeEvent } from './contract-DWs-lDPo.js';
6
+ import '@maplibre/maplibre-react-native';
7
+ import 'react-native';
8
+ import '@tileflow/core/native';
9
+
10
+ /** A Tileflow-only native Map. Its imperative ref exposes safe source state only. */
11
+ declare function Map<TAnnotation extends TileflowAnnotation = TileflowAnnotation>(props: MapProps<TAnnotation>): ReactElement;
12
+
13
+ export { Map, MapProps };