@applicaster/zapp-react-dom-app 12.0.0-alpha.5895113034 → 12.0.0-alpha.5933014273

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