@applicaster/zapp-react-dom-app 12.0.0-alpha.4960304100 → 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,14 +33,22 @@ 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
- import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
36
36
  import { getXray } from "@applicaster/zapp-react-native-utils/logger";
37
37
  import { confirmationDialogStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ConfirmDialogState";
38
+ import {
39
+ isLgPlatform,
40
+ isSamsungPlatform,
41
+ isVizioPlatform,
42
+ } from "@applicaster/zapp-react-native-utils/reactUtils";
43
+
44
+ import { OnScreenKeyboard } from "@applicaster/zapp-react-dom-ui-components/Components/OnScreenKeyboard";
38
45
 
39
46
  const { withXray } = getXray();
40
47
  const globalAny: any = global;
41
48
 
49
+ const shouldUseOnScreenKeyboard = () =>
50
+ isVizioPlatform() || window.applicaster.useOnScreenKeyboard;
51
+
42
52
  type Props = {
43
53
  displayState: string;
44
54
  setDisplayState: (state: string) => void;
@@ -56,571 +66,559 @@ type Props = {
56
66
  };
57
67
  };
58
68
 
59
- type State = { isKeyboardVisible: boolean };
69
+ interface State {
70
+ isKeyboardVisible: boolean;
71
+ keyboardInput: string;
72
+ activeInputRef: HTMLInputElement | null;
73
+ }
60
74
 
61
75
  type KeyCode = { code: string; keyCode: number | string };
62
76
 
63
77
  export const alertConfirmationMesage =
64
78
  "Are you sure you want to exit application?";
65
79
 
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;
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
+ };
104
+
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
+ }
79
143
 
80
- constructor(props) {
81
- super(props);
144
+ componentDidMount() {
145
+ window.addEventListener("keydown", this.onKeyDownListener);
146
+ window.addEventListener("keyup", this.onKeyUp);
147
+ window.addEventListener("mousemove", this.onMouseMoveListener);
82
148
 
83
- this.onKeyDownListener = this.onKeyDown.bind(this);
149
+ document.addEventListener("wheel", this.onScrollListener, {
150
+ passive: true,
151
+ });
84
152
 
85
- this.state = {
86
- isKeyboardVisible: false,
87
- };
153
+ document.addEventListener("mousedown", this.onMouseDownListener);
88
154
 
89
- this.onMouseMoveListener = debounce({
90
- fn: this.onMouseMove,
91
- context: this,
92
- });
155
+ document.addEventListener(
156
+ "keyboardStateChange",
157
+ this.onKeyboardStateChange,
158
+ false
159
+ );
93
160
 
94
- this.onScrollListener = debounce({
95
- fn: this.onScroll,
96
- wait: 100,
97
- context: this,
98
- });
161
+ document.addEventListener("focusin", this.handleInputFocus);
99
162
 
100
- this.onMouseDownListener = debounce({
101
- fn: this.onMouseDown,
102
- context: this,
103
- });
163
+ document.addEventListener(
164
+ "cursorStateChange",
165
+ this.cursorVisibilityChange,
166
+ false
167
+ );
168
+ }
104
169
 
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
- }
170
+ componentWillUnmount() {
171
+ window.removeEventListener("keydown", this.onKeyDownListener);
172
+ window.removeEventListener("keyup", this.onKeyUp);
173
+ document.removeEventListener("wheel", this.onScrollListener);
123
174
 
124
- componentDidMount() {
125
- window.addEventListener("keydown", this.onKeyDownListener);
126
- window.addEventListener("keyup", this.onKeyUp);
127
- window.addEventListener("mousemove", this.onMouseMoveListener);
175
+ document.removeEventListener(
176
+ "cursorStateChange",
177
+ this.cursorVisibilityChange
178
+ );
128
179
 
129
- document.addEventListener("wheel", this.onScrollListener, {
130
- passive: true,
131
- });
180
+ document.removeEventListener("focusin", this.handleInputFocus);
132
181
 
133
- document.addEventListener("mousedown", this.onMouseDownListener);
182
+ document.removeEventListener("mousedown", this.onMouseDownListener);
134
183
 
135
- document.addEventListener(
136
- "keyboardStateChange",
137
- this.onKeyboardStateChange,
138
- false
139
- );
140
-
141
- document.addEventListener(
142
- "cursorStateChange",
143
- this.cursorVisibilityChange,
144
- false
145
- );
184
+ if (this.timeout) {
185
+ this.clearTimeout();
146
186
  }
147
187
 
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);
188
+ this.isDismissing = false;
189
+ }
164
190
 
165
- if (this.timeout) {
166
- this.clearTimeout();
167
- }
191
+ performExit() {
192
+ if (isSamsungPlatform()) {
193
+ this.onConfirmDialogClose();
194
+ sendQuickBrickEvent(QUICK_BRICK_EVENTS.MOVE_APP_TO_BACKGROUND);
168
195
  }
169
196
 
170
- performExit() {
171
- if (Platform.OS === ("samsung_tv" as QuickBrickPlatforms)) {
172
- this.onConfirmDialogClose();
173
- sendQuickBrickEvent(QUICK_BRICK_EVENTS.MOVE_APP_TO_BACKGROUND);
174
- }
197
+ if (isLgPlatform()) {
198
+ // webOS recommend to use this method when implemention a custom exit
199
+ globalAny.close();
200
+ }
175
201
 
176
- if (Platform.OS === ("lg_tv" as QuickBrickPlatforms)) {
177
- // webOS recommend to use this method when implemention a custom exit
178
- globalAny.close();
179
- }
202
+ if (isVizioPlatform()) {
203
+ window?.VIZIO?.exitApplication();
180
204
  }
205
+ }
181
206
 
182
- handleWebOsBack() {
183
- const systemBack = globalAny?.webOS?.platformBack;
207
+ handleWebOsBack() {
208
+ const systemBack = globalAny?.webOS?.platformBack;
184
209
 
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
- }
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
+ });
197
221
  }
222
+ }
198
223
 
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();
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
+ });
223
245
  }
224
- } catch (e) {
225
- // eslint-disable-next-line no-console
226
- console.warn("Failed to execute platformBack", e);
246
+ } else if (isLgPlatform()) {
247
+ this.handleWebOsBack();
227
248
  }
249
+ } catch (e) {
250
+ // eslint-disable-next-line no-console
251
+ console.warn("Failed to execute platformBack", e);
228
252
  }
253
+ }
229
254
 
230
- cursorVisibilityChange(event) {
231
- const visible = event.detail.visibility;
232
- const { setDisplayState, displayState } = this.props;
255
+ cursorVisibilityChange(event) {
256
+ const visible = event.detail.visibility;
257
+ const { setDisplayState, displayState } = this.props;
233
258
 
234
- if (visible && displayState === DISPLAY_STATES.PLAYER) {
235
- setDisplayState(DISPLAY_STATES.HUD);
236
- this.resetHudTimeout();
237
- }
259
+ if (visible && displayState === DISPLAY_STATES.PLAYER) {
260
+ setDisplayState(DISPLAY_STATES.HUD);
261
+ this.resetHudTimeout();
238
262
  }
263
+ }
239
264
 
240
- onMouseMove() {
241
- const { setDisplayState, displayState } = this.props;
265
+ onMouseMove() {
266
+ const { setDisplayState, displayState } = this.props;
242
267
 
243
- if (displayState === DISPLAY_STATES.PLAYER) {
244
- setDisplayState(DISPLAY_STATES.HUD);
245
- this.resetHudTimeout();
246
- }
268
+ if (displayState === DISPLAY_STATES.PLAYER) {
269
+ setDisplayState(DISPLAY_STATES.HUD);
270
+ this.resetHudTimeout();
247
271
  }
272
+ }
248
273
 
249
- isHomeScreen() {
250
- const { navigator } = this.props;
251
- const { rivers } = this.props;
252
-
253
- const homeId = R.compose(
254
- R.prop("id"),
255
- R.find(R.prop("home")),
256
- R.values
257
- )(rivers);
274
+ isHomeScreen() {
275
+ const { navigator } = this.props;
276
+ const { rivers } = this.props;
258
277
 
259
- const homePath = `/river/${homeId}`;
278
+ const homeId = R.compose(
279
+ R.prop("id"),
280
+ R.find(R.prop("home")),
281
+ R.values
282
+ )(rivers);
260
283
 
261
- return homePath === navigator.currentRoute;
262
- }
284
+ const homePath = `/river/${homeId}`;
263
285
 
264
- goToHome() {
265
- const { navigator } = this.props;
266
- const { rivers } = this.props;
267
- const homeRiver = R.find(R.propEq("home", true), R.values(rivers));
286
+ return homePath === navigator.currentRoute;
287
+ }
268
288
 
269
- navigator.replace(homeRiver);
270
- }
289
+ goToHome() {
290
+ const { navigator } = this.props;
291
+ const { rivers } = this.props;
292
+ const homeRiver = R.find(R.propEq("home", true), R.values(rivers));
271
293
 
272
- resetHudTimeout() {
273
- const { resetHudTimer = noop } = this.props;
294
+ navigator.replace(homeRiver);
295
+ }
274
296
 
275
- resetHudTimer?.();
276
- }
297
+ resetHudTimeout() {
298
+ const { resetHudTimer = noop } = this.props;
277
299
 
278
- clearTimeout() {
279
- if (this.timeout) {
280
- clearTimeout(this.timeout);
281
- this.timeout = null;
282
- }
283
- }
300
+ resetHudTimer?.();
301
+ }
284
302
 
285
- clearLongPressTimeout() {
286
- if (this.longKeyPressTimeout) {
287
- clearTimeout(this.longKeyPressTimeout);
288
- this.longKeyPressTimeout = null;
289
- }
303
+ clearTimeout() {
304
+ if (this.timeout) {
305
+ clearTimeout(this.timeout);
306
+ this.timeout = null;
290
307
  }
308
+ }
291
309
 
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();
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();
309
327
 
310
- break;
328
+ break;
311
329
 
312
- case KEYS.Enter.code:
313
- focusManager.longPress();
330
+ case KEYS.Enter.code:
331
+ focusManager.longPress();
314
332
 
315
- break;
333
+ break;
316
334
 
317
- case KEYS.Forward.code:
318
- playbackState && this.props.seekMethods.onForwardLongPress();
335
+ case KEYS.Forward.code:
336
+ playbackState && this.props.seekMethods.onForwardLongPress();
319
337
 
320
- break;
338
+ break;
321
339
 
322
- case KEYS.Rewind.code:
323
- playbackState && this.props.seekMethods.onRewindLongPress();
340
+ case KEYS.Rewind.code:
341
+ playbackState && this.props.seekMethods.onRewindLongPress();
324
342
 
325
- break;
343
+ break;
326
344
 
327
- default:
328
- break;
329
- }
345
+ default:
346
+ break;
330
347
  }
348
+ }
331
349
 
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();
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();
339
357
 
340
- focusManager.pressOut();
358
+ focusManager.pressOut();
341
359
 
342
- switch (event.code) {
343
- case KEYS.Forward.code:
344
- this.props.seekMethods.onForwardPressOut();
360
+ switch (event.code) {
361
+ case KEYS.Forward.code:
362
+ this.props.seekMethods.onForwardPressOut();
345
363
 
346
- break;
364
+ break;
347
365
 
348
- case KEYS.Rewind.code:
349
- this.props.seekMethods.onForwardPressOut();
366
+ case KEYS.Rewind.code:
367
+ this.props.seekMethods.onRewindPressOut();
350
368
 
351
- break;
369
+ break;
352
370
 
353
- default:
354
- break;
355
- }
371
+ default:
372
+ break;
356
373
  }
374
+ }
357
375
 
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;
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);
387
+ }
365
388
 
366
- if (this.sequenceTimeout) {
367
- // Start timer over
368
- clearTimeout(this.sequenceTimeout);
369
- }
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
+ }
370
396
 
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);
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({});
377
422
  }
423
+ }
378
424
 
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
- }
405
- }
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];
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
+ const eventMatcher = R.compose(R.prop("matches"), keyCode)(event);
419
434
 
420
- const eventMatcher = R.compose(R.prop("matches"), keyCode)(event);
435
+ const supportsLongPress = R.compose(
436
+ R.any(R.equals(true)),
437
+ R.map(eventMatcher)
438
+ )(supportedEvents);
421
439
 
422
- const supportsLongPress = R.compose(
423
- R.any(R.equals(true)),
424
- R.map(eventMatcher)
425
- )(supportedEvents);
440
+ return supportsLongPress;
441
+ }
426
442
 
427
- return supportsLongPress;
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);
449
+ }
428
450
  }
451
+ }
429
452
 
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
- }
453
+ onKeyDown(event) {
454
+ const { displayState, setDisplayState, navigator } = this.props;
455
+ const { isKeyboardVisible } = this.state;
456
+
457
+ const { isDialogVisible } = confirmationDialogStore.getState();
458
+
459
+ if (isKeyboardVisible && shouldUseOnScreenKeyboard()) {
460
+ if (keyCode(event).matchesAny("Escape", "Back")) {
461
+ this.handleKeyboardDismiss();
439
462
  }
440
- }
441
463
 
442
- onKeyDown(event) {
443
- const { displayState, setDisplayState, navigator } = this.props;
464
+ event.preventDefault();
465
+ event.stopPropagation();
444
466
 
445
- const { isDialogVisible } = confirmationDialogStore.getState();
467
+ return;
468
+ }
446
469
 
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);
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);
452
474
  }
475
+ }
453
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
+ ) {
454
485
  /**
455
- When the player is displayed, we listen to Native Play / Pause / Stop
456
- buttons. Any other key press will display the transport controls
486
+ registering native keys for playback
457
487
  */
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
- }
488
+ if (keyCode(event).matches(KEYS.TogglePlayPause)) {
489
+ const { playing } = playerManager.getState() || {};
472
490
 
473
- if (keyCode(event).matches(KEYS.Play)) {
474
- return playerManager.play();
491
+ if (!playing) {
492
+ return playerManager.togglePlayPause();
475
493
  }
494
+ }
476
495
 
477
- if (keyCode(event).matches(KEYS.Pause)) {
478
- setDisplayState(DISPLAY_STATES.HUD);
496
+ if (keyCode(event).matches(KEYS.Play)) {
497
+ return playerManager.play();
498
+ }
479
499
 
480
- return playerManager.pause();
481
- }
500
+ if (keyCode(event).matches(KEYS.Pause)) {
501
+ setDisplayState(DISPLAY_STATES.HUD);
482
502
 
483
- if (keyCode(event).matches(KEYS.Stop)) {
484
- setDisplayState(DISPLAY_STATES.DEFAULT);
485
- navigator.goBack();
503
+ return playerManager.pause();
504
+ }
486
505
 
487
- return;
488
- }
506
+ if (keyCode(event).matches(KEYS.Stop)) {
507
+ setDisplayState(DISPLAY_STATES.DEFAULT);
508
+ navigator.goBack();
489
509
 
490
- if (keyCode(event).matches(KEYS.Forward)) {
491
- setDisplayState(DISPLAY_STATES.HUD);
492
- this.props.seekMethods.onForwardPress();
510
+ return;
511
+ }
493
512
 
494
- return;
495
- }
513
+ if (keyCode(event).matches(KEYS.Forward)) {
514
+ setDisplayState(DISPLAY_STATES.HUD);
515
+ this.props.seekMethods.onForwardPress();
496
516
 
497
- if (keyCode(event).matches(KEYS.Rewind)) {
498
- setDisplayState(DISPLAY_STATES.HUD);
499
- this.props.seekMethods.onRewindPress();
517
+ return;
518
+ }
500
519
 
501
- return;
502
- }
520
+ if (keyCode(event).matches(KEYS.Rewind)) {
521
+ setDisplayState(DISPLAY_STATES.HUD);
522
+ this.props.seekMethods.onRewindPress();
523
+
524
+ return;
503
525
  }
526
+ }
504
527
 
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
- */
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
+ */
511
534
 
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()) {
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) {
528
563
  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
564
  } else {
545
- this.goToHome();
565
+ navigator.goBack(false, true);
546
566
  }
567
+ } else {
568
+ this.goToHome();
569
+ }
547
570
 
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
- }
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;
557
579
  }
580
+ }
558
581
 
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;
582
+ if (keyCode(event).matches(KEYS.Enter)) {
583
+ if (displayState === DISPLAY_STATES.PLAYER) {
584
+ setDisplayState(DISPLAY_STATES.HUD);
585
+ } else {
586
+ focusManager.press();
567
587
  }
568
588
 
569
- if (keyCode(event).matches(KEYS.Exit)) {
570
- if (Platform.OS === ("samsung_tv" as QuickBrickPlatforms)) {
571
- this.performExit();
589
+ return true;
590
+ }
572
591
 
573
- return;
574
- }
592
+ if (keyCode(event).matches(KEYS.Exit)) {
593
+ if (isSamsungPlatform() || shouldUseOnScreenKeyboard()) {
594
+ this.performExit();
575
595
 
576
- this.platformBack();
596
+ return;
577
597
  }
578
598
 
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
- }
599
+ this.platformBack();
600
+ }
601
601
 
602
- 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);
603
608
  }
604
- }
605
609
 
606
- onScroll(event) {
607
- const { displayState } = this.props;
610
+ this.checkSequence(event.keyCode);
608
611
 
609
- const deltaY = event?.deltaY;
612
+ const direction = keyCode(event).direction();
610
613
 
611
- const DIRECTION = {
612
- axis: "y",
613
- isHorizontal: false,
614
- isVertical: true,
615
- value: "",
616
- };
614
+ if (!isDialogVisible || !["up", "down"].includes(direction.value)) {
615
+ focusManager.moveFocus(direction);
616
+ }
617
617
 
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";
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();
624
622
  }
625
623
 
626
624
  if (
@@ -628,46 +626,153 @@ export function withInteractionManager(Component: React.FC) {
628
626
  displayState === DISPLAY_STATES.HUD
629
627
  ) {
630
628
  this.resetHudTimeout();
631
- } else {
632
- focusManager.moveFocus(DIRECTION);
633
629
  }
630
+
631
+ return true;
634
632
  }
633
+ }
635
634
 
636
- onMouseDown(event) {
637
- const { button } = event;
638
- const { displayState } = this.props;
635
+ onScroll(event) {
636
+ const { displayState } = this.props;
639
637
 
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
- }
638
+ const deltaY = event?.deltaY;
639
+
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";
647
651
  }
648
652
 
649
- onKeyboardStateChange(event) {
650
- this.setState({ isKeyboardVisible: event.visibility });
653
+ if (
654
+ displayState === DISPLAY_STATES.PLAYER ||
655
+ displayState === DISPLAY_STATES.HUD
656
+ ) {
657
+ this.resetHudTimeout();
658
+ } else {
659
+ focusManager.moveFocus(DIRECTION);
651
660
  }
661
+ }
652
662
 
653
- onConfirmDialogOpen() {
654
- this.confirmDialog.showDialog();
663
+ onMouseDown(event) {
664
+ const { button } = event;
665
+ const { displayState } = this.props;
666
+
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();
655
673
  }
674
+ }
656
675
 
657
- onConfirmDialogClose() {
658
- this.confirmDialog.hideDialog();
676
+ onKeyboardStateChange(event) {
677
+ this.setState({ isKeyboardVisible: event.visibility });
678
+ }
679
+
680
+ onConfirmDialogOpen() {
681
+ this.confirmDialog.showDialog();
682
+ }
683
+
684
+ onConfirmDialogClose() {
685
+ this.confirmDialog.hideDialog();
686
+
687
+ // restore initial focus after closing dialog
688
+ focusManager.setInitialFocus();
689
+ }
690
+
691
+ handleInputFocus = (event: FocusEvent) => {
692
+ if (!shouldUseOnScreenKeyboard()) return;
693
+ const target = event.target as HTMLInputElement;
694
+
695
+ if (this.isDismissing) return;
696
+
697
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
698
+ this.setState({
699
+ isKeyboardVisible: true,
700
+ keyboardInput: target.value || "",
701
+ activeInputRef: target,
702
+ });
659
703
  }
704
+ };
705
+
706
+ handleInputBlur(event: FocusEvent) {
707
+ if (!shouldUseOnScreenKeyboard()) return;
708
+
709
+ const target = event.target as HTMLElement;
660
710
 
661
- render() {
662
- return <Component {...this.props} />;
711
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
712
+ const relatedTarget = event.relatedTarget as HTMLElement;
713
+
714
+ if (
715
+ !relatedTarget ||
716
+ (relatedTarget.tagName !== "INPUT" &&
717
+ relatedTarget.tagName !== "TEXTAREA")
718
+ ) {
719
+ this.setState({ isKeyboardVisible: false });
720
+ }
663
721
  }
664
722
  }
665
723
 
666
- return R.compose(
667
- withNavigator,
668
- withUseSeek,
669
- withXray,
670
- PlayerContentContext.withConsumer,
671
- DisplayStateContext.withConsumer
672
- )(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
+ }
673
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);