react-native-pointr 9.9.1 → 9.9.3
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.
- package/CHANGELOG.md +12 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/pointr/PTRMapWidgetManager.kt +126 -23
- package/android/src/main/java/com/pointr/PointrModule.kt +23 -8
- package/ios/PTRMapWidgetContainerView.swift +14 -0
- package/ios/PTRNativeLibrary.swift +28 -6
- package/package.json +1 -1
- package/react-native-pointr.podspec +1 -1
- package/src/types/config.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [9.9.3] - 2026-07-22
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Mobile SDK 9.9.3 integration.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [9.9.2] - 2026-07-09
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Mobile SDK 9.9.2 integration.
|
|
17
|
+
|
|
18
|
+
|
|
7
19
|
## [9.9.1] - 2026-06-23
|
|
8
20
|
|
|
9
21
|
### Changed
|
package/android/build.gradle
CHANGED
|
@@ -93,7 +93,7 @@ dependencies {
|
|
|
93
93
|
//noinspection GradleDynamicVersion
|
|
94
94
|
implementation "com.facebook.react:react-android:0.82.1"
|
|
95
95
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
96
|
-
implementation("com.pointrlabs:pointr:9.9.
|
|
96
|
+
implementation("com.pointrlabs:pointr:9.9.3")
|
|
97
97
|
implementation ("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
|
|
98
98
|
implementation 'com.google.android.material:material:1.12.0'
|
|
99
99
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
|
@@ -20,6 +20,7 @@ import com.pointrlabs.core.management.interfaces.PointrListener
|
|
|
20
20
|
import com.pointrlabs.core.management.models.ErrorMessage
|
|
21
21
|
import com.pointrlabs.core.management.models.PTRParams
|
|
22
22
|
import com.pointrlabs.core.management.models.Site
|
|
23
|
+
import com.pointrlabs.ui.map.views.PTRMapWidgetDisplaySpecs
|
|
23
24
|
import com.pointrlabs.core.management.models.deeplink.PTRMapWidgetAction
|
|
24
25
|
import com.pointrlabs.core.management.models.deeplink.PTRMapWidgetBuildingLocation
|
|
25
26
|
import com.pointrlabs.core.management.models.deeplink.PTRMapWidgetFocusAction
|
|
@@ -46,11 +47,13 @@ import com.pointrlabs.ui.map.views.PTRMapFragment
|
|
|
46
47
|
import com.pointrlabs.ui.map.views.PTRMapWidgetFragment
|
|
47
48
|
import com.pointrlabs.ui.map.views.wayfinding.MarkMyCarBottomSheet
|
|
48
49
|
import java.util.concurrent.Semaphore
|
|
50
|
+
import kotlin.reflect.KMutableProperty1
|
|
49
51
|
|
|
50
52
|
|
|
51
53
|
@SuppressLint("LogNotTimber")
|
|
52
54
|
class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
53
|
-
ViewGroupManager<FrameLayout>(), MapEventsListener, MarkMyCarDetailsEventsListener,
|
|
55
|
+
ViewGroupManager<FrameLayout>(), MapEventsListener, MarkMyCarDetailsEventsListener,
|
|
56
|
+
WayfindingEventsListener {
|
|
54
57
|
|
|
55
58
|
override fun getName() = REACT_CLASS
|
|
56
59
|
|
|
@@ -70,6 +73,7 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
70
73
|
private var pendingCommandJson: String? = null
|
|
71
74
|
private var pendingSdkConfigJson: String? = null
|
|
72
75
|
private var commandDidExecute = false
|
|
76
|
+
|
|
73
77
|
// Reset per view instance in createViewInstance so re-navigation works.
|
|
74
78
|
private var commandTriggered = false
|
|
75
79
|
|
|
@@ -88,20 +92,31 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
88
92
|
@ReactProp(name = "mapWidgetConfig")
|
|
89
93
|
fun setMapWidgetConfig(view: FrameLayout, configJson: String?) {
|
|
90
94
|
if (configJson == null) return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
val
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
val obj = org.json.JSONObject(configJson)
|
|
96
|
+
val cfg = PointrModule.mapWidgetConfiguration
|
|
97
|
+
cfg::class.members.filterIsInstance<KMutableProperty1<*, *>>().forEach { kCallable ->
|
|
98
|
+
if (!obj.has(kCallable.name)) {
|
|
99
|
+
val hasFallback = when (kCallable.name) {
|
|
100
|
+
"isBuildingLevelSelectorEnabled" -> obj.has("isLevelSelectorEnabled")
|
|
101
|
+
"ptrMapWidgetDisplaySpecs" -> obj.has("isFullScreen") || obj.has("isEdgeToEdgeEnabled")
|
|
102
|
+
else -> false
|
|
103
|
+
}
|
|
104
|
+
if (!hasFallback) return@forEach
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
val value = if (kCallable.name == "isBuildingLevelSelectorEnabled") {
|
|
108
|
+
obj.getBoolean("isLevelSelectorEnabled")
|
|
109
|
+
} else if (kCallable.name == "ptrMapWidgetDisplaySpecs") {
|
|
110
|
+
val isEdgeToEdgeEnabled = obj.getBoolean("isEdgeToEdgeEnabled")
|
|
111
|
+
val isFullScreen = obj.getBoolean("isFullScreen")
|
|
112
|
+
PTRMapWidgetDisplaySpecs(isFullScreen, isEdgeToEdgeEnabled)
|
|
113
|
+
} else {
|
|
114
|
+
obj.getBoolean(kCallable.name)
|
|
115
|
+
}
|
|
116
|
+
kCallable.setter.call(cfg, value)
|
|
117
|
+
} catch (ex: Exception) {
|
|
118
|
+
Log.e("PTRMapWidgetConfigurationError", "Caught exception: $ex")
|
|
102
119
|
}
|
|
103
|
-
} catch (e: Exception) {
|
|
104
|
-
Log.e(name, "mapWidgetConfig parse error: ${e.message}")
|
|
105
120
|
}
|
|
106
121
|
}
|
|
107
122
|
|
|
@@ -169,30 +184,39 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
169
184
|
args.pushString(site)
|
|
170
185
|
executeCommand(PTRMapWidgetCommandType.SITE, args)
|
|
171
186
|
}
|
|
187
|
+
|
|
172
188
|
PTRMapWidgetCommandType.BUILDING -> {
|
|
173
189
|
args.pushString(site); args.pushString(building)
|
|
174
190
|
executeCommand(PTRMapWidgetCommandType.BUILDING, args)
|
|
175
191
|
}
|
|
192
|
+
|
|
176
193
|
PTRMapWidgetCommandType.LEVEL -> {
|
|
177
194
|
args.pushString(site); args.pushString(building); args.pushInt(level)
|
|
178
195
|
executeCommand(PTRMapWidgetCommandType.LEVEL, args)
|
|
179
196
|
}
|
|
197
|
+
|
|
180
198
|
PTRMapWidgetCommandType.POI -> {
|
|
181
199
|
args.pushString(site); args.pushString(poi)
|
|
182
200
|
executeCommand(PTRMapWidgetCommandType.POI, args)
|
|
183
201
|
}
|
|
202
|
+
|
|
184
203
|
PTRMapWidgetCommandType.PATH -> {
|
|
185
204
|
args.pushString(site); args.pushString(poi)
|
|
186
205
|
executeCommand(PTRMapWidgetCommandType.PATH, args)
|
|
187
206
|
}
|
|
207
|
+
|
|
188
208
|
PTRMapWidgetCommandType.STATIC_PATH -> {
|
|
189
209
|
args.pushString(site); args.pushString(fromPoi); args.pushString(toPoi)
|
|
190
210
|
executeCommand(PTRMapWidgetCommandType.STATIC_PATH, args)
|
|
191
211
|
}
|
|
212
|
+
|
|
192
213
|
PTRMapWidgetCommandType.STATIC_WAYFINDING -> {
|
|
193
|
-
args.pushString(site); args.pushString(sourcePoi); args.pushString(
|
|
214
|
+
args.pushString(site); args.pushString(sourcePoi); args.pushString(
|
|
215
|
+
destinationPoi
|
|
216
|
+
)
|
|
194
217
|
executeCommand(PTRMapWidgetCommandType.STATIC_WAYFINDING, args)
|
|
195
218
|
}
|
|
219
|
+
|
|
196
220
|
else -> Log.w(name, "Unknown command type in command prop: $type")
|
|
197
221
|
}
|
|
198
222
|
} catch (e: Exception) {
|
|
@@ -221,6 +245,18 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
221
245
|
Choreographer.getInstance().postFrameCallback(frameCallback)
|
|
222
246
|
Log.v(name, "Adding itself as listener to map widget")
|
|
223
247
|
ptrMapWidgetFragment?.addListener(this)
|
|
248
|
+
// The SDK adds the fragment via an ASYNCHRONOUS commit. Force it to complete
|
|
249
|
+
// now, while the container view is still attached. Otherwise, if the widget is
|
|
250
|
+
// closed before that add lands (e.g. closed mid-load), the queued transaction
|
|
251
|
+
// runs afterwards against the detached container and crashes with
|
|
252
|
+
// "No view found for id 0x…". Running it here removes that pending-add window.
|
|
253
|
+
try {
|
|
254
|
+
(reactContext.currentActivity as? FragmentActivity)
|
|
255
|
+
?.supportFragmentManager
|
|
256
|
+
?.executePendingTransactions()
|
|
257
|
+
} catch (e: Exception) {
|
|
258
|
+
Log.e(name, "executePendingTransactions after fragment create failed: ${e.message}")
|
|
259
|
+
}
|
|
224
260
|
return ptrMapWidgetFragment!!
|
|
225
261
|
}
|
|
226
262
|
|
|
@@ -239,6 +275,51 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
239
275
|
return frameLayout
|
|
240
276
|
}
|
|
241
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Called by React Native when the <PTRMapWidget /> view is unmounted.
|
|
280
|
+
*
|
|
281
|
+
* The map widget is a Fragment attached to the host Activity's FragmentManager,
|
|
282
|
+
* so removing the React view alone does NOT tear it down — its dialogs/popups
|
|
283
|
+
* (welcome, onboarding, init-error, info page, …) live at the window level and
|
|
284
|
+
* would otherwise remain visible after the widget is closed. Removing the
|
|
285
|
+
* Fragment runs its onDestroyView(), which dismisses every dialog it owns, and
|
|
286
|
+
* cancelling the frame callback stops the per-frame command loop from leaking.
|
|
287
|
+
*/
|
|
288
|
+
override fun onDropViewInstance(view: FrameLayout) {
|
|
289
|
+
super.onDropViewInstance(view)
|
|
290
|
+
|
|
291
|
+
// Stop the per-frame loop that keeps re-measuring / re-triggering commands.
|
|
292
|
+
Choreographer.getInstance().removeFrameCallback(frameCallback)
|
|
293
|
+
|
|
294
|
+
val fragment = ptrMapWidgetFragment
|
|
295
|
+
ptrMapWidgetFragment = null
|
|
296
|
+
// Reset so a subsequent open builds a fresh widget and re-runs its command.
|
|
297
|
+
commandDidExecute = false
|
|
298
|
+
commandTriggered = false
|
|
299
|
+
|
|
300
|
+
if (fragment != null) {
|
|
301
|
+
fragment.removeListener(this)
|
|
302
|
+
val fragmentManager =
|
|
303
|
+
(reactContext.currentActivity as? FragmentActivity)?.supportFragmentManager
|
|
304
|
+
if (fragmentManager != null && !fragmentManager.isDestroyed && fragment.isAdded) {
|
|
305
|
+
// Remove the fragment SYNCHRONOUSLY (commitNow) while the container view
|
|
306
|
+
// still exists. An async commit()/commitAllowingStateLoss() would run a
|
|
307
|
+
// beat later — after React Native has already detached this FrameLayout —
|
|
308
|
+
// and the FragmentManager would then try to (re)create the fragment's view
|
|
309
|
+
// into a container that is gone, crashing with "No view found for id".
|
|
310
|
+
// Wrapped defensively so a lifecycle race can degrade to a no-op, never a
|
|
311
|
+
// crash, on the app's main thread.
|
|
312
|
+
try {
|
|
313
|
+
fragmentManager.beginTransaction()
|
|
314
|
+
.remove(fragment)
|
|
315
|
+
.commitNowAllowingStateLoss()
|
|
316
|
+
} catch (e: Exception) {
|
|
317
|
+
Log.e(name, "Failed to remove map widget fragment on view drop: ${e.message}")
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
242
323
|
/**
|
|
243
324
|
* Handle "create" command (called from JS) and call createFragment method
|
|
244
325
|
*/
|
|
@@ -328,8 +409,10 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
328
409
|
val siteId = args.getString(0).orEmpty()
|
|
329
410
|
val sourcePoiId = args.getString(1).orEmpty()
|
|
330
411
|
val destinationPoiId = args.getString(2).orEmpty()
|
|
331
|
-
val sourceLocation =
|
|
332
|
-
|
|
412
|
+
val sourceLocation =
|
|
413
|
+
PTRMapWidgetPoiLocation(siteId, sourcePoiId, isExternalIdentifier = true)
|
|
414
|
+
val destinationLocation =
|
|
415
|
+
PTRMapWidgetPoiLocation(siteId, destinationPoiId, isExternalIdentifier = true)
|
|
333
416
|
PTRMapWidgetStaticWayfindingAction(sourceLocation, destinationLocation)
|
|
334
417
|
}
|
|
335
418
|
|
|
@@ -380,22 +463,35 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
380
463
|
val siteId = args.getString(5).orEmpty()
|
|
381
464
|
PTRMapWidgetSiteLocation(siteId, isExternal = true)
|
|
382
465
|
}
|
|
466
|
+
|
|
383
467
|
PTRMapWidgetCommandType.BUILDING -> {
|
|
384
468
|
val siteId = args.getString(5).orEmpty()
|
|
385
469
|
val buildingId = args.getString(6).orEmpty()
|
|
386
|
-
PTRMapWidgetBuildingLocation(
|
|
470
|
+
PTRMapWidgetBuildingLocation(
|
|
471
|
+
siteId,
|
|
472
|
+
buildingId,
|
|
473
|
+
isExternalIdentifier = true
|
|
474
|
+
)
|
|
387
475
|
}
|
|
476
|
+
|
|
388
477
|
PTRMapWidgetCommandType.LEVEL -> {
|
|
389
478
|
val siteId = args.getString(5).orEmpty()
|
|
390
479
|
val buildingId = args.getString(6).orEmpty()
|
|
391
480
|
val levelIndex = args.getInt(7)
|
|
392
|
-
PTRMapWidgetLevelLocation(
|
|
481
|
+
PTRMapWidgetLevelLocation(
|
|
482
|
+
siteId,
|
|
483
|
+
buildingId,
|
|
484
|
+
levelIndex,
|
|
485
|
+
isExternalIdentifier = true
|
|
486
|
+
)
|
|
393
487
|
}
|
|
488
|
+
|
|
394
489
|
PTRMapWidgetCommandType.POI -> {
|
|
395
490
|
val siteId = args.getString(5).orEmpty()
|
|
396
491
|
val poiId = args.getString(6).orEmpty()
|
|
397
492
|
PTRMapWidgetPoiLocation(siteId, poiId, isExternalIdentifier = true)
|
|
398
493
|
}
|
|
494
|
+
|
|
399
495
|
else -> {
|
|
400
496
|
// Default to site if invalid focus type
|
|
401
497
|
val siteId = args.getString(5).orEmpty()
|
|
@@ -507,22 +603,26 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
507
603
|
event.putString("siteExternalIdentifier", args[5] as String)
|
|
508
604
|
event.putString("error", args.last().toString())
|
|
509
605
|
}
|
|
606
|
+
|
|
510
607
|
PTRMapWidgetCommandType.BUILDING -> {
|
|
511
608
|
event.putString("siteExternalIdentifier", args[5] as String)
|
|
512
609
|
event.putString("buildingExternalIdentifier", args[6] as String)
|
|
513
610
|
event.putString("error", args.last().toString())
|
|
514
611
|
}
|
|
612
|
+
|
|
515
613
|
PTRMapWidgetCommandType.LEVEL -> {
|
|
516
614
|
event.putString("siteExternalIdentifier", args[5] as String)
|
|
517
615
|
event.putString("buildingExternalIdentifier", args[6] as String)
|
|
518
616
|
event.putInt("levelIndex", (args[7] as Number).toInt())
|
|
519
617
|
event.putString("error", args.last().toString())
|
|
520
618
|
}
|
|
619
|
+
|
|
521
620
|
PTRMapWidgetCommandType.POI -> {
|
|
522
621
|
event.putString("siteExternalIdentifier", args[5] as String)
|
|
523
622
|
event.putString("poiExternalIdentifier", args[6] as String)
|
|
524
623
|
event.putString("error", args.last().toString())
|
|
525
624
|
}
|
|
625
|
+
|
|
526
626
|
else -> {
|
|
527
627
|
event.putString("siteExternalIdentifier", args[5] as String)
|
|
528
628
|
event.putString("error", args.last().toString())
|
|
@@ -544,7 +644,7 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
544
644
|
|
|
545
645
|
private fun onWayfindingEvent(type: Int, poi: Poi?) {
|
|
546
646
|
val event: WritableMap = WritableNativeMap()
|
|
547
|
-
event.putInt("type"
|
|
647
|
+
event.putInt("type", type)
|
|
548
648
|
event.putMap("poi", poi?.map)
|
|
549
649
|
Log.v(name, "Sending event for wayfinding: $event")
|
|
550
650
|
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
|
|
@@ -848,20 +948,23 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
848
948
|
|
|
849
949
|
override fun wayfindingDidShown() {
|
|
850
950
|
Log.v(name, "Wayfinding did shown")
|
|
851
|
-
val destination =
|
|
951
|
+
val destination =
|
|
952
|
+
(ptrMapWidgetFragment?.mapFragment?.currentRouteSession?.destination as? Poi)
|
|
852
953
|
onWayfindingEvent(0, destination)
|
|
853
954
|
|
|
854
955
|
}
|
|
855
956
|
|
|
856
957
|
override fun wayfindingDidTapClose() {
|
|
857
958
|
Log.v(name, "Wayfinding cancelled")
|
|
858
|
-
val destination =
|
|
959
|
+
val destination =
|
|
960
|
+
(ptrMapWidgetFragment?.mapFragment?.currentRouteSession?.destination as? Poi)
|
|
859
961
|
onWayfindingEvent(-1, destination)
|
|
860
962
|
}
|
|
861
963
|
|
|
862
964
|
override fun wayfindingDone() {
|
|
863
965
|
Log.v(name, "Wayfinding done")
|
|
864
|
-
val destination =
|
|
966
|
+
val destination =
|
|
967
|
+
(ptrMapWidgetFragment?.mapFragment?.currentRouteSession?.destination as? Poi)
|
|
865
968
|
onWayfindingEvent(1, destination)
|
|
866
969
|
}
|
|
867
970
|
|
|
@@ -32,6 +32,7 @@ import com.pointrlabs.core.util.models.error.PTRError
|
|
|
32
32
|
import com.pointrlabs.ui.map.models.events_listeners.MapEventsListener
|
|
33
33
|
import com.pointrlabs.ui.map.viewmodels.PTRMapWidgetConfiguration
|
|
34
34
|
import com.pointrlabs.ui.map.views.PTRMapFragment
|
|
35
|
+
import com.pointrlabs.ui.map.views.PTRMapWidgetDisplaySpecs
|
|
35
36
|
import com.pointrlabs.ui.map.views.helper_views.PTRDialog
|
|
36
37
|
import org.json.JSONObject
|
|
37
38
|
import kotlin.random.Random
|
|
@@ -154,6 +155,7 @@ class PointrModule(reactContext: ReactApplicationContext) :
|
|
|
154
155
|
|
|
155
156
|
@ReactMethod
|
|
156
157
|
fun stop() {
|
|
158
|
+
removePointrListeners()
|
|
157
159
|
Pointr.getPointr()?.stop()
|
|
158
160
|
}
|
|
159
161
|
|
|
@@ -491,16 +493,29 @@ class PointrModule(reactContext: ReactApplicationContext) :
|
|
|
491
493
|
}
|
|
492
494
|
}
|
|
493
495
|
|
|
494
|
-
fun setPointrMapWidgetConfiguration(
|
|
495
|
-
mapWidgetConfiguration::class.members.forEach { kCallable ->
|
|
496
|
-
if (kCallable
|
|
497
|
-
|
|
496
|
+
fun setPointrMapWidgetConfiguration(obj: JSONObject) {
|
|
497
|
+
mapWidgetConfiguration::class.members.filterIsInstance<KMutableProperty1<*, *>>().forEach { kCallable ->
|
|
498
|
+
if (!obj.has(kCallable.name)) {
|
|
499
|
+
val hasFallback = when (kCallable.name) {
|
|
500
|
+
"isBuildingLevelSelectorEnabled" -> obj.has("isLevelSelectorEnabled")
|
|
501
|
+
"ptrMapWidgetDisplaySpecs" -> obj.has("isFullScreen") || obj.has("isEdgeToEdgeEnabled")
|
|
502
|
+
else -> false
|
|
503
|
+
}
|
|
504
|
+
if (!hasFallback) return@forEach
|
|
505
|
+
}
|
|
498
506
|
try {
|
|
499
|
-
val value = if (kCallable.name == "isBuildingLevelSelectorEnabled")
|
|
500
|
-
|
|
501
|
-
else
|
|
507
|
+
val value = if (kCallable.name == "isBuildingLevelSelectorEnabled") {
|
|
508
|
+
obj.getBoolean("isLevelSelectorEnabled")
|
|
509
|
+
} else if (kCallable.name == "ptrMapWidgetDisplaySpecs") {
|
|
510
|
+
val isEdgeToEdgeEnabled = obj.getBoolean("isEdgeToEdgeEnabled")
|
|
511
|
+
val isFullScreen = obj.getBoolean("isFullScreen")
|
|
512
|
+
PTRMapWidgetDisplaySpecs(isFullScreen, isEdgeToEdgeEnabled)
|
|
513
|
+
} else {
|
|
514
|
+
obj.getBoolean(kCallable.name)
|
|
515
|
+
}
|
|
502
516
|
kCallable.setter.call(mapWidgetConfiguration, value)
|
|
503
|
-
} catch (
|
|
517
|
+
} catch (ex: Exception) {
|
|
518
|
+
Log.e("PTRMapWidgetConfigurationError", "Caught exception: $ex")
|
|
504
519
|
}
|
|
505
520
|
}
|
|
506
521
|
}
|
|
@@ -339,6 +339,20 @@ import PointrKit
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// React Native is unmounting this view when it is moved to a nil window.
|
|
343
|
+
// Tear the map widget down here so its popups/sheets cannot surface after the
|
|
344
|
+
// widget is closed: removing ourselves as a listener breaks the widget→container
|
|
345
|
+
// retain cycle, letting the view controller (and its whole view hierarchy,
|
|
346
|
+
// including any popup) deallocate instead of lingering.
|
|
347
|
+
override func willMove(toWindow newWindow: UIWindow?) {
|
|
348
|
+
super.willMove(toWindow: newWindow)
|
|
349
|
+
guard newWindow == nil, let mapWidget = mapWidget else { return }
|
|
350
|
+
mapWidget.removeListener(self)
|
|
351
|
+
mapWidget.view.removeFromSuperview()
|
|
352
|
+
wayfindingEventsAdapter = nil
|
|
353
|
+
self.mapWidget = nil
|
|
354
|
+
}
|
|
355
|
+
|
|
342
356
|
private func applySDKConfigIfNeeded() {
|
|
343
357
|
guard let jsonString = sdkConfig,
|
|
344
358
|
let data = jsonString.data(using: .utf8),
|
|
@@ -224,10 +224,21 @@ class PTRNativeLibrary: RCTEventEmitter, PTREventManagerDelegate {
|
|
|
224
224
|
} else {
|
|
225
225
|
let dispatchGroup = DispatchGroup()
|
|
226
226
|
dispatchGroup.enter()
|
|
227
|
+
// Pointr.shared.start(with:completion:) does not fire its completion
|
|
228
|
+
// exactly once — it's invoked on every subsequent state change too
|
|
229
|
+
// (e.g. the transition triggered by a later stop() call also matches
|
|
230
|
+
// `state.rawValue <= 0`). Guard so dispatchGroup.leave() only ever
|
|
231
|
+
// runs once; otherwise a second leave() on an already-balanced group
|
|
232
|
+
// is a fatal "BUG IN CLIENT OF LIBDISPATCH" crash.
|
|
233
|
+
var hasLeftDispatchGroup = false
|
|
234
|
+
let dispatchGroupLock = NSLock()
|
|
227
235
|
Pointr.shared.start(with: PTRNativeLibrary.params) { state in
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
236
|
+
guard state == .running || state.rawValue <= 0 else { return }
|
|
237
|
+
dispatchGroupLock.lock()
|
|
238
|
+
defer { dispatchGroupLock.unlock() }
|
|
239
|
+
guard !hasLeftDispatchGroup else { return }
|
|
240
|
+
hasLeftDispatchGroup = true
|
|
241
|
+
dispatchGroup.leave()
|
|
231
242
|
}
|
|
232
243
|
dispatchGroup.wait()
|
|
233
244
|
if Pointr.shared.state == .running {
|
|
@@ -239,9 +250,20 @@ class PTRNativeLibrary: RCTEventEmitter, PTREventManagerDelegate {
|
|
|
239
250
|
}
|
|
240
251
|
|
|
241
252
|
@objc func stop() {
|
|
242
|
-
Pointr.shared.
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
guard Pointr.shared.state == .running else {
|
|
254
|
+
// Nothing to tear down — avoids double-stop crashes if called twice
|
|
255
|
+
// in a row or before the SDK finished starting.
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
// This module reports `requiresMainQueueSetup() -> false`, so React Native
|
|
259
|
+
// invokes this method on a background queue. Pointr.shared.stop() tears down
|
|
260
|
+
// location/Bluetooth/map resources that must be released on the main thread,
|
|
261
|
+
// so hop over explicitly to avoid a crash.
|
|
262
|
+
DispatchQueue.main.async {
|
|
263
|
+
Pointr.shared.positioningManager?.removeListener(self)
|
|
264
|
+
Pointr.shared.geofenceManager?.removeListener(self)
|
|
265
|
+
Pointr.shared.stop()
|
|
266
|
+
}
|
|
245
267
|
}
|
|
246
268
|
|
|
247
269
|
@objc func requestPermissions() {
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
|
|
17
17
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
18
|
|
|
19
|
-
s.dependency 'PointrKit', '9.9.
|
|
19
|
+
s.dependency 'PointrKit', '9.9.3'
|
|
20
20
|
|
|
21
21
|
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
22
22
|
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|
package/src/types/config.ts
CHANGED
|
@@ -49,6 +49,10 @@ export interface PTRMapWidgetConfiguration {
|
|
|
49
49
|
readonly isQuickAccessEnabled?: boolean;
|
|
50
50
|
/** Show/hide the app banner */
|
|
51
51
|
readonly isAppBannerEnabled?: boolean;
|
|
52
|
+
/** If you will use MapWidget as full screen */
|
|
53
|
+
readonly isFullScreen?: boolean;
|
|
54
|
+
/** If your app has edge to edge enabled. It is usually safer to keep this true */
|
|
55
|
+
readonly isEdgeToEdgeEnabled?: boolean;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/**
|