@applicaster/zapp-react-dom-app 12.0.0-alpha.4536343143 → 12.0.0-alpha.4576271826

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