@applicaster/zapp-react-dom-app 16.0.0-rc.110 → 16.0.0-rc.112
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,10 +1,26 @@
|
|
|
1
1
|
import { DISPLAY_STATES } from "@applicaster/zapp-react-native-ui-components/Contexts/DisplayStateContext";
|
|
2
2
|
import { confirmationDialogStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ConfirmDialogState";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
focusManager,
|
|
5
|
+
KEYS,
|
|
6
|
+
playerManager,
|
|
7
|
+
} from "@applicaster/zapp-react-native-utils/appUtils";
|
|
8
|
+
import { KeyInputHandler } from "@applicaster/zapp-react-native-utils/appUtils/keyInputHandler/KeyInputHandler";
|
|
9
|
+
import { TVSeekController } from "@applicaster/zapp-react-native-utils/reactHooks/player/TVSeekControlller/TVSeekController";
|
|
10
|
+
import { SKIP_TIME_BASE } from "@applicaster/zapp-react-native-utils/reactHooks/player/const";
|
|
4
11
|
import { showConfirmationDialog } from "@applicaster/zapp-react-native-utils/alertUtils";
|
|
5
12
|
|
|
6
13
|
import { InteractionManagerClass } from "..";
|
|
7
14
|
|
|
15
|
+
// The web app runs with the web key map
|
|
16
|
+
jest.mock(
|
|
17
|
+
"@applicaster/zapp-react-native-utils/appUtils/keyCodes/keys/keys",
|
|
18
|
+
() =>
|
|
19
|
+
jest.requireActual(
|
|
20
|
+
"@applicaster/zapp-react-native-utils/appUtils/keyCodes/keys/keys.web"
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
|
|
8
24
|
jest.mock("@applicaster/zapp-react-native-utils/alertUtils", () => ({
|
|
9
25
|
showConfirmationDialog: jest.fn(),
|
|
10
26
|
}));
|
|
@@ -129,4 +145,146 @@ describe("InteractionManager (web)", () => {
|
|
|
129
145
|
);
|
|
130
146
|
});
|
|
131
147
|
});
|
|
148
|
+
|
|
149
|
+
describe("Forward / Rewind keys", () => {
|
|
150
|
+
const POSITION = 100;
|
|
151
|
+
|
|
152
|
+
const createPlayer = (supportsNativeControls: boolean) => ({
|
|
153
|
+
supportsNativeControls: () => supportsNativeControls,
|
|
154
|
+
isPaused: () => false,
|
|
155
|
+
isLive: () => false,
|
|
156
|
+
getPosition: () => POSITION,
|
|
157
|
+
getSeekableDuration: () => 1000,
|
|
158
|
+
pause: jest.fn(),
|
|
159
|
+
play: jest.fn(),
|
|
160
|
+
seekTo: jest.fn(),
|
|
161
|
+
forward: jest.fn(),
|
|
162
|
+
rewind: jest.fn(),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let removeListener = () => {};
|
|
166
|
+
|
|
167
|
+
// Same wiring as usePlayerControllerSetup
|
|
168
|
+
const setup = ({ supportsNativeControls, ...props }: any) => {
|
|
169
|
+
const player = createPlayer(supportsNativeControls);
|
|
170
|
+
|
|
171
|
+
jest
|
|
172
|
+
.spyOn(playerManager, "getInstanceController")
|
|
173
|
+
.mockReturnValue(player as any);
|
|
174
|
+
|
|
175
|
+
removeListener = KeyInputHandler.getInstance().addListener(
|
|
176
|
+
new TVSeekController(player as any)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return { player, ...createManager(props) };
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const press = (manager, key) => {
|
|
183
|
+
manager.onKeyDown(KEYS[key]);
|
|
184
|
+
manager.onKeyUp(KEYS[key]);
|
|
185
|
+
jest.runAllTimers();
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
beforeEach(() => jest.useFakeTimers());
|
|
189
|
+
|
|
190
|
+
afterEach(() => {
|
|
191
|
+
removeListener();
|
|
192
|
+
jest.useRealTimers();
|
|
193
|
+
jest.restoreAllMocks();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("with a player rendering its own controls", () => {
|
|
197
|
+
const expectUntouched = (player, props) => {
|
|
198
|
+
expect(player.seekTo).not.toHaveBeenCalled();
|
|
199
|
+
expect(player.forward).not.toHaveBeenCalled();
|
|
200
|
+
expect(player.rewind).not.toHaveBeenCalled();
|
|
201
|
+
expect(player.pause).not.toHaveBeenCalled();
|
|
202
|
+
expect(props.setDisplayState).not.toHaveBeenCalled();
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
it.each(["Forward", "Rewind"])("%s is left to the player", (key) => {
|
|
206
|
+
const { player, manager, props } = setup({
|
|
207
|
+
supportsNativeControls: true,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const event = { ...KEYS[key], stopPropagation: jest.fn() };
|
|
211
|
+
|
|
212
|
+
manager.onKeyDown(event);
|
|
213
|
+
manager.onKeyUp(event);
|
|
214
|
+
jest.runAllTimers();
|
|
215
|
+
|
|
216
|
+
expectUntouched(player, props);
|
|
217
|
+
// the key has to reach the player's own handler
|
|
218
|
+
expect(event.stopPropagation).not.toHaveBeenCalled();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("does not seek the player while the play-next overlay is visible", () => {
|
|
222
|
+
const { player, manager } = setup({ supportsNativeControls: true });
|
|
223
|
+
|
|
224
|
+
(manager.props.navigator as any).getPlayNextOverlay = () => ({});
|
|
225
|
+
|
|
226
|
+
press(manager, "Forward");
|
|
227
|
+
|
|
228
|
+
expect(player.seekTo).not.toHaveBeenCalled();
|
|
229
|
+
expect(player.forward).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("keeps the keys while a dialog is visible", () => {
|
|
233
|
+
const { player, manager } = setup({ supportsNativeControls: true });
|
|
234
|
+
|
|
235
|
+
confirmationDialogStore.getState().showDialog();
|
|
236
|
+
|
|
237
|
+
press(manager, "Forward");
|
|
238
|
+
|
|
239
|
+
expect(player.seekTo).toHaveBeenCalledWith(POSITION + SKIP_TIME_BASE);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("sends the key-up to the owner of the key-down when a dialog closes in between", () => {
|
|
243
|
+
const { player, manager } = setup({ supportsNativeControls: true });
|
|
244
|
+
|
|
245
|
+
confirmationDialogStore.getState().showDialog();
|
|
246
|
+
manager.onKeyDown(KEYS.Forward);
|
|
247
|
+
confirmationDialogStore.getState().hideDialog();
|
|
248
|
+
manager.onKeyUp(KEYS.Forward);
|
|
249
|
+
jest.runAllTimers();
|
|
250
|
+
|
|
251
|
+
// a single tap seek, not a long press left running
|
|
252
|
+
expect(player.seekTo).toHaveBeenCalledTimes(1);
|
|
253
|
+
expect(player.seekTo).toHaveBeenCalledWith(POSITION + SKIP_TIME_BASE);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("leaves the key-up to the player when a dialog opens after a deferred key-down", () => {
|
|
257
|
+
const { player, manager, props } = setup({
|
|
258
|
+
supportsNativeControls: true,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
manager.onKeyDown(KEYS.Forward);
|
|
262
|
+
confirmationDialogStore.getState().showDialog();
|
|
263
|
+
manager.onKeyUp(KEYS.Forward);
|
|
264
|
+
jest.runAllTimers();
|
|
265
|
+
|
|
266
|
+
expectUntouched(player, props);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe("with QuickBrick's transport controls", () => {
|
|
271
|
+
it.each([
|
|
272
|
+
["Forward", POSITION + SKIP_TIME_BASE],
|
|
273
|
+
["Rewind", POSITION - SKIP_TIME_BASE],
|
|
274
|
+
])("%s keeps the delayed seek", (key, expectedPosition) => {
|
|
275
|
+
const { player, manager, props } = setup({
|
|
276
|
+
supportsNativeControls: false,
|
|
277
|
+
displayState: DISPLAY_STATES.PLAYER,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
press(manager, key);
|
|
281
|
+
|
|
282
|
+
expect(props.setDisplayState).toHaveBeenCalledWith(DISPLAY_STATES.HUD);
|
|
283
|
+
expect(player.seekTo).toHaveBeenCalledWith(expectedPosition);
|
|
284
|
+
expect(player.play).toHaveBeenCalled();
|
|
285
|
+
expect(player.forward).not.toHaveBeenCalled();
|
|
286
|
+
expect(player.rewind).not.toHaveBeenCalled();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
132
290
|
});
|
|
@@ -101,6 +101,8 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
101
101
|
private sequenceTimeout: NodeJS.Timeout | null;
|
|
102
102
|
private userSequence: number[] = [];
|
|
103
103
|
private isDismissing = false;
|
|
104
|
+
// Whether each key's key-down was left to the player's own controls
|
|
105
|
+
private deferredKeyDowns = new Map<number, boolean>();
|
|
104
106
|
private confirmDialog: ConfirmationDialogState =
|
|
105
107
|
confirmationDialogStore.getState();
|
|
106
108
|
|
|
@@ -368,6 +370,18 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
368
370
|
focusManager.pressOut();
|
|
369
371
|
}
|
|
370
372
|
|
|
373
|
+
// The key-up goes to whoever got the key-down, even if a dialog or the
|
|
374
|
+
// play-next overlay opened or closed in between.
|
|
375
|
+
const deferred =
|
|
376
|
+
this.deferredKeyDowns.get(event.keyCode) ??
|
|
377
|
+
this.isDeferredToNativeControls(event);
|
|
378
|
+
|
|
379
|
+
this.deferredKeyDowns.delete(event.keyCode);
|
|
380
|
+
|
|
381
|
+
if (deferred) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
371
385
|
if (
|
|
372
386
|
keyCode(event).matches(KEYS.Forward) ||
|
|
373
387
|
(__DEV__ && keyCode(event).matches(KEYS.C))
|
|
@@ -434,14 +448,6 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
434
448
|
|
|
435
449
|
const effectiveDisplayState = this.getEffectiveDisplayState();
|
|
436
450
|
|
|
437
|
-
// TEMP(native-controls): remove after device verification
|
|
438
|
-
if (effectiveDisplayState !== displayState) {
|
|
439
|
-
log_debug("[NativeControls] Back handled as PLAYER", {
|
|
440
|
-
displayState,
|
|
441
|
-
action,
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
451
|
switch (effectiveDisplayState) {
|
|
446
452
|
case DISPLAY_STATES.DEFAULT:
|
|
447
453
|
if (action === "PLATFORM_TO_BACKGROUND") {
|
|
@@ -567,12 +573,22 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
567
573
|
return (
|
|
568
574
|
this.isNativePlayerControlsEnabled() &&
|
|
569
575
|
!this.isPlayNextOverlayVisible() &&
|
|
570
|
-
(keyCode(event).matchesAny(
|
|
571
|
-
|
|
572
|
-
|
|
576
|
+
(keyCode(event).matchesAny(
|
|
577
|
+
...ARROW_KEYS,
|
|
578
|
+
KEYS.Enter,
|
|
579
|
+
// The player seeks on its own with these keys
|
|
580
|
+
KEYS.Forward,
|
|
581
|
+
KEYS.Rewind
|
|
582
|
+
) ||
|
|
583
|
+
(__DEV__ && keyCode(event).matchesAny(KEYS.X, KEYS.C, KEYS.Z)))
|
|
573
584
|
);
|
|
574
585
|
};
|
|
575
586
|
|
|
587
|
+
// An open dialog owns the remote, whatever the player renders.
|
|
588
|
+
isDeferredToNativeControls = (event: KeyboardEvent) =>
|
|
589
|
+
!confirmationDialogStore.getState().isDialogVisible &&
|
|
590
|
+
this.shouldDeferPlaybackNavigationToNativeControls(event);
|
|
591
|
+
|
|
576
592
|
onKeyDown(event) {
|
|
577
593
|
const { setDisplayState, navigator, xray } = this.props;
|
|
578
594
|
const displayState = this.getEffectiveDisplayState();
|
|
@@ -611,18 +627,11 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
611
627
|
// Only handle repeat events for arrow keys
|
|
612
628
|
if (event.repeat && !keyCode(event).matchesAny(...ARROW_KEYS)) return;
|
|
613
629
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
this.shouldDeferPlaybackNavigationToNativeControls(event)
|
|
617
|
-
) {
|
|
618
|
-
// The player's own spatial navigation handles these keys.
|
|
619
|
-
// TEMP(native-controls): remove after device verification
|
|
620
|
-
log_debug("[NativeControls] key deferred to player", {
|
|
621
|
-
key: event.key,
|
|
622
|
-
displayState,
|
|
623
|
-
activeElement: document.activeElement?.className,
|
|
624
|
-
});
|
|
630
|
+
const deferred = this.isDeferredToNativeControls(event);
|
|
631
|
+
this.deferredKeyDowns.set(event.keyCode, deferred);
|
|
625
632
|
|
|
633
|
+
if (deferred) {
|
|
634
|
+
// The player's own key handling takes these keys.
|
|
626
635
|
return;
|
|
627
636
|
}
|
|
628
637
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-dom-app",
|
|
3
|
-
"version": "16.0.0-rc.
|
|
3
|
+
"version": "16.0.0-rc.112",
|
|
4
4
|
"description": "Zapp App Component for Applicaster's Quick Brick React Native App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/applicaster/zapp-react-dom-app#readme",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@applicaster/zapp-react-dom-ui-components": "16.0.0-rc.
|
|
26
|
-
"@applicaster/zapp-react-native-bridge": "16.0.0-rc.
|
|
27
|
-
"@applicaster/zapp-react-native-redux": "16.0.0-rc.
|
|
28
|
-
"@applicaster/zapp-react-native-ui-components": "16.0.0-rc.
|
|
29
|
-
"@applicaster/zapp-react-native-utils": "16.0.0-rc.
|
|
25
|
+
"@applicaster/zapp-react-dom-ui-components": "16.0.0-rc.112",
|
|
26
|
+
"@applicaster/zapp-react-native-bridge": "16.0.0-rc.112",
|
|
27
|
+
"@applicaster/zapp-react-native-redux": "16.0.0-rc.112",
|
|
28
|
+
"@applicaster/zapp-react-native-ui-components": "16.0.0-rc.112",
|
|
29
|
+
"@applicaster/zapp-react-native-utils": "16.0.0-rc.112",
|
|
30
30
|
"abortcontroller-polyfill": "^1.7.5",
|
|
31
31
|
"typeface-montserrat": "^0.0.54",
|
|
32
32
|
"video.js": "7.14.3",
|