@applicaster/zapp-react-native-ui-components 16.0.0-rc.5 → 16.0.0-rc.50
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.
- package/Components/BackgroundImage/BackgroundImage.android.tv.tsx +28 -0
- package/Components/BackgroundImage/BackgroundImage.ios.tv.tsx +24 -0
- package/Components/BackgroundImage/BackgroundImage.tsx +42 -0
- package/Components/BackgroundImage/index.ts +1 -0
- package/Components/CellRendererResolver/index.ts +2 -2
- package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
- package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
- package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
- package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
- package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +124 -0
- package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
- package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
- package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
- package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +72 -0
- package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
- package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
- package/Components/GeneralContentScreen/index.ts +2 -0
- package/Components/Layout/FullWidthRow.tsx +38 -0
- package/Components/Layout/TV/ScreenContainer.tsx +5 -0
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +0 -1
- package/Components/Layout/TV/index.tsx +3 -4
- package/Components/Layout/TV/index.web.tsx +2 -3
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +1 -1
- package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
- package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
- package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
- package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
- package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +32 -21
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
- package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
- package/Components/MasterCell/dataAdapter.ts +15 -5
- package/Components/MasterCell/hooks/index.ts +2 -0
- package/Components/MasterCell/hooks/usePressedState.ts +4 -0
- package/Components/MasterCell/index.tsx +30 -5
- package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
- package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
- package/Components/MasterCell/utils/index.ts +85 -46
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +332 -0
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +402 -0
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +811 -0
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +878 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +9 -0
- package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
- package/Components/ModalComponent/BottomSheetModalContent.tsx +49 -26
- package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
- package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
- package/Components/OfflineHandler/__tests__/index.test.tsx +78 -18
- package/Components/OfflineHandler/hooks.ts +123 -0
- package/Components/OfflineHandler/index.tsx +51 -73
- package/Components/OfflineHandler/utils/index.ts +1 -13
- package/Components/PlayerContainer/PlayerContainer.tsx +23 -13
- package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +285 -0
- package/Components/PreloaderWrapper/index.tsx +1 -1
- package/Components/River/ComponentsMap/ComponentsMap.tsx +2 -2
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +3 -15
- package/Components/Screen/TV/hooks/__tests__/useAfterPaint.test.ts +60 -0
- package/Components/Screen/TV/hooks/index.ts +2 -0
- package/Components/Screen/TV/hooks/useAfterPaint.ts +23 -0
- package/Components/Screen/TV/index.web.tsx +16 -7
- package/Components/Screen/index.tsx +8 -4
- package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
- package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
- package/Components/ScreenRevealManager/Overlay.tsx +34 -0
- package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +8 -19
- package/Components/TopCutoffOverlay/index.tsx +2 -2
- package/Components/Transitioner/Transitioner.tsx +28 -17
- package/Components/VideoLive/LiveImageManager.ts +56 -45
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +4 -2
- package/Components/VideoModal/utils.ts +6 -1
- package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
- package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
- package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
- package/Contexts/CachedDimensionsContext/index.ts +17 -2
- package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
- package/Helpers/index.js +7 -40
- package/package.json +5 -5
- package/Components/Layout/TV/LayoutBackground.tsx +0 -31
- package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
- package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
- package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
- package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
- package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
- package/Components/Screen/utils.ts +0 -16
- package/Helpers/Analytics/index.js +0 -95
- /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
|
@@ -1,105 +1,83 @@
|
|
|
1
|
-
import React, { useCallback, useEffect
|
|
2
|
-
import { View, StyleSheet } from "react-native";
|
|
3
|
-
import { NotificationView as NotificationViewNative } from "./NotificationView/NotificationView";
|
|
4
|
-
import { NotificationView as NotificationViewSamsung } from "./NotificationView/NotificationView.samsung";
|
|
5
|
-
import { NotificationView as NotificationViewLG } from "./NotificationView/NotificationView.lg";
|
|
6
|
-
|
|
1
|
+
import React, { useCallback, useEffect } from "react";
|
|
7
2
|
import { useConnectionInfo } from "@applicaster/zapp-react-native-utils/reactHooks/connection";
|
|
8
3
|
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
|
|
9
|
-
import {
|
|
4
|
+
import { useSubscriberFor } from "@applicaster/zapp-react-native-utils/reactHooks/useSubscriberFor";
|
|
10
5
|
|
|
6
|
+
import { useOfflineHandlerProps } from "./hooks";
|
|
11
7
|
import { componentsLogger } from "../../Helpers/logger";
|
|
12
8
|
import { parseConnectionInfo } from "./utils";
|
|
13
9
|
|
|
14
10
|
const logger = componentsLogger.addSubsystem("OfflineHandler");
|
|
15
11
|
|
|
12
|
+
// mirrors NOTIFICATION_TOAST_EVENTS.SHOW_TOAST in
|
|
13
|
+
// @applicaster/quick-brick-core/App/NotificationToastRenderer/NotificationToastManager
|
|
14
|
+
// (kept as a literal here since this package must not depend on quick-brick-core)
|
|
15
|
+
const SHOW_TOAST_EVENT = "showToast";
|
|
16
|
+
const OFFLINE_STATUS_TOAST_ID = "offline-status";
|
|
17
|
+
|
|
16
18
|
type Props = {
|
|
17
19
|
children?: React.ReactElement<any>;
|
|
18
20
|
};
|
|
19
21
|
|
|
20
|
-
const styles = StyleSheet.create({
|
|
21
|
-
container: {
|
|
22
|
-
flex: 1,
|
|
23
|
-
position: "relative",
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
22
|
const timers = {
|
|
28
23
|
goingOffline: 5000,
|
|
29
24
|
goingOnline: 2000,
|
|
30
25
|
};
|
|
31
26
|
|
|
32
|
-
export const NotificationView = platformSelect({
|
|
33
|
-
samsung_tv: NotificationViewSamsung,
|
|
34
|
-
lg_tv: NotificationViewLG,
|
|
35
|
-
default: NotificationViewNative,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
27
|
export const OfflineHandler = ({ children }: Props) => {
|
|
39
28
|
const connectionInfo = useConnectionInfo(false);
|
|
40
|
-
const { online
|
|
29
|
+
const { online } = parseConnectionInfo(connectionInfo);
|
|
30
|
+
const prevOnline = usePrevious(online);
|
|
41
31
|
|
|
42
|
-
const
|
|
43
|
-
const previousOnline: boolean = usePrevious(online);
|
|
44
|
-
const timer = useRef<NodeJS.Timeout | null>(null);
|
|
45
|
-
const initialRender = useRef(false);
|
|
32
|
+
const emitShowToast = useSubscriberFor(SHOW_TOAST_EVENT);
|
|
46
33
|
|
|
47
|
-
const
|
|
48
|
-
setHidden(true);
|
|
34
|
+
const { message, extraMessage, style } = useOfflineHandlerProps(online);
|
|
49
35
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
timer.current = null;
|
|
53
|
-
}
|
|
54
|
-
}, []);
|
|
36
|
+
const showOnlineToast = useCallback(() => {
|
|
37
|
+
logger.log("Device is back online, emit toast");
|
|
55
38
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
39
|
+
emitShowToast({
|
|
40
|
+
id: OFFLINE_STATUS_TOAST_ID,
|
|
41
|
+
message,
|
|
42
|
+
extraMessage,
|
|
43
|
+
style,
|
|
44
|
+
timeout: timers.goingOnline,
|
|
45
|
+
});
|
|
46
|
+
}, [emitShowToast, message, extraMessage, style]);
|
|
47
|
+
|
|
48
|
+
const showOfflineToast = useCallback(() => {
|
|
49
|
+
logger.log("Device went offline, emit toast");
|
|
60
50
|
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
emitShowToast({
|
|
52
|
+
id: OFFLINE_STATUS_TOAST_ID,
|
|
53
|
+
message,
|
|
54
|
+
extraMessage,
|
|
55
|
+
style,
|
|
56
|
+
timeout: timers.goingOffline,
|
|
57
|
+
});
|
|
58
|
+
}, [emitShowToast, message, extraMessage, style]);
|
|
63
59
|
|
|
64
60
|
useEffect(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
logger.log("Device went offline");
|
|
72
|
-
setTimer(() => setHidden(true), timers.goingOffline);
|
|
73
|
-
setHidden(false);
|
|
61
|
+
// On mount there is no previous state yet: only surface the offline toast
|
|
62
|
+
// for a device that starts offline. Don't announce "back online" for a
|
|
63
|
+
// device that was online to begin with.
|
|
64
|
+
if (prevOnline === undefined) {
|
|
65
|
+
if (!online) {
|
|
66
|
+
showOfflineToast();
|
|
74
67
|
}
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
logger.log("Device is back online");
|
|
78
|
-
setHidden(false);
|
|
79
|
-
setTimer(() => setHidden(true), timers.goingOnline);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
initialRender.current = true;
|
|
69
|
+
return;
|
|
83
70
|
}
|
|
84
71
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
72
|
+
// On subsequent renders, only emit a toast when the online/offline state changes.
|
|
73
|
+
if (prevOnline !== online) {
|
|
74
|
+
if (!online) {
|
|
75
|
+
showOfflineToast();
|
|
76
|
+
} else {
|
|
77
|
+
showOnlineToast();
|
|
89
78
|
}
|
|
90
|
-
}
|
|
91
|
-
}, [
|
|
92
|
-
|
|
93
|
-
return
|
|
94
|
-
<View style={styles.container}>
|
|
95
|
-
<NotificationView
|
|
96
|
-
hidden={hidden}
|
|
97
|
-
online={online}
|
|
98
|
-
previousOnline={previousOnline}
|
|
99
|
-
dismiss={dismiss}
|
|
100
|
-
>
|
|
101
|
-
{children}
|
|
102
|
-
</NotificationView>
|
|
103
|
-
</View>
|
|
104
|
-
);
|
|
79
|
+
}
|
|
80
|
+
}, [online, prevOnline, showOfflineToast, showOnlineToast]);
|
|
81
|
+
|
|
82
|
+
return children;
|
|
105
83
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
-
|
|
3
2
|
import { NetInfoState } from "@react-native-community/netinfo";
|
|
4
3
|
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
5
4
|
import { isOnline as isOnlineUtils } from "@applicaster/quick-brick-core/App/NetworkStatusProvider/utils";
|
|
6
|
-
|
|
5
|
+
|
|
7
6
|
import { Platform } from "react-native";
|
|
8
7
|
|
|
9
8
|
type ConnectionInfo = NetInfoState & { deviceStatus: NetStatus };
|
|
@@ -51,14 +50,3 @@ export const parseConnectionInfo: typeof parseConnectionInfoForDefault =
|
|
|
51
50
|
samsung_tv: parseConnectionInfoForSamsung,
|
|
52
51
|
default: parseConnectionInfoForDefault,
|
|
53
52
|
});
|
|
54
|
-
|
|
55
|
-
export const useNotificationHeight = () => {
|
|
56
|
-
const insets = useSafeAreaInsets();
|
|
57
|
-
|
|
58
|
-
const navBarHeight = platformSelect({ ios: 44, android: 56 });
|
|
59
|
-
|
|
60
|
-
const statusHeight = insets.top;
|
|
61
|
-
const notificationHeight = statusHeight + navBarHeight;
|
|
62
|
-
|
|
63
|
-
return { statusHeight, notificationHeight };
|
|
64
|
-
};
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
platformSelect,
|
|
10
10
|
isAndroidTVPlatform,
|
|
11
11
|
isTvOSPlatform,
|
|
12
|
+
isApplePlatform,
|
|
12
13
|
} from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
13
14
|
import {
|
|
14
15
|
isAudioItem,
|
|
@@ -60,6 +61,7 @@ import {
|
|
|
60
61
|
PlayerNativeSendCommand,
|
|
61
62
|
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
|
|
62
63
|
import { useAppData } from "@applicaster/zapp-react-native-redux";
|
|
64
|
+
import { PLAYER_EVENTS } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/conts";
|
|
63
65
|
|
|
64
66
|
type Props = {
|
|
65
67
|
Player: React.ComponentType<any>;
|
|
@@ -265,14 +267,12 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
265
267
|
navigator.goBack();
|
|
266
268
|
}, [isModal, state.playerId, showNavBar, navigator]);
|
|
267
269
|
|
|
268
|
-
const pluginConfiguration = React.useMemo(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
);
|
|
273
|
-
}, [playerManager.isRegistered()]);
|
|
270
|
+
const pluginConfiguration = React.useMemo(
|
|
271
|
+
() => player?.getPluginConfiguration(),
|
|
272
|
+
[player]
|
|
273
|
+
);
|
|
274
274
|
|
|
275
|
-
const playEntry = (entry) => navigator.replaceTop(entry, { mode });
|
|
275
|
+
const playEntry = (entry: ZappEntry) => navigator.replaceTop(entry, { mode });
|
|
276
276
|
|
|
277
277
|
const onPlayNextPerformNextVideoPlay = React.useCallback(() => {
|
|
278
278
|
if (!playNextOverlayState.entry) {
|
|
@@ -284,12 +284,16 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
log_debug(
|
|
287
|
-
`onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${playNextOverlayState.entry.title}, id: ${playNextOverlayState.entry.id}`
|
|
287
|
+
`onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${playNextOverlayState.entry.title}, id: ${playNextOverlayState.entry.id}, outgoing item id: ${item?.id}`
|
|
288
288
|
);
|
|
289
289
|
|
|
290
|
+
playNextOverlayState.hidePlayNext();
|
|
291
|
+
|
|
292
|
+
playerEvent(PLAYER_EVENTS.PlayNextTriggered, { id: item?.id });
|
|
293
|
+
|
|
290
294
|
setIsLoadingNextVideo(true);
|
|
291
295
|
playEntry(playNextOverlayState.entry);
|
|
292
|
-
}, [playNextOverlayState]);
|
|
296
|
+
}, [playNextOverlayState, item?.id]);
|
|
293
297
|
|
|
294
298
|
const playNextData: Nullable<PlayNextData> = React.useMemo(() => {
|
|
295
299
|
if (!playNextOverlayState) {
|
|
@@ -346,11 +350,15 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
346
350
|
setIsLoadingNextVideo(false);
|
|
347
351
|
|
|
348
352
|
const resumeTime = Number(item?.extensions?.resumeTime);
|
|
353
|
+
const activePlayer = playerManager.getActivePlayer();
|
|
349
354
|
|
|
350
|
-
// Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast
|
|
355
|
+
// Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast.
|
|
356
|
+
// Live streams and a missing/zero resume position are skipped: seeking to 0 on
|
|
357
|
+
// a live stream pulls playback off the live edge into a buffer-starvation seek loop.
|
|
351
358
|
if (
|
|
352
|
-
!
|
|
353
|
-
|
|
359
|
+
!activePlayer?.hasResumePosition() &&
|
|
360
|
+
resumeTime > 0 &&
|
|
361
|
+
!activePlayer?.isLive()
|
|
354
362
|
) {
|
|
355
363
|
player?.seekTo(resumeTime);
|
|
356
364
|
}
|
|
@@ -468,6 +476,8 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
468
476
|
if (isModal && mode === VideoModalMode.MAXIMIZED) {
|
|
469
477
|
if (disableMiniPlayer) {
|
|
470
478
|
navigator.closeVideoModal();
|
|
479
|
+
} else {
|
|
480
|
+
navigator.minimiseVideoModal();
|
|
471
481
|
}
|
|
472
482
|
}
|
|
473
483
|
|
|
@@ -491,7 +501,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
491
501
|
const isAudioContent = isAudioItem(item);
|
|
492
502
|
|
|
493
503
|
useEffect(() => {
|
|
494
|
-
if (!isAudioContent) {
|
|
504
|
+
if (!isAudioContent && isApplePlatform()) {
|
|
495
505
|
sendQuickBrickEvent(
|
|
496
506
|
QUICK_BRICK_EVENTS.IDLE_TIMER_DISABLED as QuickBrickEvent,
|
|
497
507
|
{
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render } from "@testing-library/react-native";
|
|
3
|
+
|
|
4
|
+
jest.mock(
|
|
5
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager",
|
|
6
|
+
() => ({
|
|
7
|
+
playerManager: {
|
|
8
|
+
invokeHandler: jest.fn(),
|
|
9
|
+
getPluginConfiguration: jest.fn(() => ({})),
|
|
10
|
+
isRegistered: jest.fn(() => true),
|
|
11
|
+
on: jest.fn().mockReturnThis(),
|
|
12
|
+
removeHandler: jest.fn().mockReturnThis(),
|
|
13
|
+
getActivePlayer: jest.fn(() => null),
|
|
14
|
+
closeNativePlayer: jest.fn(),
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
|
|
20
|
+
isTV: jest.fn(() => false),
|
|
21
|
+
isAndroidTVPlatform: jest.fn(() => false),
|
|
22
|
+
isTvOSPlatform: jest.fn(() => false),
|
|
23
|
+
isApplePlatform: jest.fn(() => true),
|
|
24
|
+
platformSelect: jest.fn(({ native }) => native),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock("@applicaster/zapp-react-native-utils/playerUtils", () => ({
|
|
28
|
+
isAudioItem: jest.fn(() => false),
|
|
29
|
+
isInlineTV: jest.fn(() => false),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
jest.mock(
|
|
33
|
+
"@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent",
|
|
34
|
+
() => ({
|
|
35
|
+
TVEventHandlerComponent: ({ children }) => children,
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/utils", () => ({
|
|
40
|
+
usePrevious: jest.fn(),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => {
|
|
44
|
+
const navigator = {
|
|
45
|
+
closeVideoModal: jest.fn(),
|
|
46
|
+
minimiseVideoModal: jest.fn(),
|
|
47
|
+
maximiseVideoModal: jest.fn(),
|
|
48
|
+
fullscreenVideoModal: jest.fn(),
|
|
49
|
+
isVideoModalDocked: jest.fn(() => false),
|
|
50
|
+
goBack: jest.fn(),
|
|
51
|
+
replaceTop: jest.fn(),
|
|
52
|
+
push: jest.fn(),
|
|
53
|
+
setPlayNextOverlay: jest.fn(),
|
|
54
|
+
currentRoute: "/playable/entry-1",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const backHandlerRef: { current: (() => boolean) | null } = {
|
|
58
|
+
current: null,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
useBackHandler: jest.fn((cb) => {
|
|
63
|
+
backHandlerRef.current = cb;
|
|
64
|
+
}),
|
|
65
|
+
useNavigation: jest.fn(() => navigator),
|
|
66
|
+
__navigator: navigator,
|
|
67
|
+
__backHandlerRef: backHandlerRef,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
|
|
72
|
+
QUICK_BRICK_EVENTS: {
|
|
73
|
+
IDLE_TIMER_DISABLED: "IDLE_TIMER_DISABLED",
|
|
74
|
+
MOVE_APP_TO_BACKGROUND: "MOVE_APP_TO_BACKGROUND",
|
|
75
|
+
},
|
|
76
|
+
sendQuickBrickEvent: jest.fn(),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
jest.mock("../ProgramInfo", () => ({
|
|
80
|
+
ProgramInfo: () => null,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
jest.mock("../../AudioPlayer", () => ({
|
|
84
|
+
AudioPlayer: () => null,
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
jest.mock("../logger", () => ({
|
|
88
|
+
log_debug: jest.fn(),
|
|
89
|
+
log_info: jest.fn(),
|
|
90
|
+
log_warning: jest.fn(),
|
|
91
|
+
playerContainerLogger: { error: jest.fn() },
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
jest.mock(
|
|
95
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer",
|
|
96
|
+
() => {
|
|
97
|
+
const player = {
|
|
98
|
+
addListener: jest.fn(),
|
|
99
|
+
removeListener: jest.fn(),
|
|
100
|
+
isPaused: jest.fn(() => false),
|
|
101
|
+
isAd: jest.fn(() => false),
|
|
102
|
+
seekTo: jest.fn(),
|
|
103
|
+
getPluginConfiguration: jest.fn(() => ({})),
|
|
104
|
+
getOverlayObservable: jest.fn(() => ({ getPlayNextEntry: () => null })),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
usePlayer: jest.fn(() => player),
|
|
109
|
+
__player: player,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
jest.mock(
|
|
115
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayNextOverlay",
|
|
116
|
+
() => ({
|
|
117
|
+
usePlayNextOverlay: jest.fn(() => null),
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
jest.mock(
|
|
122
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand",
|
|
123
|
+
() => ({
|
|
124
|
+
PlayerNativeCommandTypes: { clearPlayerData: "clearPlayerData" },
|
|
125
|
+
PlayerNativeSendCommand: jest.fn(),
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
jest.mock(
|
|
130
|
+
"@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
|
|
131
|
+
() => ({
|
|
132
|
+
useSetNavbarState: jest.fn(() => ({ setVisible: jest.fn() })),
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
jest.mock(
|
|
137
|
+
"@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData",
|
|
138
|
+
() => ({
|
|
139
|
+
useTargetScreenData: jest.fn(() => ({ id: "screen-id", styles: {} })),
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
jest.mock("../PlayerContainerContext", () => {
|
|
144
|
+
const ReactActual = require("react");
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
PlayerContainerContext: ReactActual.createContext({
|
|
148
|
+
isLanguageOverlayVisible: false,
|
|
149
|
+
bottomFocusableId: "bottom",
|
|
150
|
+
showComponentsContainer: false,
|
|
151
|
+
refs: null,
|
|
152
|
+
}),
|
|
153
|
+
PlayerContainerContextProvider: ({ children }) => children,
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
jest.mock(
|
|
158
|
+
"@applicaster/zapp-react-native-ui-components/Components/FocusableGroup",
|
|
159
|
+
() => ({
|
|
160
|
+
FocusableGroup: ({ children }) => children,
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
jest.mock("../WappersView/PlayerFocusableWrapperView", () => ({
|
|
165
|
+
PlayerFocusableWrapperView: ({ children }) => children,
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
jest.mock("../WappersView/ComponentFocusableWrapperView", () => ({
|
|
169
|
+
ComponentFocusableWrapperView: () => null,
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
jest.mock("../index", () => ({
|
|
173
|
+
FocusableGroupMainContainerId: "player-container-general",
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
jest.mock(
|
|
177
|
+
"@applicaster/zapp-react-native-utils/navigationUtils/itemTypeMatchers",
|
|
178
|
+
() => ({
|
|
179
|
+
isPlayable: jest.fn(() => true),
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
jest.mock("../../GeneralContentScreen", () => ({
|
|
184
|
+
GeneralContentScreen: () => null,
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
jest.mock("@applicaster/zapp-react-native-redux", () => ({
|
|
188
|
+
useAppData: jest.fn(() => ({ isTabletPortrait: false })),
|
|
189
|
+
}));
|
|
190
|
+
|
|
191
|
+
import { PlayerContainer, VideoModalMode } from "../PlayerContainer";
|
|
192
|
+
|
|
193
|
+
const { __navigator: mockNavigator, __backHandlerRef: mockBackHandlerRef } =
|
|
194
|
+
jest.requireMock("@applicaster/zapp-react-native-utils/reactHooks");
|
|
195
|
+
|
|
196
|
+
const { playerManager: mockPlayerManager } = jest.requireMock(
|
|
197
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager"
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const { __player: mockPlayer } = jest.requireMock(
|
|
201
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer"
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const Player = React.forwardRef(() => null);
|
|
205
|
+
|
|
206
|
+
const item = {
|
|
207
|
+
id: "entry-1",
|
|
208
|
+
title: "Test entry",
|
|
209
|
+
summary: "",
|
|
210
|
+
content: { src: "https://example.com/video.m3u8" },
|
|
211
|
+
extensions: {},
|
|
212
|
+
} as any;
|
|
213
|
+
|
|
214
|
+
const renderPlayerContainer = (props = {}) =>
|
|
215
|
+
render(
|
|
216
|
+
<PlayerContainer
|
|
217
|
+
Player={Player}
|
|
218
|
+
item={item}
|
|
219
|
+
style={{}}
|
|
220
|
+
loadPipesData={jest.fn()}
|
|
221
|
+
isModal={true}
|
|
222
|
+
mode={VideoModalMode.MAXIMIZED}
|
|
223
|
+
{...props}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const pressHardwareBack = () => {
|
|
228
|
+
if (!mockBackHandlerRef.current) {
|
|
229
|
+
throw new Error("back handler was not registered via useBackHandler");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return mockBackHandlerRef.current();
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
describe("PlayerContainer hardware back handling", () => {
|
|
236
|
+
beforeEach(() => {
|
|
237
|
+
jest.clearAllMocks();
|
|
238
|
+
mockBackHandlerRef.current = null;
|
|
239
|
+
mockPlayer.getPluginConfiguration.mockReturnValue({});
|
|
240
|
+
mockPlayer.isPaused.mockReturnValue(false);
|
|
241
|
+
mockPlayer.isAd.mockReturnValue(false);
|
|
242
|
+
|
|
243
|
+
mockPlayer.getOverlayObservable.mockReturnValue({
|
|
244
|
+
getPlayNextEntry: () => null,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
mockPlayerManager.getPluginConfiguration.mockReturnValue({});
|
|
248
|
+
mockPlayerManager.on.mockReturnThis();
|
|
249
|
+
mockPlayerManager.removeHandler.mockReturnThis();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("minimises the video modal on back press when mini player is enabled", () => {
|
|
253
|
+
renderPlayerContainer();
|
|
254
|
+
|
|
255
|
+
const handled = pressHardwareBack();
|
|
256
|
+
|
|
257
|
+
expect(mockNavigator.minimiseVideoModal).toHaveBeenCalled();
|
|
258
|
+
expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
|
|
259
|
+
expect(handled).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("closes the video modal on back press when mini player is disabled", () => {
|
|
263
|
+
mockPlayer.getPluginConfiguration.mockReturnValue({
|
|
264
|
+
disable_mini_player_when_inline: true,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
renderPlayerContainer();
|
|
268
|
+
|
|
269
|
+
const handled = pressHardwareBack();
|
|
270
|
+
|
|
271
|
+
expect(mockNavigator.closeVideoModal).toHaveBeenCalled();
|
|
272
|
+
expect(mockNavigator.minimiseVideoModal).not.toHaveBeenCalled();
|
|
273
|
+
expect(handled).toBe(true);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("does not handle back press when the player is already minimised", () => {
|
|
277
|
+
renderPlayerContainer({ mode: VideoModalMode.MINIMIZED });
|
|
278
|
+
|
|
279
|
+
const handled = pressHardwareBack();
|
|
280
|
+
|
|
281
|
+
expect(handled).toBe(false);
|
|
282
|
+
expect(mockNavigator.minimiseVideoModal).not.toHaveBeenCalled();
|
|
283
|
+
expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -9,7 +9,7 @@ import { useScreenConfiguration } from "../useScreenConfiguration";
|
|
|
9
9
|
import { RefreshControl } from "../RefreshControl";
|
|
10
10
|
import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
useMarkPipesDataStale,
|
|
13
13
|
useProfilerLogging,
|
|
14
14
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
15
15
|
import { useLoadingState } from "./hooks/useLoadingState";
|
|
@@ -154,7 +154,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
154
154
|
[flatListHeight]
|
|
155
155
|
);
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
useMarkPipesDataStale(riverId, riverComponents);
|
|
158
158
|
|
|
159
159
|
const refreshControl = React.useMemo(
|
|
160
160
|
() => (pullToRefreshEnabled ? <RefreshControl /> : null),
|
|
@@ -10,11 +10,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
10
10
|
>
|
|
11
11
|
<View
|
|
12
12
|
onLayout={[Function]}
|
|
13
|
-
style={
|
|
14
|
-
{
|
|
15
|
-
"flex": 1,
|
|
16
|
-
}
|
|
17
|
-
}
|
|
13
|
+
style={{}}
|
|
18
14
|
>
|
|
19
15
|
<RCTScrollView
|
|
20
16
|
ListFooterComponent={
|
|
@@ -167,11 +163,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
167
163
|
>
|
|
168
164
|
<View
|
|
169
165
|
onLayout={[Function]}
|
|
170
|
-
style={
|
|
171
|
-
{
|
|
172
|
-
"flex": 1,
|
|
173
|
-
}
|
|
174
|
-
}
|
|
166
|
+
style={{}}
|
|
175
167
|
>
|
|
176
168
|
<View />
|
|
177
169
|
</View>
|
|
@@ -183,11 +175,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
183
175
|
>
|
|
184
176
|
<View
|
|
185
177
|
onLayout={[Function]}
|
|
186
|
-
style={
|
|
187
|
-
{
|
|
188
|
-
"flex": 1,
|
|
189
|
-
}
|
|
190
|
-
}
|
|
178
|
+
style={{}}
|
|
191
179
|
/>
|
|
192
180
|
</View>
|
|
193
181
|
<View
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { renderHook, act } from "@testing-library/react-native";
|
|
2
|
+
|
|
3
|
+
import { useAfterPaint } from "../useAfterPaint";
|
|
4
|
+
|
|
5
|
+
describe("useAfterPaint", () => {
|
|
6
|
+
let frameCallbacks: FrameRequestCallback[];
|
|
7
|
+
let originalRaf: typeof requestAnimationFrame;
|
|
8
|
+
let originalCancelRaf: typeof cancelAnimationFrame;
|
|
9
|
+
|
|
10
|
+
const flushFrame = () => {
|
|
11
|
+
const callbacks = frameCallbacks;
|
|
12
|
+
frameCallbacks = [];
|
|
13
|
+
|
|
14
|
+
act(() => {
|
|
15
|
+
callbacks.forEach((cb) => cb(0));
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
frameCallbacks = [];
|
|
21
|
+
originalRaf = global.requestAnimationFrame;
|
|
22
|
+
originalCancelRaf = global.cancelAnimationFrame;
|
|
23
|
+
|
|
24
|
+
global.requestAnimationFrame = jest.fn((cb: FrameRequestCallback) => {
|
|
25
|
+
frameCallbacks.push(cb);
|
|
26
|
+
|
|
27
|
+
return frameCallbacks.length;
|
|
28
|
+
}) as unknown as typeof requestAnimationFrame;
|
|
29
|
+
|
|
30
|
+
global.cancelAnimationFrame = jest.fn();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
global.requestAnimationFrame = originalRaf;
|
|
35
|
+
global.cancelAnimationFrame = originalCancelRaf;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns false on the initial render", () => {
|
|
39
|
+
const { result } = renderHook(() => useAfterPaint());
|
|
40
|
+
|
|
41
|
+
expect(result.current).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("stays false after only one animation frame (before paint completes)", () => {
|
|
45
|
+
const { result } = renderHook(() => useAfterPaint());
|
|
46
|
+
|
|
47
|
+
flushFrame();
|
|
48
|
+
|
|
49
|
+
expect(result.current).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns true after two animation frames (a real post-paint boundary)", () => {
|
|
53
|
+
const { result } = renderHook(() => useAfterPaint());
|
|
54
|
+
|
|
55
|
+
flushFrame();
|
|
56
|
+
flushFrame();
|
|
57
|
+
|
|
58
|
+
expect(result.current).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|