@rnmapbox/maps 10.1.0-rc.3 → 10.1.0-rc.5

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 (62) hide show
  1. package/README.md +1 -30
  2. package/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt +11 -0
  3. package/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotation.kt +33 -35
  4. package/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapView.kt +173 -106
  5. package/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapViewManager.kt +7 -0
  6. package/android/src/main/java/com/rnmapbox/rnmbx/modules/RNMBXOfflineModuleLegacy.kt +444 -0
  7. package/android/src/main/java/com/rnmapbox/rnmbx/utils/GeoJSONUtils.kt +5 -0
  8. package/android/src/main/mapbox-v11-compat/v10/com/rnmapbox/rnmbx/v11compat/Annotation.kt +4 -0
  9. package/android/src/main/mapbox-v11-compat/v11/com/rnmapbox/rnmbx/v11compat/Annotation.kt +0 -1
  10. package/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerDelegate.java +3 -0
  11. package/android/src/main/old-arch/com/facebook/react/viewmanagers/RNMBXMapViewManagerInterface.java +1 -0
  12. package/ios/RNMBX/RNMBXEvent.swift +2 -0
  13. package/ios/RNMBX/RNMBXMapView.swift +66 -39
  14. package/ios/RNMBX/RNMBXMapViewComponentView.mm +26 -20
  15. package/ios/RNMBX/RNMBXMapViewManager.m +1 -0
  16. package/ios/RNMBX/RNMBXMarkerViewContentComponentView.mm +15 -1
  17. package/ios/RNMBX/RNMBXOfflineModule.m +1 -1
  18. package/ios/RNMBX/RNMBXOfflineModuleLegacy.m +33 -0
  19. package/ios/RNMBX/RNMBXOfflineModuleLegacy.swift +431 -0
  20. package/ios/RNMBX/RNMBXPointAnnotation.swift +33 -0
  21. package/lib/commonjs/Mapbox.js +8 -0
  22. package/lib/commonjs/Mapbox.js.map +1 -1
  23. package/lib/commonjs/components/MapView.js.map +1 -1
  24. package/lib/commonjs/modules/offline/OfflinePackLegacy.js +40 -0
  25. package/lib/commonjs/modules/offline/OfflinePackLegacy.js.map +1 -0
  26. package/lib/commonjs/modules/offline/offlineManager.js +4 -4
  27. package/lib/commonjs/modules/offline/offlineManager.js.map +1 -1
  28. package/lib/commonjs/modules/offline/offlineManagerLegacy.js +166 -0
  29. package/lib/commonjs/modules/offline/offlineManagerLegacy.js.map +1 -0
  30. package/lib/commonjs/specs/RNMBXMapViewNativeComponent.js.map +1 -1
  31. package/lib/module/Mapbox.js +1 -0
  32. package/lib/module/Mapbox.js.map +1 -1
  33. package/lib/module/components/MapView.js.map +1 -1
  34. package/lib/module/modules/offline/OfflinePackLegacy.js +34 -0
  35. package/lib/module/modules/offline/OfflinePackLegacy.js.map +1 -0
  36. package/lib/module/modules/offline/offlineManager.js +4 -4
  37. package/lib/module/modules/offline/offlineManager.js.map +1 -1
  38. package/lib/module/modules/offline/offlineManagerLegacy.js +154 -0
  39. package/lib/module/modules/offline/offlineManagerLegacy.js.map +1 -0
  40. package/lib/module/specs/RNMBXMapViewNativeComponent.js.map +1 -1
  41. package/lib/typescript/src/Mapbox.d.ts +1 -0
  42. package/lib/typescript/src/Mapbox.d.ts.map +1 -1
  43. package/lib/typescript/src/components/Images.d.ts +1 -1
  44. package/lib/typescript/src/components/MapView.d.ts +6 -1
  45. package/lib/typescript/src/components/MapView.d.ts.map +1 -1
  46. package/lib/typescript/src/modules/offline/OfflinePackLegacy.d.ts +24 -0
  47. package/lib/typescript/src/modules/offline/OfflinePackLegacy.d.ts.map +1 -0
  48. package/lib/typescript/src/modules/offline/offlineManager.d.ts +1 -1
  49. package/lib/typescript/src/modules/offline/offlineManager.d.ts.map +1 -1
  50. package/lib/typescript/src/modules/offline/offlineManagerLegacy.d.ts +93 -0
  51. package/lib/typescript/src/modules/offline/offlineManagerLegacy.d.ts.map +1 -0
  52. package/lib/typescript/src/specs/RNMBXMapViewNativeComponent.d.ts +1 -0
  53. package/lib/typescript/src/specs/RNMBXMapViewNativeComponent.d.ts.map +1 -1
  54. package/package.json +3 -2
  55. package/setup-jest.js +15 -0
  56. package/src/Mapbox.ts +1 -0
  57. package/src/components/Images.tsx +1 -1
  58. package/src/components/MapView.tsx +7 -1
  59. package/src/modules/offline/OfflinePackLegacy.ts +55 -0
  60. package/src/modules/offline/offlineManager.ts +4 -4
  61. package/src/modules/offline/offlineManagerLegacy.ts +181 -0
  62. package/src/specs/RNMBXMapViewNativeComponent.ts +2 -0
@@ -0,0 +1,444 @@
1
+ package com.rnmapbox.rnmbx.modules
2
+
3
+ import android.os.Build
4
+ import android.util.Log
5
+ import com.facebook.react.bridge.Arguments
6
+ import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
9
+ import com.facebook.react.bridge.ReactMethod
10
+ import com.facebook.react.bridge.ReadableMap
11
+ import com.facebook.react.bridge.UiThreadUtil
12
+ import com.facebook.react.bridge.WritableMap
13
+ import com.facebook.react.module.annotations.ReactModule
14
+ import com.mapbox.bindgen.Expected
15
+ import com.mapbox.geojson.FeatureCollection
16
+ import com.mapbox.geojson.Point
17
+ import com.mapbox.maps.CoordinateBounds
18
+ import com.mapbox.maps.GlyphsRasterizationMode
19
+ import com.mapbox.maps.OfflineRegion
20
+ import com.mapbox.maps.OfflineRegionCallback
21
+ import com.mapbox.maps.OfflineRegionCreateCallback
22
+ import com.mapbox.maps.OfflineRegionDownloadState
23
+ import com.mapbox.maps.OfflineRegionManager
24
+ import com.mapbox.maps.OfflineRegionStatus
25
+ import com.mapbox.maps.OfflineRegionTilePyramidDefinition
26
+ import com.rnmapbox.rnmbx.utils.ConvertUtils
27
+ import com.rnmapbox.rnmbx.utils.extensions.toGeometryCollection
28
+ import com.rnmapbox.rnmbx.utils.writableArrayOf
29
+ import com.rnmapbox.rnmbx.v11compat.offlinemanager.getOfflineRegionManager
30
+ import org.json.JSONException
31
+ import org.json.JSONObject
32
+ import java.io.File
33
+ import java.io.UnsupportedEncodingException
34
+ import java.nio.file.Files
35
+ import java.nio.file.Paths
36
+ import java.nio.file.StandardCopyOption
37
+ import kotlin.math.ceil
38
+
39
+
40
+ @ReactModule(name = RNMBXOfflineModuleLegacy.REACT_CLASS)
41
+ class RNMBXOfflineModuleLegacy(private val mReactContext: ReactApplicationContext) :
42
+ ReactContextBaseJavaModule(
43
+ mReactContext
44
+ ) {
45
+ companion object {
46
+ const val REACT_CLASS = "RNMBXOfflineModuleLegacy"
47
+ const val LOG_TAG = "OfflineModuleLegacy"
48
+ const val DEFAULT_STYLE_URL = "mapbox://styles/mapbox/streets-v11"
49
+ const val DEFAULT_MIN_ZOOM_LEVEL = 10.0
50
+ const val DEFAULT_MAX_ZOOM_LEVEL = 20.0
51
+ const val COMPLETE_REGION_DOWNLOAD_STATE = 2
52
+ }
53
+
54
+ override fun getName(): String {
55
+ return REACT_CLASS
56
+ }
57
+
58
+ val offlineRegionManager: OfflineRegionManager by lazy {
59
+ getOfflineRegionManager {
60
+ RNMBXModule.getAccessToken(mReactContext)
61
+ }
62
+ }
63
+
64
+ private fun makeDefinition(
65
+ bounds: CoordinateBounds,
66
+ options: ReadableMap
67
+ ): OfflineRegionTilePyramidDefinition {
68
+ return OfflineRegionTilePyramidDefinition.Builder()
69
+ .styleURL(ConvertUtils.getString("styleURL", options, DEFAULT_STYLE_URL))
70
+ .bounds(bounds)
71
+ .minZoom(ConvertUtils.getDouble("minZoom", options, DEFAULT_MIN_ZOOM_LEVEL))
72
+ .maxZoom(ConvertUtils.getDouble("maxZoom", options, DEFAULT_MAX_ZOOM_LEVEL))
73
+ .pixelRatio(mReactContext.getResources().getDisplayMetrics().density)
74
+ .glyphsRasterizationMode(GlyphsRasterizationMode.IDEOGRAPHS_RASTERIZED_LOCALLY)
75
+ .build()
76
+ }
77
+
78
+ private fun convertPointPairToBounds(boundsFC: FeatureCollection): CoordinateBounds? {
79
+ val geometryCollection = boundsFC.toGeometryCollection()
80
+ val geometries = geometryCollection.geometries()
81
+ if (geometries.size != 2) {
82
+ return null
83
+ }
84
+ val pt0 = geometries.get(0) as Point?
85
+ val pt1 = geometries.get(1) as Point?
86
+ if (pt0 == null || pt1 == null) {
87
+ return null
88
+ }
89
+ return CoordinateBounds(pt0, pt1)
90
+ }
91
+
92
+ private fun createPackCallback(promise: Promise, metadata: ByteArray): OfflineRegionCreateCallback {
93
+ return OfflineRegionCreateCallback { expected ->
94
+ if (expected.isValue) {
95
+ expected.value?.let {
96
+ it.setOfflineRegionDownloadState(OfflineRegionDownloadState.ACTIVE)
97
+ it.setMetadata(metadata) { expectedMetadata ->
98
+ if (expectedMetadata.isError) {
99
+ promise.reject("createPack error:", "Failed to setMetadata")
100
+ } else {
101
+ Log.d(LOG_TAG, "createPack done:")
102
+ promise.resolve(fromOfflineRegion(it))
103
+ }
104
+ }
105
+ }
106
+ } else {
107
+ Log.d(LOG_TAG, "createPack error:")
108
+ promise.reject("createPack error:", "Failed to create OfflineRegion")
109
+ }
110
+ }
111
+ }
112
+
113
+
114
+ private fun fromOfflineRegion(region: OfflineRegion): WritableMap? {
115
+ val bb = region.tilePyramidDefinition?.bounds
116
+ val map = Arguments.createMap()
117
+
118
+ if (bb === null) return map
119
+
120
+ val jsonBounds = writableArrayOf(
121
+ writableArrayOf(bb.east(), bb.north()),
122
+ writableArrayOf(bb.west(), bb.south())
123
+ )
124
+
125
+ map.putArray("bounds", jsonBounds)
126
+ map.putString("metadata", String(region.metadata))
127
+
128
+ return map
129
+ }
130
+
131
+ private fun getMetadataBytes(metadata: String?): ByteArray? {
132
+ var metadataBytes: ByteArray? = null
133
+ if (metadata == null || metadata.isEmpty()) {
134
+ return metadataBytes
135
+ }
136
+ try {
137
+ metadataBytes = metadata.toByteArray(charset("utf-8"))
138
+ } catch (e: UnsupportedEncodingException) {
139
+ Log.w(LOG_TAG, e.localizedMessage)
140
+ }
141
+ return metadataBytes
142
+ }
143
+
144
+ private fun getRegionByName(
145
+ name: String?,
146
+ offlineRegions: List<OfflineRegion>
147
+ ): OfflineRegion? {
148
+ if (name.isNullOrEmpty()) {
149
+ return null
150
+ }
151
+ for (region in offlineRegions) {
152
+ try {
153
+ val byteMetadata = region.metadata
154
+
155
+ if (byteMetadata != null) {
156
+ val metadata = JSONObject(String(byteMetadata))
157
+ if (name == metadata.getString("name")) {
158
+ return region
159
+ }
160
+ }
161
+ } catch (e: JSONException) {
162
+ Log.w(LOG_TAG, e.localizedMessage)
163
+ }
164
+ }
165
+ return null
166
+ }
167
+
168
+ private fun makeRegionStatus(regionName: String, status: OfflineRegionStatus): WritableMap? {
169
+ val map = Arguments.createMap()
170
+ val progressPercentage = if (status.requiredResourceCount > 0) status.completedResourceCount.toDouble() / status.requiredResourceCount.toDouble() else 0.0
171
+ val percentage = ceil(progressPercentage * 100.0).coerceAtMost(100.0)
172
+ val isCompleted = percentage == 100.0
173
+ val downloadState = if (isCompleted) COMPLETE_REGION_DOWNLOAD_STATE else status.downloadState.ordinal
174
+
175
+ map.putString("name", regionName)
176
+ map.putInt("state", downloadState)
177
+ map.putDouble("percentage", percentage)
178
+ map.putInt("completedResourceCount", status.completedResourceCount.toInt())
179
+ map.putInt("completedResourceSize", status.completedResourceSize.toInt())
180
+ map.putInt("completedTileSize", status.completedTileSize.toInt())
181
+ map.putInt("completedTileCount", status.completedTileCount.toInt())
182
+ map.putInt("requiredResourceCount", status.requiredResourceCount.toInt())
183
+ return map
184
+ }
185
+
186
+ @ReactMethod
187
+ @Throws(JSONException::class)
188
+ fun createPack(options: ReadableMap, promise: Promise) {
189
+ try {
190
+ val metadataBytes: ByteArray? =
191
+ getMetadataBytes(ConvertUtils.getString("metadata", options, ""))
192
+
193
+ val boundsStr = options.getString("bounds")!!
194
+ val boundsFC = FeatureCollection.fromJson(boundsStr)
195
+ val bounds = convertPointPairToBounds(boundsFC)
196
+
197
+ if (metadataBytes == null || bounds == null) {
198
+ promise.reject("createPack error:", "No metadata or bounds set")
199
+ return
200
+ };
201
+
202
+ val definition: OfflineRegionTilePyramidDefinition = makeDefinition(bounds, options)
203
+
204
+ UiThreadUtil.runOnUiThread {
205
+ offlineRegionManager.createOfflineRegion(definition, createPackCallback(promise, metadataBytes))
206
+ }
207
+
208
+ } catch (e: Throwable) {
209
+ promise.reject("createPack error:", e)
210
+ }
211
+ }
212
+
213
+ @ReactMethod
214
+ fun getPacks(promise: Promise) {
215
+ UiThreadUtil.runOnUiThread {
216
+ offlineRegionManager.getOfflineRegions(object: OfflineRegionCallback {
217
+ override fun run(expected: Expected<String, MutableList<OfflineRegion>>) {
218
+ if (expected.isValue) {
219
+ expected.value?.let {
220
+ val payload = Arguments.createArray()
221
+
222
+ for (region in it) {
223
+ payload.pushMap(fromOfflineRegion(region!!))
224
+ }
225
+
226
+ Log.d(LOG_TAG, "getPacks done:" + it.size.toString())
227
+ promise.resolve(payload)
228
+ }
229
+ } else {
230
+ promise.reject("getPacks error:", expected.error)
231
+ Log.d(LOG_TAG, "getPacks error:${expected.error}")
232
+ }
233
+ }
234
+ })
235
+ }
236
+ }
237
+
238
+ @ReactMethod
239
+ fun deletePack(name: String?, promise: Promise) {
240
+ UiThreadUtil.runOnUiThread {
241
+ offlineRegionManager.getOfflineRegions { regionsExpected ->
242
+ if (regionsExpected.isValue) {
243
+ regionsExpected.value?.let { regions ->
244
+ var region = getRegionByName(name, regions);
245
+
246
+ if (region == null) {
247
+ promise.resolve(null);
248
+ Log.w(LOG_TAG, "deleteRegion - Unknown offline region");
249
+ return@getOfflineRegions
250
+ }
251
+
252
+ region.setOfflineRegionDownloadState(OfflineRegionDownloadState.INACTIVE)
253
+
254
+ region.purge { purgeExpected ->
255
+ if (purgeExpected.isError) {
256
+ promise.reject("deleteRegion error:", purgeExpected.error);
257
+ } else {
258
+ promise.resolve(null);
259
+ }
260
+ }
261
+ }
262
+ } else {
263
+ promise.reject("deleteRegion error:", regionsExpected.error);
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ @ReactMethod
270
+ fun invalidatePack(name: String?, promise: Promise) {
271
+ UiThreadUtil.runOnUiThread {
272
+ offlineRegionManager.getOfflineRegions { expected ->
273
+ if (expected.isValue) {
274
+ expected.value?.let { regions ->
275
+ var region = getRegionByName(name, regions);
276
+
277
+ if (region == null) {
278
+ promise.resolve(null);
279
+ Log.w(LOG_TAG, "invalidateRegion - Unknown offline region");
280
+ return@getOfflineRegions
281
+ }
282
+
283
+ region.invalidate { expected ->
284
+ if (expected.isError) {
285
+ promise.reject("invalidateRegion error:", expected.error);
286
+ } else {
287
+ promise.resolve(null);
288
+ }
289
+ }
290
+ }
291
+ } else {
292
+ promise.reject("invalidateRegion error:", expected.error);
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ @ReactMethod
299
+ fun getPackStatus(name: String?, promise: Promise) {
300
+ UiThreadUtil.runOnUiThread {
301
+ offlineRegionManager.getOfflineRegions { expected ->
302
+ if (expected.isValue) {
303
+ expected.value?.let { regions ->
304
+ var region = getRegionByName(name, regions);
305
+
306
+ if (region == null) {
307
+ promise.resolve(null);
308
+ Log.w(LOG_TAG, "getPackStatus - Unknown offline region");
309
+ return@getOfflineRegions
310
+ }
311
+
312
+ region.getStatus {
313
+ if (it.isValue) {
314
+ it.value?.let { status ->
315
+ promise.resolve(makeRegionStatus(name!!, status));
316
+ }
317
+ } else {
318
+ promise.reject("getPackStatus error:", expected.error);
319
+ }
320
+ }
321
+ }
322
+ } else {
323
+ promise.reject("getPackStatus error:", expected.error);
324
+ }
325
+ }
326
+ }
327
+ }
328
+
329
+ @ReactMethod
330
+ fun pausePackDownload(name: String?, promise: Promise) {
331
+ UiThreadUtil.runOnUiThread {
332
+ offlineRegionManager.getOfflineRegions { expected ->
333
+ if (expected.isValue) {
334
+ expected.value?.let { regions ->
335
+ var region = getRegionByName(name, regions);
336
+
337
+ if (region == null) {
338
+ promise.resolve(null);
339
+ Log.w(LOG_TAG, "pausePackDownload - Unknown offline region");
340
+ return@getOfflineRegions
341
+ }
342
+
343
+ region.setOfflineRegionDownloadState(OfflineRegionDownloadState.INACTIVE)
344
+ promise.resolve(null)
345
+ }
346
+ } else {
347
+ promise.reject("pausePackDownload error:", expected.error);
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ @ReactMethod
354
+ fun resumePackDownload(name: String?, promise: Promise) {
355
+ UiThreadUtil.runOnUiThread {
356
+ offlineRegionManager.getOfflineRegions { expected ->
357
+ if (expected.isValue) {
358
+ expected.value?.let { regions ->
359
+ var region = getRegionByName(name, regions);
360
+
361
+ if (region == null) {
362
+ promise.resolve(null);
363
+ Log.w(LOG_TAG, "resumeRegionDownload - Unknown offline region");
364
+ return@getOfflineRegions
365
+ }
366
+
367
+ region.setOfflineRegionDownloadState(OfflineRegionDownloadState.ACTIVE)
368
+ promise.resolve(null);
369
+ }
370
+ } else {
371
+ promise.reject("resumeRegionDownload error:", expected.error);
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ @ReactMethod
378
+ fun resetDatabase(promise: Promise) {
379
+ UiThreadUtil.runOnUiThread {
380
+ var purgedCount = 0
381
+ offlineRegionManager.getOfflineRegions { expected ->
382
+ if (expected.isValue) {
383
+ expected.value?.let { regions ->
384
+ if (regions.size == 0) promise.resolve(null)
385
+
386
+ for (region in regions) {
387
+ region.setOfflineRegionDownloadState(OfflineRegionDownloadState.INACTIVE)
388
+
389
+ region.purge { expected ->
390
+ if (expected.isError) {
391
+ promise.reject("resetDatabase error:", expected.error);
392
+ } else {
393
+ purgedCount++
394
+ if (purgedCount == regions.size) {
395
+ Log.d(LOG_TAG, "resetDatabase done: ${regions.size} packs were purged")
396
+ promise.resolve(null)
397
+ }
398
+ }
399
+ }
400
+ }
401
+ }
402
+ } else {
403
+ promise.reject("resetDatabase error:", expected.error);
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ @ReactMethod
410
+ fun migrateOfflineCache(promise: Promise) {
411
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
412
+ // Old and new cache file paths
413
+ val targetDirectoryPathName = mReactContext.filesDir.absolutePath + "/.mapbox/map_data"
414
+ val sourcePathName = mReactContext.filesDir.absolutePath + "/mbgl-offline.db"
415
+ val sourcePath = Paths.get(sourcePathName)
416
+ val targetPath = Paths.get("$targetDirectoryPathName/map_data.db")
417
+
418
+ try {
419
+ val source = File(sourcePath.toString())
420
+
421
+ if (!source.exists()) {
422
+ Log.d(LOG_TAG, "Nothing to migrate")
423
+ promise.resolve(false)
424
+ return
425
+ }
426
+
427
+ val directory = File(targetDirectoryPathName)
428
+
429
+ directory.mkdirs()
430
+ Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)
431
+ Log.d(LOG_TAG, "v10 cache directory created successfully")
432
+ promise.resolve(true)
433
+ } catch (e: Exception) {
434
+ val mes = "${e}... file move unsuccessful"
435
+ Log.d(LOG_TAG, mes)
436
+ promise.reject(mes)
437
+ }
438
+ } else {
439
+ val mes = "\"migrateOfflineCache only supported on api level 26 or later\""
440
+ Log.w(LOG_TAG, "migrateOfflineCache only supported on api level 26 or later")
441
+ promise.reject(mes)
442
+ }
443
+ }
444
+ }
@@ -100,6 +100,11 @@ object GeoJSONUtils {
100
100
  val map: WritableMap = WritableNativeMap()
101
101
  map.putString("type", "Feature")
102
102
  map.putMap("geometry", toPointGeometry(latLng))
103
+ properties?.let {
104
+ if (it.hasKey("id") == true) {
105
+ map.putString("id", it.getString("id"));
106
+ }
107
+ }
103
108
  map.putMap("properties", properties)
104
109
  return map
105
110
  }
@@ -21,6 +21,10 @@ fun ViewAnnotationOptions.Builder.height(value: Double): ViewAnnotationOptions.B
21
21
  return this.height(value.toInt())
22
22
  }
23
23
 
24
+ fun com.mapbox.maps.ViewAnnotationOptions.Builder.allowOverlapWithPuck(value: Boolean): ViewAnnotationOptions.Builder {
25
+ return this;
26
+ }
27
+
24
28
  abstract class OnViewAnnotationUpdatedListener : _OnViewAnnotationUpdatedListener {
25
29
  override fun onViewAnnotationPositionUpdated(
26
30
  view: View,
@@ -27,7 +27,6 @@ fun ViewAnnotationOptions.Builder.offsets(x: Double, y: Double) {
27
27
  ))
28
28
  }
29
29
 
30
-
31
30
  abstract class OnViewAnnotationUpdatedListener : _OnViewAnnotationUpdatedListener {
32
31
 
33
32
  }
@@ -67,6 +67,9 @@ public class RNMBXMapViewManagerDelegate<T extends View, U extends BaseViewManag
67
67
  case "pitchEnabled":
68
68
  mViewManager.setPitchEnabled(view, new DynamicFromObject(value));
69
69
  break;
70
+ case "deselectAnnotationOnTap":
71
+ mViewManager.setDeselectAnnotationOnTap(view, new DynamicFromObject(value));
72
+ break;
70
73
  case "requestDisallowInterceptTouchEvent":
71
74
  mViewManager.setRequestDisallowInterceptTouchEvent(view, new DynamicFromObject(value));
72
75
  break;
@@ -28,6 +28,7 @@ public interface RNMBXMapViewManagerInterface<T extends View> {
28
28
  void setScrollEnabled(T view, Dynamic value);
29
29
  void setRotateEnabled(T view, Dynamic value);
30
30
  void setPitchEnabled(T view, Dynamic value);
31
+ void setDeselectAnnotationOnTap(T view, Dynamic value);
31
32
  void setRequestDisallowInterceptTouchEvent(T view, Dynamic value);
32
33
  void setProjection(T view, Dynamic value);
33
34
  void setLocalizeLabels(T view, Dynamic value);
@@ -38,6 +38,8 @@ class RNMBXEvent : NSObject, RNMBXEventProtocol {
38
38
  case onUserTrackingModeChange
39
39
  case vectorSourceLayerPress
40
40
  case shapeSourceLayerPress
41
+ case annotationSelected = "annotationselected"
42
+ case annotationDeselected = "annotationdeselected"
41
43
  }
42
44
 
43
45
  init(type: EventType, payload: [String:Any?]?) {
@@ -129,13 +129,16 @@ open class RNMBXMapView: UIView {
129
129
  private var isGestureActive = false
130
130
 
131
131
  var layerWaiters : [String:[(String) -> Void]] = [:]
132
+
133
+ @objc
134
+ public var deselectAnnotationOnTap: Bool = false
132
135
 
133
136
  #if RNMBX_11
134
137
  var cancelables = Set<AnyCancelable>()
135
138
  #endif
136
139
 
137
- lazy var pointAnnotationManager : PointAnnotationManager = {
138
- let result = PointAnnotationManager(annotations: mapView.annotations, mapView: mapView)
140
+ lazy var pointAnnotationManager : RNMBXPointAnnotationManager = {
141
+ let result = RNMBXPointAnnotationManager(annotations: mapView.annotations, mapView: mapView)
139
142
  self._removeMapboxLongPressGestureRecognizer()
140
143
  return result
141
144
  }()
@@ -1158,11 +1161,29 @@ extension RNMBXMapView: GestureManagerDelegate {
1158
1161
  return orderedLayers.lazy.reversed().compactMap { layersToSource[$0.id] }.first ?? sources.first
1159
1162
  }
1160
1163
 
1164
+
1165
+
1166
+ func _tapEvent(_ tapPoint: CGPoint) -> RNMBXEvent {
1167
+ let location = self.mapboxMap.coordinate(for: tapPoint)
1168
+ var geojson = Feature(geometry: .point(Point(location)));
1169
+ geojson.properties = [
1170
+ "screenPointX": .number(Double(tapPoint.x)),
1171
+ "screenPointY": .number(Double(tapPoint.y))
1172
+ ]
1173
+ let event = RNMBXEvent(type:.tap, payload: logged("reactOnPress") { try geojson.toJSON() })
1174
+ return event
1175
+ }
1176
+
1161
1177
  @objc
1162
1178
  func doHandleTap(_ sender: UITapGestureRecognizer) {
1163
1179
  let tapPoint = sender.location(in: self)
1164
1180
  pointAnnotationManager.handleTap(sender) { (_: UITapGestureRecognizer) in
1165
1181
  DispatchQueue.main.async {
1182
+ if (self.deselectAnnotationOnTap) {
1183
+ if (self.pointAnnotationManager.deselectCurrentlySelected(deselectAnnotationOnTap: true)) {
1184
+ return
1185
+ }
1186
+ }
1166
1187
  let touchableSources = self.touchableSources()
1167
1188
  self.doHandleTapInSources(sources: touchableSources, tapPoint: tapPoint, hits: [:], touchedSources: []) { (hits, touchedSources) in
1168
1189
 
@@ -1194,14 +1215,7 @@ extension RNMBXMapView: GestureManagerDelegate {
1194
1215
 
1195
1216
  } else {
1196
1217
  if let reactOnPress = self.reactOnPress {
1197
- let location = self.mapboxMap.coordinate(for: tapPoint)
1198
- var geojson = Feature(geometry: .point(Point(location)));
1199
- geojson.properties = [
1200
- "screenPointX": .number(Double(tapPoint.x)),
1201
- "screenPointY": .number(Double(tapPoint.y))
1202
- ]
1203
- let event = RNMBXEvent(type:.tap, payload: logged("reactOnPress") { try geojson.toJSON() })
1204
- self.fireEvent(event: event, callback: reactOnPress)
1218
+ self.fireEvent(event: self._tapEvent(tapPoint), callback: reactOnPress)
1205
1219
  }
1206
1220
  }
1207
1221
  }
@@ -1341,7 +1355,7 @@ extension RNMBXMapView {
1341
1355
  }
1342
1356
  }
1343
1357
 
1344
- class PointAnnotationManager : AnnotationInteractionDelegate {
1358
+ class RNMBXPointAnnotationManager : AnnotationInteractionDelegate {
1345
1359
  weak var selected : RNMBXPointAnnotation? = nil
1346
1360
  private var draggedAnnotation: PointAnnotation?
1347
1361
 
@@ -1349,6 +1363,44 @@ class PointAnnotationManager : AnnotationInteractionDelegate {
1349
1363
  // We handle taps ourselfs
1350
1364
  // onTap(annotations: annotations)
1351
1365
  }
1366
+
1367
+ func deselectCurrentlySelected(deselectAnnotationOnTap: Bool = false) -> Bool {
1368
+ if let selected = selected {
1369
+ selected.doDeselect(deselectAnnotationOnMapTap: deselectAnnotationOnTap)
1370
+ self.selected = nil
1371
+ return true
1372
+ }
1373
+ return false
1374
+ }
1375
+
1376
+ func onAnnotationClick(pointAnnotation: RNMBXPointAnnotation) {
1377
+ let oldSelected = selected
1378
+ var newSelected: RNMBXPointAnnotation? = pointAnnotation
1379
+
1380
+ if (newSelected == oldSelected) {
1381
+ newSelected = nil
1382
+ }
1383
+
1384
+ deselectCurrentlySelected()
1385
+
1386
+ if let newSelected = newSelected {
1387
+ newSelected.doSelect()
1388
+ selected = newSelected
1389
+ }
1390
+ }
1391
+
1392
+ func lookup(_ annotation: PointAnnotation) -> RNMBXPointAnnotation? {
1393
+ guard let userInfo = annotation.userInfo else {
1394
+ return nil
1395
+ }
1396
+ if let rnmbxPointAnnotationWeakRef = userInfo[RNMBXPointAnnotation.key] as? WeakRef<RNMBXPointAnnotation> {
1397
+ if let rnmbxPointAnnotation = rnmbxPointAnnotationWeakRef.object {
1398
+ return rnmbxPointAnnotation
1399
+ }
1400
+ }
1401
+ return nil
1402
+ }
1403
+
1352
1404
 
1353
1405
  func onTap(annotations: [Annotation]) {
1354
1406
  guard annotations.count > 0 else {
@@ -1356,34 +1408,9 @@ class PointAnnotationManager : AnnotationInteractionDelegate {
1356
1408
  }
1357
1409
 
1358
1410
  for annotation in annotations {
1359
- if let pointAnnotation = annotation as? PointAnnotation,
1360
- let userInfo = pointAnnotation.userInfo {
1361
-
1362
- if let RNMBXPointAnnotation = userInfo[RNMBXPointAnnotation.key] as? WeakRef<RNMBXPointAnnotation> {
1363
- if let pt = RNMBXPointAnnotation.object {
1364
- let position = pt.superview?.convert(pt.layer.position, to: nil)
1365
- let location = pt.map?.mapboxMap.coordinate(for: position!)
1366
- var geojson = Feature(geometry: .point(Point(location!)))
1367
- geojson.identifier = .string(pt.id)
1368
- geojson.properties = [
1369
- "screenPointX": .number(Double(position!.x)),
1370
- "screenPointY": .number(Double(position!.y))
1371
- ]
1372
- let event = RNMBXEvent(type:.tap, payload: logged("doHandleTap") { try geojson.toJSON() })
1373
- if let selected = selected {
1374
- guard let onDeselected = pt.onDeselected else {
1375
- return
1376
- }
1377
- onDeselected(event.toJSON())
1378
- selected.onDeselect()
1379
- }
1380
- guard let onSelected = pt.onSelected else {
1381
- return
1382
- }
1383
- onSelected(event.toJSON())
1384
- pt.onSelect()
1385
- selected = pt
1386
- }
1411
+ if let annotation = annotation as? PointAnnotation {
1412
+ if let pointAnnotation = lookup(annotation) {
1413
+ onAnnotationClick(pointAnnotation: pointAnnotation)
1387
1414
  }
1388
1415
  }
1389
1416
  }