react-native-pointr 9.6.0 → 9.7.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ 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.7.0] - 2026-04-02
8
+
9
+ ### Changed
10
+ - Mobile SDK 9.7.0 integration.
11
+
12
+
7
13
  ## [9.6.0] - 2026-03-26
8
14
 
9
15
  ### Changed
package/README.md CHANGED
@@ -153,243 +153,87 @@ Then run `npm install` or `yarn install` to install the dependencies.
153
153
 
154
154
  ## 4. Usage
155
155
 
156
- Now you can start using the package in your React Native components. Here's a simple example of how to import on `App.tsx` file:
156
+ Import the package exports and render the map widget:
157
157
 
158
158
  ```typescript
159
- import React, { useRef } from 'react';
160
- import {
161
- requireNativeComponent,
162
- findNodeHandle,
163
- Button,
164
- SafeAreaView,
165
- View,
166
- ScrollView,
167
- NativeSyntheticEvent,
168
- NativeEventEmitter,
169
- NativeModules
170
- } from 'react-native';
171
- import {
172
- PTRSiteCommand,
173
- PTRBuildingCommand,
174
- PTRLevelCommand,
175
- PTRPoiCommand,
176
- PTRPathCommand,
177
- PTRStaticPathCommand,
178
- PTRMarkMyCarLevelCommand,
179
- PTRMarkMyCarSiteCommand,
180
- PTRShowMyCarSiteCommand,
181
- PTRStartAndFocusCommand
182
- } from 'react-native-pointr/src/PTRCommand';
183
- import { showMapWidget } from 'react-native-pointr/src/PTRMapWidgetUtils';
159
+ import { PTRMapWidget, PTRSiteCommand } from 'react-native-pointr';
160
+ import type { PTRConfiguration, PTRMapWidgetConfiguration } from 'react-native-pointr';
184
161
  ```
185
162
 
186
- Define the `PTRMapWidget` component interface and create the component using `requireNativeComponent`:
163
+ The simplest integration passes `sdkConfig`, `mapWidgetConfig`, and `command` as props directly — the widget handles initialization automatically:
187
164
 
188
- ```typescript
189
- interface PTRMapWidgetProps {
190
- style?: any;
191
- onMapWidgetDidEndLoading?: (event: NativeSyntheticEvent<any>) => void;
192
- }
193
-
194
- const PTRMapWidget = requireNativeComponent<PTRMapWidgetProps>('PTRMapWidget');
195
- ```
165
+ ```tsx
166
+ const SDK_CONFIG: PTRConfiguration = {
167
+ clientId: '<CLIENT_ID>',
168
+ licenseKey: '<LICENSE_KEY>',
169
+ baseUrl: 'https://<your-instance>.pointr.cloud',
170
+ };
196
171
 
197
- Initialize and start Pointr instance before doing any operation:
172
+ const MAP_CONFIG: PTRMapWidgetConfiguration = {
173
+ isLevelSelectorEnabled: true,
174
+ isExitButtonEnabled: false,
175
+ };
198
176
 
199
- ```typescript
200
- // Initialize Pointr instance with the provided credentials
201
- NativeModules.PTRNativePointrLibrary.initialize(
202
- "<CLIENT_ID>",
203
- "<LICENSE_KEY>",
204
- "<BASE_URL>",
205
- <LOG_LEVEL> // 0=off, 1=error, 2=warn, 3=info, 4=debug
206
- );
207
-
208
- // Start the Pointr SDK
209
- NativeModules.PTRNativePointrLibrary.start((state: string) => {
210
- console.log(`Pointr state: ${state}`);
211
- });
212
-
213
- // Create event emitter for Pointr events (optional)
214
- const PTREventEmitter = NativeModules.PTRNativePointrLibrary;
215
- const nativeEventEmitter = new NativeEventEmitter(PTREventEmitter);
177
+ function MapScreen() {
178
+ return (
179
+ <PTRMapWidget
180
+ style={{ flex: 1 }}
181
+ sdkConfig={SDK_CONFIG}
182
+ mapWidgetConfig={MAP_CONFIG}
183
+ command={new PTRSiteCommand('<SITE_EXTERNAL_ID>')}
184
+ onMapWidgetDidEndLoading={(e) => console.log('Map loaded', e.nativeEvent)}
185
+ onWayfindingEvent={(e) => console.log('Navigation event', e.nativeEvent)}
186
+ />
187
+ );
188
+ }
216
189
  ```
217
190
 
218
- Implement the map widget in your React Native component:
219
-
220
- ```typescript
221
- function App(): React.JSX.Element {
222
- const ref = useRef<any>(null);
223
-
224
- const handleMapWidgetDidEndLoading = (event: NativeSyntheticEvent<any>) => {
225
- const ptrCommandResponse = event.nativeEvent;
226
- if (ptrCommandResponse.command === "site") {
227
- console.log("Map did end loading:", ptrCommandResponse.command, ptrCommandResponse.siteExternalIdentifier, ptrCommandResponse.error);
228
- } else {
229
- console.log("Map did end loading with unknown parameters:", ptrCommandResponse);
230
- }
231
- };
232
-
233
- const showSite = () => {
234
- const reactTag = findNodeHandle(ref.current);
235
- if (reactTag) {
236
- let command = new PTRSiteCommand("your-site-id");
237
- showMapWidget(reactTag, command);
238
- } else {
239
- console.error("Failed to find node handle for ref");
240
- }
241
- };
242
-
243
- const showBuilding = () => {
244
- const reactTag = findNodeHandle(ref.current);
245
- if (reactTag) {
246
- let command = new PTRBuildingCommand("your-site-id", "your-building-id");
247
- showMapWidget(reactTag, command);
248
- } else {
249
- console.error("Failed to find node handle for ref");
250
- }
251
- };
252
-
253
- const showLevel = () => {
254
- const reactTag = findNodeHandle(ref.current);
255
- if (reactTag) {
256
- let command = new PTRLevelCommand("your-site-id", "your-building-id", 2);
257
- showMapWidget(reactTag, command);
258
- } else {
259
- console.error("Failed to find node handle for ref");
260
- }
261
- };
262
-
263
- const showPoi = () => {
264
- const reactTag = findNodeHandle(ref.current);
265
- if (reactTag) {
266
- let command = new PTRPoiCommand("your-site-id", "your-poi-id");
267
- showMapWidget(reactTag, command);
268
- } else {
269
- console.error("Failed to find node handle for ref");
270
- }
271
- };
272
-
273
- const showPath = () => {
274
- const reactTag = findNodeHandle(ref.current);
275
- if (reactTag) {
276
- let command = new PTRPathCommand("your-site-id", "your-poi-id");
277
- showMapWidget(reactTag, command);
278
- } else {
279
- console.error("Failed to find node handle for ref");
280
- }
281
- };
191
+ To manage the SDK lifecycle and position updates at the app level, use the `usePointrSdk` hook:
282
192
 
283
- const showStaticPath = () => {
284
- const reactTag = findNodeHandle(ref.current);
285
- if (reactTag) {
286
- let command = new PTRStaticPathCommand("your-site-id", "from-poi-id", "to-poi-id");
287
- showMapWidget(reactTag, command);
288
- } else {
289
- console.error("Failed to find node handle for ref");
290
- }
291
- };
193
+ ```tsx
194
+ import { usePointrSdk, usePointrPosition } from 'react-native-pointr';
292
195
 
293
- const markMyCarLevel = () => {
294
- const reactTag = findNodeHandle(ref.current);
295
- if (reactTag) {
296
- let command = new PTRMarkMyCarLevelCommand("your-site-id", "your-building-id", 1, true);
297
- showMapWidget(reactTag, command);
298
- } else {
299
- console.error("Failed to find node handle for ref");
300
- }
301
- };
196
+ function App() {
197
+ const { sdk, isStarted, state } = usePointrSdk(SDK_CONFIG);
302
198
 
303
- const markMyCarSite = () => {
304
- const reactTag = findNodeHandle(ref.current);
305
- if (reactTag) {
306
- let command = new PTRMarkMyCarSiteCommand("your-site-id", true);
307
- showMapWidget(reactTag, command);
308
- } else {
309
- console.error("Failed to find node handle for ref");
310
- }
311
- };
312
-
313
- const showMyCarSite = () => {
314
- const reactTag = findNodeHandle(ref.current);
315
- if (reactTag) {
316
- let command = new PTRShowMyCarSiteCommand("your-site-id", true);
317
- showMapWidget(reactTag, command);
318
- } else {
319
- console.error("Failed to find node handle for ref");
320
- }
321
- };
199
+ const position = usePointrPosition(isStarted);
322
200
 
323
- const startAndFocusSite = () => {
324
- const reactTag = findNodeHandle(ref.current);
325
- if (reactTag) {
326
- const siteCommand = new PTRSiteCommand("your-site-id");
327
- const command = new PTRStartAndFocusCommand(
328
- "your-client-id",
329
- "your-license-key",
330
- "your-base-url",
331
- 0,
332
- siteCommand
333
- );
334
- showMapWidget(reactTag, command);
335
- } else {
336
- console.error("Failed to find node handle for ref");
337
- }
201
+ const checkPosition = async () => {
202
+ const loc = await sdk.getCurrentLocation();
203
+ if (loc) console.log(loc.latitude, loc.longitude, loc.levelIndex);
338
204
  };
339
-
340
- return (
341
- <SafeAreaView style={{ flex: 1 }}>
342
- <View style={{ flex: 1 }}>
343
- <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }}>
344
- <Button title="Show Site" onPress={showSite} />
345
- <View style={{ height: 12 }} />
346
- <Button title="Show Building" onPress={showBuilding} />
347
- <View style={{ height: 12 }} />
348
- <Button title="Show Level" onPress={showLevel} />
349
- <View style={{ height: 12 }} />
350
- <Button title="Show POI" onPress={showPoi} />
351
- <View style={{ height: 12 }} />
352
- <Button title="Show Path" onPress={showPath} />
353
- <View style={{ height: 12 }} />
354
- <Button title="Show Static Path" onPress={showStaticPath} />
355
- <View style={{ height: 12 }} />
356
- <Button title="Mark My Car Level" onPress={markMyCarLevel} />
357
- <View style={{ height: 12 }} />
358
- <Button title="Mark My Car Site" onPress={markMyCarSite} />
359
- <View style={{ height: 12 }} />
360
- <Button title="Show My Car Site" onPress={showMyCarSite} />
361
- <View style={{ height: 12 }} />
362
- <Button title="Start & Focus Site" onPress={startAndFocusSite} />
363
- </ScrollView>
364
- </View>
365
- <PTRMapWidget
366
- ref={ref}
367
- style={{ flex: 4 }}
368
- onMapWidgetDidEndLoading={handleMapWidgetDidEndLoading}
369
- />
370
- </SafeAreaView>
371
- );
372
205
  }
373
-
374
- export default App;
375
206
  ```
376
207
 
377
208
  ### Available Commands
378
209
 
379
- The package provides several command types for interacting with the map:
210
+ ```typescript
211
+ import { PTRSiteCommand, PTRBuildingCommand, PTRLevelCommand, PTRPoiCommand,
212
+ PTRPathCommand, PTRStaticPathCommand, executeMapCommand } from 'react-native-pointr';
213
+
214
+ executeMapCommand(mapRef, new PTRSiteCommand('site-id'));
215
+ executeMapCommand(mapRef, new PTRBuildingCommand('site-id', 'building-id'));
216
+ executeMapCommand(mapRef, new PTRLevelCommand('site-id', 'building-id', 2));
217
+ executeMapCommand(mapRef, new PTRPoiCommand('site-id', 'poi-id'));
218
+ executeMapCommand(mapRef, new PTRPathCommand('site-id', 'poi-id'));
219
+ executeMapCommand(mapRef, new PTRStaticPathCommand('site-id', 'from-poi', 'to-poi'));
220
+ ```
221
+
222
+ ### Available Hooks
380
223
 
381
- - **PTRSiteCommand**: Display a specific site
382
- - **PTRBuildingCommand**: Display a specific building within a site
383
- - **PTRLevelCommand**: Display a specific level of a building
384
- - **PTRPoiCommand**: Display and focus on a specific point of interest (POI)
385
- - **PTRPathCommand**: Display a navigation path from the current location to a POI
386
- - **PTRStaticPathCommand**: Display a navigation path between two POIs
387
- - **PTRMarkMyCarLevelCommand**: Mark car location at a specific level
388
- - **PTRMarkMyCarSiteCommand**: Mark car location at site level
389
- - **PTRShowMyCarSiteCommand**: Show marked car location on the site
390
- - **PTRStartAndFocusCommand**: Initialize SDK and focus on a location in one command
224
+ ```typescript
225
+ import { usePointrSdk, usePointrPosition, usePointrPois,
226
+ usePointrGeofence, usePointrBuildingClick, usePointrSiteClick } from 'react-native-pointr';
227
+
228
+ const { sdk, state, isInitialized, isStarted } = usePointrSdk(config);
229
+ const position = usePointrPosition(enabled);
230
+ const { pois, loading, error, refetch } = usePointrPois(siteId);
231
+ usePointrGeofence((event) => console.log('Geofence:', event));
232
+ usePointrBuildingClick((event) => console.log('Building:', event));
233
+ usePointrSiteClick((event) => console.log('Site:', event));
234
+ ```
391
235
 
392
- For more information about the available functionality, check the [API documentation](https://docs.pointr.tech/docs/8.x/Developer%20Portal/API%20Reference/api%20ref-mobile/).
236
+ For the complete API see the [API Reference](./API_REFERENCE.md).
393
237
 
394
238
  ## 5. Run the App
395
239
 
@@ -408,3 +252,18 @@ After completing the setup, start your app by running:
408
252
  ```
409
253
 
410
254
  That’s it! Your package should now be installed and ready to use in your React Native project. For more details on usage and advanced configuration, refer to the [API documentation](https://docs.pointr.tech/docs/8.x/Developer%20Portal/API%20Reference/api%20ref-mobile/).
255
+ ## 6. Run the Example App
256
+
257
+ To explore the included demo app:
258
+
259
+ ```bash
260
+ cd example/pointr_rn_demo
261
+ npm install
262
+
263
+ # iOS
264
+ cd ios && pod install && cd ..
265
+ npm run ios
266
+
267
+ # Android
268
+ npm run android
269
+ ```
@@ -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.6.0")
96
+ implementation("com.pointrlabs:pointr:9.7.0")
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'
@@ -12,6 +12,7 @@ import com.facebook.react.bridge.WritableMap
12
12
  import com.facebook.react.bridge.WritableNativeMap
13
13
  import com.facebook.react.uimanager.ThemedReactContext
14
14
  import com.facebook.react.uimanager.ViewGroupManager
15
+ import com.facebook.react.uimanager.annotations.ReactProp
15
16
  import com.facebook.react.uimanager.events.RCTEventEmitter
16
17
  import com.pointrlabs.core.management.DataManager
17
18
  import com.pointrlabs.core.management.Pointr
@@ -57,6 +58,151 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
57
58
 
58
59
  private var ptrMapWidgetFragment: PTRMapWidgetFragment? = null
59
60
 
61
+ // Declarative prop support -----------------------------------------------
62
+ // When a `command` JSON prop is passed on <PTRMapWidget />, the manager
63
+ // auto-executes it once the view is attached rather than requiring a JS
64
+ // imperative command dispatch.
65
+ //
66
+ // JSON shape: { "type": "site"|"building"|"level"|"poi"|"path"|"staticPath"|"staticWayfinding",
67
+ // "site": String, "building"?: String, "level"?: Int,
68
+ // "poi"?: String, "fromPoi"?: String, "toPoi"?: String,
69
+ // "sourcePoi"?: String, "destinationPoi"?: String }
70
+ private var pendingCommandJson: String? = null
71
+ private var pendingSdkConfigJson: String? = null
72
+ private var commandDidExecute = false
73
+ // Reset per view instance in createViewInstance so re-navigation works.
74
+ private var commandTriggered = false
75
+
76
+ @ReactProp(name = "command")
77
+ fun setCommand(view: FrameLayout, commandJson: String?) {
78
+ pendingCommandJson = commandJson
79
+ commandDidExecute = false
80
+ }
81
+
82
+ @ReactProp(name = "sdkConfig")
83
+ fun setSdkConfig(view: FrameLayout, configJson: String?) {
84
+ pendingSdkConfigJson = configJson
85
+ applySdkConfigIfNeeded()
86
+ }
87
+
88
+ @ReactProp(name = "mapWidgetConfig")
89
+ fun setMapWidgetConfig(view: FrameLayout, configJson: String?) {
90
+ if (configJson == null) return
91
+ try {
92
+ val obj = org.json.JSONObject(configJson)
93
+ val cfg = PointrModule.mapWidgetConfiguration
94
+ cfg::class.members.filterIsInstance<kotlin.reflect.KMutableProperty1<*, *>>().forEach { prop ->
95
+ val jsonKey = if (prop.name == "isBuildingLevelSelectorEnabled") "isLevelSelectorEnabled" else prop.name
96
+ if (!obj.has(jsonKey)) return@forEach
97
+ try {
98
+ val value = obj.getBoolean(jsonKey)
99
+ @Suppress("UNCHECKED_CAST")
100
+ (prop as kotlin.reflect.KMutableProperty1<Any, Boolean>).set(cfg, value)
101
+ } catch (_: Exception) {}
102
+ }
103
+ } catch (e: Exception) {
104
+ Log.e(name, "mapWidgetConfig parse error: ${e.message}")
105
+ }
106
+ }
107
+
108
+ private fun applySdkConfigIfNeeded() {
109
+ val json = pendingSdkConfigJson ?: return
110
+ try {
111
+ val obj = org.json.JSONObject(json)
112
+ val clientId = obj.optString("clientId")
113
+ val licenseKey = obj.optString("licenseKey")
114
+ val baseUrl = obj.optString("baseUrl")
115
+ if (clientId.isNotEmpty() && licenseKey.isNotEmpty() && baseUrl.isNotEmpty()) {
116
+ val params = PTRParams(clientId, licenseKey, baseUrl)
117
+ val logLevel = obj.optInt("logLevel", Plog.LogLevel.ERROR.ordinal)
118
+ params.logLevel = try {
119
+ Plog.LogLevel.entries[logLevel]
120
+ } catch (_: Exception) {
121
+ Plog.LogLevel.ERROR
122
+ }
123
+ PointrModule.mapWidgetConfiguration.sdkParams = params
124
+ // Initialize the Pointr SDK if it has not been initialized yet
125
+ // (covers the case where sdkConfig is used without a prior pointrSdk.initialize() call).
126
+ if (Pointr.getPointr() == null) {
127
+ Pointr.with(reactContext, params)
128
+ }
129
+ }
130
+ } catch (e: Exception) {
131
+ Log.e(name, "sdkConfig parse error: ${e.message}")
132
+ }
133
+ }
134
+
135
+ private fun executePendingCommandIfNeeded() {
136
+ val json = pendingCommandJson ?: return
137
+ if (commandDidExecute) return
138
+ commandDidExecute = true
139
+ Thread {
140
+ // Block on background thread until Pointr is running (semaphore wait).
141
+ // If Pointr never reaches RUNNING (e.g. SDK not initialized), skip entirely
142
+ // so we never block the main thread inside getMapWidgetFragment.
143
+ if (!waitForPointrToRun()) {
144
+ Log.e(name, "Pointr did not reach RUNNING state — skipping declarative command")
145
+ return@Thread
146
+ }
147
+ // Fragment transactions must happen on the main thread.
148
+ android.os.Handler(android.os.Looper.getMainLooper()).post {
149
+ // Guard: activity may be null if the user navigated away while we were waiting.
150
+ if (reactContext.currentActivity == null) {
151
+ Log.e(name, "No current activity — cannot execute declarative map command")
152
+ return@post
153
+ }
154
+ try {
155
+ val obj = org.json.JSONObject(json)
156
+ val type = obj.optString("type")
157
+ val site = obj.optString("site", "")
158
+ val building = obj.optString("building", "")
159
+ val level = obj.optInt("level", 0)
160
+ val poi = obj.optString("poi", "")
161
+ val fromPoi = obj.optString("fromPoi", "")
162
+ val toPoi = obj.optString("toPoi", "")
163
+ val sourcePoi = obj.optString("sourcePoi", "")
164
+ val destinationPoi = obj.optString("destinationPoi", "")
165
+
166
+ val args = com.facebook.react.bridge.Arguments.createArray()
167
+ when (type) {
168
+ PTRMapWidgetCommandType.SITE -> {
169
+ args.pushString(site)
170
+ executeCommand(PTRMapWidgetCommandType.SITE, args)
171
+ }
172
+ PTRMapWidgetCommandType.BUILDING -> {
173
+ args.pushString(site); args.pushString(building)
174
+ executeCommand(PTRMapWidgetCommandType.BUILDING, args)
175
+ }
176
+ PTRMapWidgetCommandType.LEVEL -> {
177
+ args.pushString(site); args.pushString(building); args.pushInt(level)
178
+ executeCommand(PTRMapWidgetCommandType.LEVEL, args)
179
+ }
180
+ PTRMapWidgetCommandType.POI -> {
181
+ args.pushString(site); args.pushString(poi)
182
+ executeCommand(PTRMapWidgetCommandType.POI, args)
183
+ }
184
+ PTRMapWidgetCommandType.PATH -> {
185
+ args.pushString(site); args.pushString(poi)
186
+ executeCommand(PTRMapWidgetCommandType.PATH, args)
187
+ }
188
+ PTRMapWidgetCommandType.STATIC_PATH -> {
189
+ args.pushString(site); args.pushString(fromPoi); args.pushString(toPoi)
190
+ executeCommand(PTRMapWidgetCommandType.STATIC_PATH, args)
191
+ }
192
+ PTRMapWidgetCommandType.STATIC_WAYFINDING -> {
193
+ args.pushString(site); args.pushString(sourcePoi); args.pushString(destinationPoi)
194
+ executeCommand(PTRMapWidgetCommandType.STATIC_WAYFINDING, args)
195
+ }
196
+ else -> Log.w(name, "Unknown command type in command prop: $type")
197
+ }
198
+ } catch (e: Exception) {
199
+ Log.e(name, "Failed to execute command prop: ${e.message}")
200
+ }
201
+ }
202
+ }.start()
203
+ }
204
+ // ------------------------------------------------------------------------
205
+
60
206
  private fun getMapWidgetFragment(action: PTRMapWidgetAction): PTRMapWidgetFragment {
61
207
  Log.i(name, "Creating map widget fragment with action: $action")
62
208
  waitForPointrToRun()
@@ -78,6 +224,11 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
78
224
  */
79
225
  override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
80
226
  frameLayout = FrameLayout(reactContext)
227
+ commandDidExecute = false
228
+ commandTriggered = false // reset so re-navigation triggers the command again
229
+ // Start the frame loop immediately so the declarative command prop can
230
+ // fire once the view has been measured and laid out by React Native.
231
+ Choreographer.getInstance().postFrameCallback(frameCallback)
81
232
  return frameLayout
82
233
  }
83
234
 
@@ -679,6 +830,11 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
679
830
  frameLayout.bottom
680
831
  )
681
832
  frameLayout.viewTreeObserver.dispatchOnGlobalLayout()
833
+ // Execute a declarative command prop once the view has non-zero dimensions.
834
+ if (!commandTriggered && frameLayout.width > 0 && frameLayout.height > 0) {
835
+ commandTriggered = true
836
+ executePendingCommandIfNeeded()
837
+ }
682
838
  Choreographer.getInstance().postFrameCallback(this)
683
839
  }
684
840
  }