@applicaster/zapp-react-dom-app 12.0.0-alpha.7855034103 → 12.0.0-alpha.7990242848

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.
@@ -31,14 +31,23 @@ import {
31
31
  import { PlayerContentContext } from "@applicaster/zapp-react-dom-ui-components/Contexts";
32
32
 
33
33
  import { withNavigator } from "@applicaster/zapp-react-native-ui-components/Decorators/Navigator";
34
- import { Platform } from "react-native";
35
34
  import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
36
35
  import { getXray } from "@applicaster/zapp-react-native-utils/logger";
37
36
  import { confirmationDialogStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ConfirmDialogState";
37
+ import {
38
+ isLgPlatform,
39
+ isSamsungPlatform,
40
+ isVizioPlatform,
41
+ } from "@applicaster/zapp-react-native-utils/reactUtils";
42
+
43
+ import { OnScreenKeyboard } from "@applicaster/zapp-react-dom-ui-components/Components/OnScreenKeyboard";
38
44
 
39
45
  const { withXray } = getXray();
40
46
  const globalAny: any = global;
41
47
 
48
+ const shouldUseOnScreenKeyboard = () =>
49
+ isVizioPlatform() || window.applicaster.useOnScreenKeyboard;
50
+
42
51
  type Props = {
43
52
  displayState: string;
44
53
  setDisplayState: (state: string) => void;
@@ -56,571 +65,562 @@ type Props = {
56
65
  };
57
66
  };
58
67
 
59
- type State = { isKeyboardVisible: boolean };
68
+ type State = { isKeyboardVisible: boolean; keyboardInput: string };
60
69
 
61
70
  type KeyCode = { code: string; keyCode: number | string };
62
71
 
63
72
  export const alertConfirmationMesage =
64
73
  "Are you sure you want to exit application?";
65
74
 
66
- export function withInteractionManager(Component: React.FC) {
67
- class InteractionManager extends React.Component<Props, State> {
68
- onKeyDownListener: (keyCode: KeyCode) => void;
69
- onMouseDownListener: (event: MouseEvent) => void;
70
- onMouseMoveListener: (event: MouseEvent) => void;
71
- onScrollListener: (event: MouseEvent) => void;
72
-
73
- longKeyPressTimeout: any | null;
74
- longPressActive: boolean;
75
- timeout: any | null;
76
- sequenceTimeout: NodeJS.Timeout | null; // timeoutID
77
- userSequence: number[] = [];
78
- confirmDialog: any;
75
+ class InteractionManager extends React.Component<Props, State> {
76
+ onKeyDownListener: (keyCode: KeyCode) => void;
77
+ onMouseDownListener: (event: MouseEvent) => void;
78
+ onMouseMoveListener: (event: MouseEvent) => void;
79
+ onScrollListener: (event: MouseEvent) => void;
80
+
81
+ longKeyPressTimeout: any | null;
82
+ longPressActive: boolean;
83
+ timeout: any | null;
84
+ sequenceTimeout: NodeJS.Timeout | null; // timeoutID
85
+ userSequence: number[] = [];
86
+ confirmDialog: any;
87
+ isDismissing = false;
88
+
89
+ constructor(props) {
90
+ super(props);
91
+
92
+ this.onKeyDownListener = this.onKeyDown.bind(this);
93
+
94
+ this.state = {
95
+ isKeyboardVisible: false,
96
+ keyboardInput: "",
97
+ };
98
+
99
+ this.onMouseMoveListener = debounce({
100
+ fn: this.onMouseMove,
101
+ context: this,
102
+ });
103
+
104
+ this.onScrollListener = debounce({
105
+ fn: this.onScroll,
106
+ wait: 100,
107
+ context: this,
108
+ });
109
+
110
+ this.onMouseDownListener = debounce({
111
+ fn: this.onMouseDown,
112
+ context: this,
113
+ });
114
+
115
+ this.longKeyPressTimeout = null;
116
+ this.longPressActive = false;
117
+ this.confirmDialog = confirmationDialogStore.getState();
118
+ this.resetHudTimeout = this.resetHudTimeout.bind(this);
119
+ this.cursorVisibilityChange = this.cursorVisibilityChange.bind(this);
120
+ this.onKeyDown = this.onKeyDown.bind(this);
121
+ this.onKeyUp = this.onKeyUp.bind(this);
122
+ this.onMouseMove = this.onMouseMove.bind(this);
123
+ this.onScroll = this.onScroll.bind(this);
124
+ this.onMouseDown = this.onMouseDown.bind(this);
125
+ this.onKeyboardStateChange = this.onKeyboardStateChange.bind(this);
126
+ this.onConfirmDialogOpen = this.onConfirmDialogOpen.bind(this);
127
+ this.onConfirmDialogClose = this.onConfirmDialogClose.bind(this);
128
+ this.performExit = this.performExit.bind(this);
129
+ this.handleInputFocus = this.handleInputFocus.bind(this);
130
+ this.handleInputBlur = this.handleInputBlur.bind(this);
131
+ this.handleKeyboardInput = this.handleKeyboardInput.bind(this);
132
+ this.handleKeyboardDismiss = this.handleKeyboardDismiss.bind(this);
133
+
134
+ this.confirmDialog.setConfirmAction(this.performExit);
135
+ this.confirmDialog.setCancelAction(this.onConfirmDialogClose);
136
+ }
79
137
 
80
- constructor(props) {
81
- super(props);
138
+ componentDidMount() {
139
+ window.addEventListener("keydown", this.onKeyDownListener);
140
+ window.addEventListener("keyup", this.onKeyUp);
141
+ window.addEventListener("mousemove", this.onMouseMoveListener);
82
142
 
83
- this.onKeyDownListener = this.onKeyDown.bind(this);
143
+ document.addEventListener("wheel", this.onScrollListener, {
144
+ passive: true,
145
+ });
84
146
 
85
- this.state = {
86
- isKeyboardVisible: false,
87
- };
147
+ document.addEventListener("mousedown", this.onMouseDownListener);
88
148
 
89
- this.onMouseMoveListener = debounce({
90
- fn: this.onMouseMove,
91
- context: this,
92
- });
149
+ document.addEventListener(
150
+ "keyboardStateChange",
151
+ this.onKeyboardStateChange,
152
+ false
153
+ );
93
154
 
94
- this.onScrollListener = debounce({
95
- fn: this.onScroll,
96
- wait: 100,
97
- context: this,
98
- });
155
+ document.addEventListener("focusin", this.handleInputFocus);
99
156
 
100
- this.onMouseDownListener = debounce({
101
- fn: this.onMouseDown,
102
- context: this,
103
- });
157
+ document.addEventListener(
158
+ "cursorStateChange",
159
+ this.cursorVisibilityChange,
160
+ false
161
+ );
162
+ }
104
163
 
105
- this.longKeyPressTimeout = null;
106
- this.longPressActive = false;
107
- this.confirmDialog = confirmationDialogStore.getState();
108
- this.resetHudTimeout = this.resetHudTimeout.bind(this);
109
- this.cursorVisibilityChange = this.cursorVisibilityChange.bind(this);
110
- this.onKeyDown = this.onKeyDown.bind(this);
111
- this.onKeyUp = this.onKeyUp.bind(this);
112
- this.onMouseMove = this.onMouseMove.bind(this);
113
- this.onScroll = this.onScroll.bind(this);
114
- this.onMouseDown = this.onMouseDown.bind(this);
115
- this.onKeyboardStateChange = this.onKeyboardStateChange.bind(this);
116
- this.onConfirmDialogOpen = this.onConfirmDialogOpen.bind(this);
117
- this.onConfirmDialogClose = this.onConfirmDialogClose.bind(this);
118
- this.performExit = this.performExit.bind(this);
119
-
120
- this.confirmDialog.setConfirmAction(this.performExit);
121
- this.confirmDialog.setCancelAction(this.onConfirmDialogClose);
122
- }
164
+ componentWillUnmount() {
165
+ window.removeEventListener("keydown", this.onKeyDownListener);
166
+ window.removeEventListener("keyup", this.onKeyUp);
167
+ document.removeEventListener("wheel", this.onScrollListener);
123
168
 
124
- componentDidMount() {
125
- window.addEventListener("keydown", this.onKeyDownListener);
126
- window.addEventListener("keyup", this.onKeyUp);
127
- window.addEventListener("mousemove", this.onMouseMoveListener);
169
+ document.removeEventListener(
170
+ "cursorStateChange",
171
+ this.cursorVisibilityChange
172
+ );
128
173
 
129
- document.addEventListener("wheel", this.onScrollListener, {
130
- passive: true,
131
- });
174
+ document.removeEventListener("focusin", this.handleInputFocus);
132
175
 
133
- document.addEventListener("mousedown", this.onMouseDownListener);
176
+ document.removeEventListener("mousedown", this.onMouseDownListener);
134
177
 
135
- document.addEventListener(
136
- "keyboardStateChange",
137
- this.onKeyboardStateChange,
138
- false
139
- );
140
-
141
- document.addEventListener(
142
- "cursorStateChange",
143
- this.cursorVisibilityChange,
144
- false
145
- );
178
+ if (this.timeout) {
179
+ this.clearTimeout();
146
180
  }
147
181
 
148
- componentWillUnmount() {
149
- window.removeEventListener("keydown", this.onKeyDownListener);
150
- window.removeEventListener("keyup", this.onKeyUp);
151
- document.removeEventListener("wheel", this.onScrollListener);
152
-
153
- document.removeEventListener(
154
- "cursorStateChange",
155
- this.cursorVisibilityChange
156
- );
157
-
158
- document.removeEventListener(
159
- "keyboardStateChange",
160
- this.onKeyboardStateChange
161
- );
162
-
163
- document.removeEventListener("mousedown", this.onMouseDownListener);
182
+ this.isDismissing = false;
183
+ }
164
184
 
165
- if (this.timeout) {
166
- this.clearTimeout();
167
- }
185
+ performExit() {
186
+ if (isSamsungPlatform()) {
187
+ this.onConfirmDialogClose();
188
+ sendQuickBrickEvent(QUICK_BRICK_EVENTS.MOVE_APP_TO_BACKGROUND);
168
189
  }
169
190
 
170
- performExit() {
171
- if (Platform.OS === ("samsung_tv" as QuickBrickPlatforms)) {
172
- this.onConfirmDialogClose();
173
- sendQuickBrickEvent(QUICK_BRICK_EVENTS.MOVE_APP_TO_BACKGROUND);
174
- }
191
+ if (isLgPlatform()) {
192
+ // webOS recommend to use this method when implemention a custom exit
193
+ globalAny.close();
194
+ }
175
195
 
176
- if (Platform.OS === ("lg_tv" as QuickBrickPlatforms)) {
177
- // webOS recommend to use this method when implemention a custom exit
178
- globalAny.close();
179
- }
196
+ if (isVizioPlatform()) {
197
+ window?.VIZIO?.exitApplication();
180
198
  }
199
+ }
181
200
 
182
- handleWebOsBack() {
183
- const systemBack = globalAny?.webOS?.platformBack;
201
+ handleWebOsBack() {
202
+ const systemBack = globalAny?.webOS?.platformBack;
184
203
 
185
- if (systemBack) {
186
- systemBack();
187
- } else {
188
- /**
189
- * In case webOS failed to show the exit pop-up, we show a regular promtp as fallback
190
- */
191
- showConfirmationDialog({
192
- title: "",
193
- message: alertConfirmationMesage,
194
- confirmCompletion: this.performExit,
195
- });
196
- }
204
+ if (systemBack) {
205
+ systemBack();
206
+ } else {
207
+ /**
208
+ * In case webOS failed to show the exit pop-up, we show a regular promtp as fallback
209
+ */
210
+ showConfirmationDialog({
211
+ title: "",
212
+ message: alertConfirmationMesage,
213
+ confirmCompletion: this.performExit,
214
+ });
197
215
  }
216
+ }
198
217
 
199
- /**
200
- * This methods sends the app to background in whatever way is appropriate for each platform
201
- * In the case of Tizen it shows a custom dialog on confirm it exits the app
202
- * WebOS behavior depends on the SDK version, WebOS 4 would pull up the launcher menu
203
- * following versions would show a native exit prompt
204
- */
205
- platformBack() {
206
- try {
207
- // TODO: temporary hack until this code is refactored
208
- if (Platform.OS === ("samsung_tv" as QuickBrickPlatforms)) {
209
- const { isDialogVisible } = confirmationDialogStore.getState();
210
-
211
- if (isDialogVisible) {
212
- this.onConfirmDialogClose();
213
- } else {
214
- showConfirmationDialog({
215
- title: "",
216
- message: alertConfirmationMesage,
217
- confirmCompletion: this.performExit,
218
- cancelCompletion: this.onConfirmDialogClose,
219
- });
220
- }
221
- } else if (Platform.OS === ("lg_tv" as QuickBrickPlatforms)) {
222
- this.handleWebOsBack();
218
+ /**
219
+ * This methods sends the app to background in whatever way is appropriate for each platform
220
+ * In the case of Tizen it shows a custom dialog on confirm it exits the app
221
+ * WebOS behavior depends on the SDK version, WebOS 4 would pull up the launcher menu
222
+ * following versions would show a native exit prompt
223
+ */
224
+ platformBack() {
225
+ try {
226
+ // TODO: temporary hack until this code is refactored
227
+ if (isSamsungPlatform() || isVizioPlatform()) {
228
+ const { isDialogVisible } = confirmationDialogStore.getState();
229
+
230
+ if (isDialogVisible) {
231
+ this.onConfirmDialogClose();
232
+ } else {
233
+ showConfirmationDialog({
234
+ title: "",
235
+ message: alertConfirmationMesage,
236
+ confirmCompletion: this.performExit,
237
+ cancelCompletion: this.onConfirmDialogClose,
238
+ });
223
239
  }
224
- } catch (e) {
225
- // eslint-disable-next-line no-console
226
- console.warn("Failed to execute platformBack", e);
240
+ } else if (isLgPlatform()) {
241
+ this.handleWebOsBack();
227
242
  }
243
+ } catch (e) {
244
+ // eslint-disable-next-line no-console
245
+ console.warn("Failed to execute platformBack", e);
228
246
  }
247
+ }
229
248
 
230
- cursorVisibilityChange(event) {
231
- const visible = event.detail.visibility;
232
- const { setDisplayState, displayState } = this.props;
249
+ cursorVisibilityChange(event) {
250
+ const visible = event.detail.visibility;
251
+ const { setDisplayState, displayState } = this.props;
233
252
 
234
- if (visible && displayState === DISPLAY_STATES.PLAYER) {
235
- setDisplayState(DISPLAY_STATES.HUD);
236
- this.resetHudTimeout();
237
- }
253
+ if (visible && displayState === DISPLAY_STATES.PLAYER) {
254
+ setDisplayState(DISPLAY_STATES.HUD);
255
+ this.resetHudTimeout();
238
256
  }
257
+ }
239
258
 
240
- onMouseMove() {
241
- const { setDisplayState, displayState } = this.props;
259
+ onMouseMove() {
260
+ const { setDisplayState, displayState } = this.props;
242
261
 
243
- if (displayState === DISPLAY_STATES.PLAYER) {
244
- setDisplayState(DISPLAY_STATES.HUD);
245
- this.resetHudTimeout();
246
- }
262
+ if (displayState === DISPLAY_STATES.PLAYER) {
263
+ setDisplayState(DISPLAY_STATES.HUD);
264
+ this.resetHudTimeout();
247
265
  }
266
+ }
248
267
 
249
- isHomeScreen() {
250
- const { navigator } = this.props;
251
- const { rivers } = this.props;
268
+ isHomeScreen() {
269
+ const { navigator } = this.props;
270
+ const { rivers } = this.props;
252
271
 
253
- const homeId = R.compose(
254
- R.prop("id"),
255
- R.find(R.prop("home")),
256
- R.values
257
- )(rivers);
272
+ const homeId = R.compose(
273
+ R.prop("id"),
274
+ R.find(R.prop("home")),
275
+ R.values
276
+ )(rivers);
258
277
 
259
- const homePath = `/river/${homeId}`;
278
+ const homePath = `/river/${homeId}`;
260
279
 
261
- return homePath === navigator.currentRoute;
262
- }
280
+ return homePath === navigator.currentRoute;
281
+ }
263
282
 
264
- goToHome() {
265
- const { navigator } = this.props;
266
- const { rivers } = this.props;
267
- const homeRiver = R.find(R.propEq("home", true), R.values(rivers));
283
+ goToHome() {
284
+ const { navigator } = this.props;
285
+ const { rivers } = this.props;
286
+ const homeRiver = R.find(R.propEq("home", true), R.values(rivers));
268
287
 
269
- navigator.replace(homeRiver);
270
- }
288
+ navigator.replace(homeRiver);
289
+ }
271
290
 
272
- resetHudTimeout() {
273
- const { resetHudTimer = noop } = this.props;
291
+ resetHudTimeout() {
292
+ const { resetHudTimer = noop } = this.props;
274
293
 
275
- resetHudTimer?.();
276
- }
294
+ resetHudTimer?.();
295
+ }
277
296
 
278
- clearTimeout() {
279
- if (this.timeout) {
280
- clearTimeout(this.timeout);
281
- this.timeout = null;
282
- }
297
+ clearTimeout() {
298
+ if (this.timeout) {
299
+ clearTimeout(this.timeout);
300
+ this.timeout = null;
283
301
  }
302
+ }
284
303
 
285
- clearLongPressTimeout() {
286
- if (this.longKeyPressTimeout) {
287
- clearTimeout(this.longKeyPressTimeout);
288
- this.longKeyPressTimeout = null;
289
- }
304
+ clearLongPressTimeout() {
305
+ if (this.longKeyPressTimeout) {
306
+ clearTimeout(this.longKeyPressTimeout);
307
+ this.longKeyPressTimeout = null;
290
308
  }
309
+ }
291
310
 
292
- /**
293
- * If the keySupportsLongPress this method is called on longpress of key
294
- * Register your longpress handlers here, and make sure to add it to list of supported keys
295
- */
296
- onLongPress(event) {
297
- this.longPressActive = true;
298
- // Legacy way of determining whether controls are being displayed
299
- // Or if the player is in a playback state.
300
- const { displayState } = this.props;
301
- const playerState = displayState === DISPLAY_STATES.PLAYER;
302
- const hudState = displayState === DISPLAY_STATES.HUD;
303
- const playbackState = playerState || hudState;
304
-
305
- switch (event.code) {
306
- case KEYS.Back.code:
307
- // If you longpress the back button we probably want to exit
308
- this.platformBack();
311
+ /**
312
+ * If the keySupportsLongPress this method is called on longpress of key
313
+ * Register your longpress handlers here, and make sure to add it to list of supported keys
314
+ */
315
+ onLongPress(event) {
316
+ this.longPressActive = true;
317
+ // Legacy way of determining whether controls are being displayed
318
+ // Or if the player is in a playback state.
319
+ const { displayState } = this.props;
320
+ const playerState = displayState === DISPLAY_STATES.PLAYER;
321
+ const hudState = displayState === DISPLAY_STATES.HUD;
322
+ const playbackState = playerState || hudState;
323
+
324
+ switch (event.code) {
325
+ case KEYS.Back.code:
326
+ // If you longpress the back button we probably want to exit
327
+ this.platformBack();
309
328
 
310
- break;
329
+ break;
311
330
 
312
- case KEYS.Enter.code:
313
- focusManager.longPress();
331
+ case KEYS.Enter.code:
332
+ focusManager.longPress();
314
333
 
315
- break;
334
+ break;
316
335
 
317
- case KEYS.Forward.code:
318
- playbackState && this.props.seekMethods.onForwardLongPress();
336
+ case KEYS.Forward.code:
337
+ playbackState && this.props.seekMethods.onForwardLongPress();
319
338
 
320
- break;
339
+ break;
321
340
 
322
- case KEYS.Rewind.code:
323
- playbackState && this.props.seekMethods.onRewindLongPress();
341
+ case KEYS.Rewind.code:
342
+ playbackState && this.props.seekMethods.onRewindLongPress();
324
343
 
325
- break;
344
+ break;
326
345
 
327
- default:
328
- break;
329
- }
346
+ default:
347
+ break;
330
348
  }
349
+ }
331
350
 
332
- /**
333
- * If the keySupportsLongPress this method is called on keyup of a longpress key
334
- * If you need to handle the release or keyup of a longpress key register the handler here
335
- */
336
- onLongPressRelease(event) {
337
- this.longPressActive = false;
338
- this.resetHudTimeout();
351
+ /**
352
+ * If the keySupportsLongPress this method is called on keyup of a longpress key
353
+ * If you need to handle the release or keyup of a longpress key register the handler here
354
+ */
355
+ onLongPressRelease(event) {
356
+ this.longPressActive = false;
357
+ this.resetHudTimeout();
339
358
 
340
- focusManager.pressOut();
359
+ focusManager.pressOut();
341
360
 
342
- switch (event.code) {
343
- case KEYS.Forward.code:
344
- this.props.seekMethods.onForwardPressOut();
361
+ switch (event.code) {
362
+ case KEYS.Forward.code:
363
+ this.props.seekMethods.onForwardPressOut();
345
364
 
346
- break;
365
+ break;
347
366
 
348
- case KEYS.Rewind.code:
349
- this.props.seekMethods.onForwardPressOut();
367
+ case KEYS.Rewind.code:
368
+ this.props.seekMethods.onForwardPressOut();
350
369
 
351
- break;
370
+ break;
352
371
 
353
- default:
354
- break;
355
- }
372
+ default:
373
+ break;
356
374
  }
375
+ }
357
376
 
358
- /**
359
- * Resets the sequence of keys we captured.
360
- * Clear ensures that we don't interpret regular interactions
361
- * as a sequence used to unlock certain features
362
- */
363
- resetSequenceTimeout() {
364
- const SEQUENCE_ENTRY_TIME = 1900;
365
-
366
- if (this.sequenceTimeout) {
367
- // Start timer over
368
- clearTimeout(this.sequenceTimeout);
369
- }
370
-
371
- this.sequenceTimeout = setTimeout(() => {
372
- // User needs to complete sequence before SEQUENCE_ENTRY_TIME
373
- // or we set userSequence, and prev sequenceTimeout to empty values
374
- this.sequenceTimeout = null;
375
- this.userSequence = [];
376
- }, SEQUENCE_ENTRY_TIME);
377
+ /**
378
+ * Resets the sequence of keys we captured.
379
+ * Clear ensures that we don't interpret regular interactions
380
+ * as a sequence used to unlock certain features
381
+ */
382
+ resetSequenceTimeout() {
383
+ const SEQUENCE_ENTRY_TIME = 1900;
384
+
385
+ if (this.sequenceTimeout) {
386
+ // Start timer over
387
+ clearTimeout(this.sequenceTimeout);
377
388
  }
378
389
 
379
- /**
380
- * If a certain sequence of keys are pressed we enable special menus or settings
381
- * Each supported sequence can be stored as a constant
382
- */
383
- checkSequence(key) {
384
- const SINK_SEQUENCE = [
385
- KEYS.ArrowUp.keyCode,
386
- KEYS.ArrowUp.keyCode,
387
- KEYS.ArrowDown.keyCode,
388
- KEYS.ArrowDown.keyCode,
389
- KEYS.ArrowLeft.keyCode,
390
- KEYS.ArrowRight.keyCode,
391
- KEYS.ArrowLeft.keyCode,
392
- KEYS.ArrowRight.keyCode,
393
- ];
394
-
395
- this.resetSequenceTimeout();
396
- this.userSequence.push(key);
397
-
398
- // Remember [1,2,3] !== [1,2,3] in js, so string comparison is better
399
- const shouldAddSink =
400
- this.userSequence.toString() === SINK_SEQUENCE.toString();
401
-
402
- if (shouldAddSink) {
403
- this.props.xray.addRemoteSink({});
404
- }
390
+ this.sequenceTimeout = setTimeout(() => {
391
+ // User needs to complete sequence before SEQUENCE_ENTRY_TIME
392
+ // or we set userSequence, and prev sequenceTimeout to empty values
393
+ this.sequenceTimeout = null;
394
+ this.userSequence = [];
395
+ }, SEQUENCE_ENTRY_TIME);
396
+ }
397
+
398
+ /**
399
+ * If a certain sequence of keys are pressed we enable special menus or settings
400
+ * Each supported sequence can be stored as a constant
401
+ */
402
+ checkSequence(key) {
403
+ const SINK_SEQUENCE = [
404
+ KEYS.ArrowUp.keyCode,
405
+ KEYS.ArrowUp.keyCode,
406
+ KEYS.ArrowDown.keyCode,
407
+ KEYS.ArrowDown.keyCode,
408
+ KEYS.ArrowLeft.keyCode,
409
+ KEYS.ArrowRight.keyCode,
410
+ KEYS.ArrowLeft.keyCode,
411
+ KEYS.ArrowRight.keyCode,
412
+ ];
413
+
414
+ this.resetSequenceTimeout();
415
+ this.userSequence.push(key);
416
+
417
+ // Remember [1,2,3] !== [1,2,3] in js, so string comparison is better
418
+ const shouldAddSink =
419
+ this.userSequence.toString() === SINK_SEQUENCE.toString();
420
+
421
+ if (shouldAddSink) {
422
+ this.props.xray.addRemoteSink({});
405
423
  }
424
+ }
406
425
 
407
- /**
408
- * Determines whether the key supports or handles longpress
409
- * At the moment the only keys that support are the ones defined in
410
- * supportedEvents array. This logic just prevents us from registering timeouts for every key
411
- */
412
- keySupportsLongPress(event): boolean {
413
- const supportedEvents = [
414
- KEYS.Back,
415
- KEYS.Enter,
416
- KEYS.Rewind,
417
- KEYS.Forward,
418
- ];
426
+ /**
427
+ * Determines whether the key supports or handles longpress
428
+ * At the moment the only keys that support are the ones defined in
429
+ * supportedEvents array. This logic just prevents us from registering timeouts for every key
430
+ */
431
+ keySupportsLongPress(event): boolean {
432
+ const supportedEvents = [KEYS.Back, KEYS.Enter, KEYS.Rewind, KEYS.Forward];
419
433
 
420
- const eventMatcher = R.compose(R.prop("matches"), keyCode)(event);
434
+ const eventMatcher = R.compose(R.prop("matches"), keyCode)(event);
421
435
 
422
- const supportsLongPress = R.compose(
423
- R.any(R.equals(true)),
424
- R.map(eventMatcher)
425
- )(supportedEvents);
436
+ const supportsLongPress = R.compose(
437
+ R.any(R.equals(true)),
438
+ R.map(eventMatcher)
439
+ )(supportedEvents);
426
440
 
427
- return supportsLongPress;
428
- }
441
+ return supportsLongPress;
442
+ }
429
443
 
430
- onKeyUp(event: KeyboardEvent) {
431
- // If this is a supported longpress key we need to clear the timeout and handle release of key
432
- // This logic is handled by onLongPressRelease
433
- if (this.keySupportsLongPress(event)) {
434
- if (this.longPressActive) {
435
- this.onLongPressRelease(event);
436
- } else {
437
- this.clearLongPressTimeout();
438
- }
444
+ onKeyUp(event: KeyboardEvent) {
445
+ // If this is a supported longpress key we need to clear the timeout and handle release of key
446
+ // This logic is handled by onLongPressRelease
447
+ if (this.keySupportsLongPress(event)) {
448
+ if (this.longPressActive) {
449
+ this.onLongPressRelease(event);
450
+ } else {
451
+ this.clearLongPressTimeout();
439
452
  }
440
453
  }
454
+ }
441
455
 
442
- onKeyDown(event) {
443
- const { displayState, setDisplayState, navigator } = this.props;
456
+ onKeyDown(event) {
457
+ const { displayState, setDisplayState, navigator } = this.props;
458
+ const { isKeyboardVisible } = this.state;
444
459
 
445
- const { isDialogVisible } = confirmationDialogStore.getState();
460
+ const { isDialogVisible } = confirmationDialogStore.getState();
446
461
 
447
- // If this is a supported longpress key we need to set the timeout and handle the longpress
448
- if (this.keySupportsLongPress(event)) {
449
- this.longKeyPressTimeout = setTimeout(() => {
450
- this.onLongPress(event);
451
- }, LONG_KEY_PRESS_TIMEOUT);
462
+ // If this is a supported longpress key we need to set the timeout and handle the longpress
463
+ if (this.keySupportsLongPress(event)) {
464
+ this.longKeyPressTimeout = setTimeout(() => {
465
+ this.onLongPress(event);
466
+ }, LONG_KEY_PRESS_TIMEOUT);
467
+ }
468
+
469
+ if (isKeyboardVisible && shouldUseOnScreenKeyboard()) {
470
+ if (keyCode(event).matchesAny("Escape", "Back")) {
471
+ event.preventDefault();
472
+ event.stopPropagation();
473
+ this.handleKeyboardDismiss();
474
+
475
+ return;
452
476
  }
477
+ }
453
478
 
479
+ /**
480
+ When the player is displayed, we listen to Native Play / Pause / Stop
481
+ buttons. Any other key press will display the transport controls
482
+ */
483
+ if (
484
+ displayState === DISPLAY_STATES.PLAYER ||
485
+ displayState === DISPLAY_STATES.HUD
486
+ ) {
454
487
  /**
455
- When the player is displayed, we listen to Native Play / Pause / Stop
456
- buttons. Any other key press will display the transport controls
488
+ registering native keys for playback
457
489
  */
458
- if (
459
- displayState === DISPLAY_STATES.PLAYER ||
460
- displayState === DISPLAY_STATES.HUD
461
- ) {
462
- /**
463
- registering native keys for playback
464
- */
465
- if (keyCode(event).matches(KEYS.TogglePlayPause)) {
466
- const { playing } = playerManager.getState() || {};
467
-
468
- if (!playing) {
469
- return playerManager.togglePlayPause();
470
- }
471
- }
490
+ if (keyCode(event).matches(KEYS.TogglePlayPause)) {
491
+ const { playing } = playerManager.getState() || {};
472
492
 
473
- if (keyCode(event).matches(KEYS.Play)) {
474
- return playerManager.play();
493
+ if (!playing) {
494
+ return playerManager.togglePlayPause();
475
495
  }
496
+ }
476
497
 
477
- if (keyCode(event).matches(KEYS.Pause)) {
478
- setDisplayState(DISPLAY_STATES.HUD);
498
+ if (keyCode(event).matches(KEYS.Play)) {
499
+ return playerManager.play();
500
+ }
479
501
 
480
- return playerManager.pause();
481
- }
502
+ if (keyCode(event).matches(KEYS.Pause)) {
503
+ setDisplayState(DISPLAY_STATES.HUD);
482
504
 
483
- if (keyCode(event).matches(KEYS.Stop)) {
484
- setDisplayState(DISPLAY_STATES.DEFAULT);
485
- navigator.goBack();
505
+ return playerManager.pause();
506
+ }
486
507
 
487
- return;
488
- }
508
+ if (keyCode(event).matches(KEYS.Stop)) {
509
+ setDisplayState(DISPLAY_STATES.DEFAULT);
510
+ navigator.goBack();
489
511
 
490
- if (keyCode(event).matches(KEYS.Forward)) {
491
- setDisplayState(DISPLAY_STATES.HUD);
492
- this.props.seekMethods.onForwardPress();
512
+ return;
513
+ }
493
514
 
494
- return;
495
- }
515
+ if (keyCode(event).matches(KEYS.Forward)) {
516
+ setDisplayState(DISPLAY_STATES.HUD);
517
+ this.props.seekMethods.onForwardPress();
496
518
 
497
- if (keyCode(event).matches(KEYS.Rewind)) {
498
- setDisplayState(DISPLAY_STATES.HUD);
499
- this.props.seekMethods.onRewindPress();
519
+ return;
520
+ }
500
521
 
501
- return;
502
- }
522
+ if (keyCode(event).matches(KEYS.Rewind)) {
523
+ setDisplayState(DISPLAY_STATES.HUD);
524
+ this.props.seekMethods.onRewindPress();
525
+
526
+ return;
503
527
  }
528
+ }
504
529
 
505
- /**
506
- When pressing backspace or the Native "Back" button:
507
- if on the transport controls: we show the player
508
- if on the player : we show the UI
509
- if on the UI : we exit the app
510
- */
530
+ /**
531
+ When pressing backspace or the Native "Back" button:
532
+ if on the transport controls: we show the player
533
+ if on the player : we show the UI
534
+ if on the UI : we exit the app
535
+ */
511
536
 
512
- if (
513
- keyCode(event).matchesAny(
514
- KEYS.Back,
515
- KEYS.Escape,
516
- KEYS.EscapeDevice,
517
- KEYS.Backspace
518
- )
519
- ) {
520
- switch (displayState) {
521
- case DISPLAY_STATES.DEFAULT:
522
- case DISPLAY_STATES.PLAYER:
523
- if (isDialogVisible) {
524
- this.onConfirmDialogClose();
525
- } else if (navigator.canGoBack()) {
526
- navigator.goBack();
527
- } else if (this.isHomeScreen()) {
537
+ if (
538
+ keyCode(event).matchesAny(
539
+ KEYS.Back,
540
+ KEYS.Escape,
541
+ KEYS.EscapeDevice,
542
+ KEYS.Backspace
543
+ )
544
+ ) {
545
+ switch (displayState) {
546
+ case DISPLAY_STATES.DEFAULT:
547
+ case DISPLAY_STATES.PLAYER:
548
+ if (isDialogVisible) {
549
+ this.onConfirmDialogClose();
550
+ } else if (navigator.canGoBack()) {
551
+ navigator.goBack();
552
+ } else if (this.isHomeScreen()) {
553
+ this.platformBack();
554
+ } else if (navigator.currentRoute.includes("hook")) {
555
+ // If code reaches this block navigator cant go back, it is not home,
556
+ // and this is a login plugin that was pushed on top of the home
557
+ // Treat it like a home screen and run platformBack()
558
+ const entryData = navigator?.data?.entry;
559
+ const isHookInHomescreen = entryData?.payload?.home;
560
+
561
+ const isHookFlowBlocker =
562
+ entryData?.hookPlugin?.module?.isFlowBlocker();
563
+
564
+ if (isHookInHomescreen && isHookFlowBlocker) {
528
565
  this.platformBack();
529
- } else if (navigator.currentRoute.includes("hook")) {
530
- // If code reaches this block navigator cant go back, it is not home,
531
- // and this is a login plugin that was pushed on top of the home
532
- // Treat it like a home screen and run platformBack()
533
- const entryData = navigator?.data?.entry;
534
- const isHookInHomescreen = entryData?.payload?.home;
535
-
536
- const isHookFlowBlocker =
537
- entryData?.hookPlugin?.module?.isFlowBlocker();
538
-
539
- if (isHookInHomescreen && isHookFlowBlocker) {
540
- this.platformBack();
541
- } else {
542
- navigator.goBack(false, true);
543
- }
544
566
  } else {
545
- this.goToHome();
567
+ navigator.goBack(false, true);
546
568
  }
569
+ } else {
570
+ this.goToHome();
571
+ }
547
572
 
548
- break;
549
- case DISPLAY_STATES.HUD:
550
- // IF transport controls are open, close them instead of closing player
551
- setDisplayState(DISPLAY_STATES.PLAYER);
552
- break;
553
- default:
554
- setDisplayState(DISPLAY_STATES.PLAYER);
555
- break;
556
- }
573
+ break;
574
+ case DISPLAY_STATES.HUD:
575
+ // IF transport controls are open, close them instead of closing player
576
+ setDisplayState(DISPLAY_STATES.PLAYER);
577
+ break;
578
+ default:
579
+ setDisplayState(DISPLAY_STATES.PLAYER);
580
+ break;
557
581
  }
582
+ }
558
583
 
559
- if (keyCode(event).matches(KEYS.Enter)) {
560
- if (displayState === DISPLAY_STATES.PLAYER) {
561
- setDisplayState(DISPLAY_STATES.HUD);
562
- } else {
563
- focusManager.press();
564
- }
565
-
566
- return true;
584
+ if (keyCode(event).matches(KEYS.Enter)) {
585
+ if (displayState === DISPLAY_STATES.PLAYER) {
586
+ setDisplayState(DISPLAY_STATES.HUD);
587
+ } else {
588
+ focusManager.press();
567
589
  }
568
590
 
569
- if (keyCode(event).matches(KEYS.Exit)) {
570
- if (Platform.OS === ("samsung_tv" as QuickBrickPlatforms)) {
571
- this.performExit();
591
+ return true;
592
+ }
572
593
 
573
- return;
574
- }
594
+ if (keyCode(event).matches(KEYS.Exit)) {
595
+ if (isSamsungPlatform() || shouldUseOnScreenKeyboard()) {
596
+ this.performExit();
575
597
 
576
- this.platformBack();
598
+ return;
577
599
  }
578
600
 
579
- if (
580
- keyCode(event).matchesAny(...ARROW_KEYS) &&
581
- !this.state.isKeyboardVisible
582
- ) {
583
- if (displayState === DISPLAY_STATES.PLAYER) {
584
- setDisplayState(DISPLAY_STATES.HUD);
585
- }
586
-
587
- this.checkSequence(event.keyCode);
588
-
589
- const direction = keyCode(event).direction();
590
-
591
- if (!isDialogVisible || !["up", "down"].includes(direction.value)) {
592
- focusManager.moveFocus(direction);
593
- }
594
-
595
- if (
596
- displayState === DISPLAY_STATES.PLAYER ||
597
- displayState === DISPLAY_STATES.HUD
598
- ) {
599
- this.resetHudTimeout();
600
- }
601
+ this.platformBack();
602
+ }
601
603
 
602
- return true;
604
+ if (
605
+ keyCode(event).matchesAny(...ARROW_KEYS) &&
606
+ !this.state.isKeyboardVisible
607
+ ) {
608
+ if (displayState === DISPLAY_STATES.PLAYER) {
609
+ setDisplayState(DISPLAY_STATES.HUD);
603
610
  }
604
- }
605
611
 
606
- onScroll(event) {
607
- const { displayState } = this.props;
612
+ this.checkSequence(event.keyCode);
608
613
 
609
- const deltaY = event?.deltaY;
614
+ const direction = keyCode(event).direction();
610
615
 
611
- const DIRECTION = {
612
- axis: "y",
613
- isHorizontal: false,
614
- isVertical: true,
615
- value: "",
616
- };
616
+ if (!isDialogVisible || !["up", "down"].includes(direction.value)) {
617
+ focusManager.moveFocus(direction);
618
+ }
617
619
 
618
- if (deltaY > 0) {
619
- DIRECTION.value =
620
- Platform.OS === ("lg_tv" as QuickBrickPlatforms) ? "down" : "up";
621
- } else if (deltaY < 0) {
622
- DIRECTION.value =
623
- Platform.OS === ("lg_tv" as QuickBrickPlatforms) ? "up" : "down";
620
+ if (!isDialogVisible) {
621
+ // run focus recovery on user interaction attempt
622
+ // probably we failed to set initial focus for some reason
623
+ focusManager.recoverFocus();
624
624
  }
625
625
 
626
626
  if (
@@ -628,46 +628,152 @@ export function withInteractionManager(Component: React.FC) {
628
628
  displayState === DISPLAY_STATES.HUD
629
629
  ) {
630
630
  this.resetHudTimeout();
631
- } else {
632
- focusManager.moveFocus(DIRECTION);
633
631
  }
632
+
633
+ return true;
634
634
  }
635
+ }
635
636
 
636
- onMouseDown(event) {
637
- const { button } = event;
638
- const { displayState } = this.props;
637
+ onScroll(event) {
638
+ const { displayState } = this.props;
639
639
 
640
- if (
641
- button === 1 && // number 1 represents press on the scroll wheel
642
- (displayState === DISPLAY_STATES.PLAYER ||
643
- displayState === DISPLAY_STATES.HUD)
644
- ) {
645
- this.resetHudTimeout();
646
- }
640
+ const deltaY = event?.deltaY;
641
+
642
+ const DIRECTION = {
643
+ axis: "y",
644
+ isHorizontal: false,
645
+ isVertical: true,
646
+ value: "",
647
+ };
648
+
649
+ if (deltaY > 0) {
650
+ DIRECTION.value = isLgPlatform() ? "down" : "up";
651
+ } else if (deltaY < 0) {
652
+ DIRECTION.value = isLgPlatform() ? "up" : "down";
647
653
  }
648
654
 
649
- onKeyboardStateChange(event) {
650
- this.setState({ isKeyboardVisible: event.visibility });
655
+ if (
656
+ displayState === DISPLAY_STATES.PLAYER ||
657
+ displayState === DISPLAY_STATES.HUD
658
+ ) {
659
+ this.resetHudTimeout();
660
+ } else {
661
+ focusManager.moveFocus(DIRECTION);
651
662
  }
663
+ }
652
664
 
653
- onConfirmDialogOpen() {
654
- this.confirmDialog.showDialog();
665
+ onMouseDown(event) {
666
+ const { button } = event;
667
+ const { displayState } = this.props;
668
+
669
+ if (
670
+ button === 1 && // number 1 represents press on the scroll wheel
671
+ (displayState === DISPLAY_STATES.PLAYER ||
672
+ displayState === DISPLAY_STATES.HUD)
673
+ ) {
674
+ this.resetHudTimeout();
655
675
  }
676
+ }
656
677
 
657
- onConfirmDialogClose() {
658
- this.confirmDialog.hideDialog();
678
+ onKeyboardStateChange(event) {
679
+ this.setState({ isKeyboardVisible: event.visibility });
680
+ }
681
+
682
+ onConfirmDialogOpen() {
683
+ this.confirmDialog.showDialog();
684
+ }
685
+
686
+ onConfirmDialogClose() {
687
+ this.confirmDialog.hideDialog();
688
+
689
+ // restore initial focus after closing dialog
690
+ focusManager.setInitialFocus();
691
+ }
692
+
693
+ handleInputFocus = (event: FocusEvent) => {
694
+ if (!shouldUseOnScreenKeyboard()) return;
695
+ const target = event.target as HTMLElement;
696
+
697
+ if (this.isDismissing) {
698
+ return;
659
699
  }
660
700
 
661
- render() {
662
- return <Component {...this.props} />;
701
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
702
+ this.setState({
703
+ isKeyboardVisible: true,
704
+ keyboardInput: (target as HTMLInputElement).value || "",
705
+ });
663
706
  }
707
+ };
708
+
709
+ handleInputBlur(event: FocusEvent) {
710
+ if (!shouldUseOnScreenKeyboard()) return;
711
+
712
+ const target = event.target as HTMLElement;
713
+
714
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
715
+ const relatedTarget = event.relatedTarget as HTMLElement;
716
+
717
+ if (
718
+ !relatedTarget ||
719
+ (relatedTarget.tagName !== "INPUT" &&
720
+ relatedTarget.tagName !== "TEXTAREA")
721
+ ) {
722
+ this.setState({ isKeyboardVisible: false });
723
+ }
724
+ }
725
+ }
726
+
727
+ handleKeyboardDismiss = () => {
728
+ this.isDismissing = true;
729
+
730
+ this.setState(
731
+ {
732
+ isKeyboardVisible: false,
733
+ keyboardInput: "",
734
+ },
735
+ () => {
736
+ this.isDismissing = false;
737
+ }
738
+ );
739
+ };
740
+
741
+ handleKeyboardInput(value: string) {
742
+ this.setState({ keyboardInput: value });
664
743
  }
665
744
 
666
- return R.compose(
667
- withNavigator,
668
- withUseSeek,
669
- withXray,
670
- PlayerContentContext.withConsumer,
671
- DisplayStateContext.withConsumer
672
- )(InteractionManager);
745
+ render() {
746
+ return (
747
+ (shouldUseOnScreenKeyboard() && this.state.isKeyboardVisible && (
748
+ <OnScreenKeyboard
749
+ initialValue={this.state.keyboardInput}
750
+ onInput={this.handleKeyboardInput}
751
+ onDismiss={this.handleKeyboardDismiss}
752
+ />
753
+ )) ||
754
+ null
755
+ );
756
+ }
757
+ }
758
+
759
+ const IM = R.compose(
760
+ withNavigator,
761
+ withUseSeek,
762
+ withXray,
763
+ PlayerContentContext.withConsumer,
764
+ DisplayStateContext.withConsumer
765
+ )(InteractionManager);
766
+
767
+ export function withInteractionManager(Component: React.FC) {
768
+ // Wrapper was created to avoid re-renders of the Component when InteractionManager context changes
769
+ const InteractionManagerWrapper = (props: Props) => {
770
+ return (
771
+ <>
772
+ <Component {...props} />
773
+ <IM {...props} />
774
+ </>
775
+ );
776
+ };
777
+
778
+ return InteractionManagerWrapper;
673
779
  }
@@ -62,6 +62,9 @@ export const PLATFORMS = {
62
62
  iphone: "iPhone",
63
63
  android: "Android",
64
64
  mobile: "Mobile",
65
+ vizio: "VIZIO",
66
+ smartcast: "SmartCast",
67
+ conjure: "Conjure",
65
68
  };
66
69
 
67
70
  /**
@@ -42,6 +42,36 @@ export const isSamsungPlatform = () => {
42
42
  );
43
43
  };
44
44
 
45
+ /**
46
+ * Checks if we are on the Vizio platform by using the userAgent string
47
+ *
48
+ * Use this method when you need to identify the platform on runtime
49
+ * before the VIZIO Companion Library has loaded
50
+ */
51
+ export const isVizioPlatform = () => {
52
+ const userAgent = getUserAgent();
53
+
54
+ const isVizioAgent =
55
+ userAgent.includes(PLATFORMS.vizio) ||
56
+ userAgent.includes(PLATFORMS.smartcast) ||
57
+ userAgent.includes(PLATFORMS.conjure);
58
+
59
+ return isVizioAgent;
60
+ };
61
+
62
+ /**
63
+ * Checks if we are on the Vizio platform by checking for the global VIZIO object
64
+ * and the VIZIO Companion Library. This method does not use the userAgent string,
65
+ * because it mainly checks whether we have access to apis, which may not be available yet
66
+ */
67
+ export const hasVizioAPIs = () => {
68
+ const hasAPIs =
69
+ typeof window.VIZIO !== "undefined" &&
70
+ window?.applicaster?.vizioLibraryDidLoad;
71
+
72
+ return hasAPIs;
73
+ };
74
+
45
75
  /**
46
76
  * platformSelect can't be used, because Platform.OS is overriden
47
77
  * by overloadReactNativePlatform and don't support 'web' value
@@ -253,6 +283,24 @@ export const getDeviceData = async () => {
253
283
  ...tizenConnectionInfo,
254
284
  device_type: getDeviceType(),
255
285
  };
286
+ } else if (isVizioPlatform()) {
287
+ deviceData = {
288
+ ...deviceData,
289
+ platform: "vizio",
290
+ deviceMake: "Vizio",
291
+ deviceType: "tv",
292
+ userAgent: getUserAgent(),
293
+ deviceWidth: window.innerWidth,
294
+ deviceHeight: window.innerHeight,
295
+ };
296
+
297
+ if (hasVizioAPIs()) {
298
+ window.VIZIO.getFirmwareVersion(function (firmwareVersion) {
299
+ deviceData.osVersion = firmwareVersion;
300
+ });
301
+
302
+ deviceData.deviceModel = window.VIZIO.deviceModel;
303
+ }
256
304
  }
257
305
 
258
306
  return deviceData;
package/main.css CHANGED
@@ -10,8 +10,8 @@ body {
10
10
  }
11
11
 
12
12
  div#zapp_app_container {
13
- height: 100%;
14
- width: 100%;
13
+ height: 1080px;
14
+ width: 1920px;
15
15
  display: flex;
16
16
  flex: 1;
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-dom-app",
3
- "version": "12.0.0-alpha.7855034103",
3
+ "version": "12.0.0-alpha.7990242848",
4
4
  "description": "Zapp App Component for Applicaster's Quick Brick React Native App",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "homepage": "https://github.com/applicaster/zapp-react-dom-app#readme",
24
24
  "dependencies": {
25
- "@applicaster/zapp-react-dom-ui-components": "12.0.0-alpha.7855034103",
26
- "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.7855034103",
27
- "@applicaster/zapp-react-native-redux": "12.0.0-alpha.7855034103",
28
- "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.7855034103",
29
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.7855034103",
25
+ "@applicaster/zapp-react-dom-ui-components": "12.0.0-alpha.7990242848",
26
+ "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.7990242848",
27
+ "@applicaster/zapp-react-native-redux": "12.0.0-alpha.7990242848",
28
+ "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.7990242848",
29
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.7990242848",
30
30
  "abortcontroller-polyfill": "^1.7.5",
31
31
  "history": "^4.7.2",
32
32
  "react-router-dom": "^5.1.2",