@applicaster/zapp-react-dom-app 12.0.0-alpha.5227180498 → 12.0.0-alpha.5230293331

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