@applicaster/zapp-react-dom-app 12.0.0-alpha.4963544520 → 12.0.0-alpha.4987513251

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