@applicaster/zapp-react-native-ui-components 16.0.0-alpha.9225550512 → 16.0.0-alpha.9338540010
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/AudioPlayer/tv/__tests__/audioPlayer.test.js +19 -0
- package/Components/AudioPlayer/tv/__tests__/index.test.tsx +120 -0
- package/Components/AudioPlayer/tv/index.tsx +5 -1
- package/Components/Cell/Cell.tsx +3 -6
- package/Components/Cell/TvOSCellComponent.tsx +0 -1
- package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +15 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +64 -0
- package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
- package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +18 -0
- package/Components/LinkHandler/LinkHandler.tsx +2 -7
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
- package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
- 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/LiveImage/executePreloadHooks.ts +2 -4
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -6
- package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
- package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +1 -1
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +4 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
- package/Components/MasterCell/__tests__/masterCell.test.js +60 -0
- package/Components/MasterCell/index.tsx +28 -4
- package/Components/MasterCell/utils/__tests__/withoutFocusedState.test.js +20 -0
- package/Components/MasterCell/utils/index.ts +18 -0
- package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +56 -0
- package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +24 -0
- package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +61 -0
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +87 -97
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +87 -126
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +140 -83
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +274 -124
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +205 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +164 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +3 -3
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +433 -0
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +563 -0
- package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
- package/Components/ModalComponent/BottomSheetModalContent.tsx +440 -66
- package/Components/ModalComponent/Button/index.tsx +5 -3
- package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
- package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
- package/Components/ModalComponent/Header/index.tsx +20 -7
- package/Components/ModalComponent/Header/utils.ts +1 -1
- package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
- package/Components/ModalComponent/SortableList.tsx +156 -0
- package/Components/ModalComponent/SortableList.web.tsx +34 -0
- package/Components/ModalComponent/TextInputContent.tsx +135 -0
- package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +343 -0
- package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +169 -0
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +120 -0
- package/Components/ModalComponent/index.tsx +18 -9
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +90 -0
- package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
- package/Components/ModalComponent/presets/index.ts +27 -0
- package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
- package/Components/ModalComponent/presets/standardCell.tsx +41 -0
- package/Components/ModalComponent/presets/types.ts +35 -0
- package/Components/ModalComponent/showTextInput.ts +54 -0
- package/Components/ModalComponent/utils.ts +82 -21
- package/Components/OfflineHandler/__tests__/index.test.tsx +109 -0
- package/Components/OfflineHandler/hooks.ts +0 -12
- package/Components/OfflineHandler/index.tsx +42 -4
- package/Components/PlayerContainer/PlayerContainer.tsx +19 -8
- package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
- package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +66 -13
- package/Components/River/ComponentsMap/ComponentsMap.tsx +89 -60
- package/Components/River/River.tsx +4 -0
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +140 -75
- package/Components/River/__tests__/componentsMap.test.js +8 -3
- package/Components/River/__tests__/resultCallback.test.tsx +47 -0
- package/Components/Screen/__tests__/Screen.test.tsx +24 -0
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
- package/Components/Screen/index.tsx +5 -0
- package/Components/Screen/navigationHandler.ts +8 -8
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
- package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +21 -4
- package/Components/Transitioner/AnimationManager.js +1 -1
- package/Components/Transitioner/Scene.tsx +4 -3
- package/Components/Transitioner/Transitioner.tsx +71 -33
- package/Components/Transitioner/__tests__/transitioner.test.js +372 -3
- package/Components/VideoLive/LiveImageManager.ts +9 -3
- package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
- package/Components/VideoModal/PlayerWrapper.tsx +4 -1
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
- package/Components/VideoModal/__tests__/playerWrapperStyle.test.ts +58 -0
- package/Components/VideoModal/__tests__/playerWrapperUtils.test.ts +16 -0
- package/Components/VideoModal/playerWrapperStyle.ts +21 -3
- package/Components/VideoModal/playerWrapperUtils.ts +7 -1
- package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
- package/Contexts/ModalNavigationContext/index.tsx +2 -2
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolbox.test.ts +347 -0
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolboxUnavailable.test.ts +53 -0
- package/Decorators/ZappPipesDataConnector/index.tsx +19 -4
- package/Decorators/ZappPipesDataConnector/screenToolbox.ts +384 -0
- package/Hooks/useEntryActionsExpander.ts +63 -0
- package/assets/empty-Info.png +0 -0
- package/package.json +6 -5
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +0 -332
- /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
|
@@ -29,12 +29,22 @@ export const OfflineHandler = ({ children }: Props) => {
|
|
|
29
29
|
const { online } = parseConnectionInfo(connectionInfo);
|
|
30
30
|
const prevOnline = usePrevious(online);
|
|
31
31
|
|
|
32
|
+
const stillOfflineAfterResumeCount =
|
|
33
|
+
connectionInfo?.stillOfflineAfterResumeCount ?? 0;
|
|
34
|
+
|
|
35
|
+
const prevStillOfflineAfterResumeCount = usePrevious(
|
|
36
|
+
stillOfflineAfterResumeCount
|
|
37
|
+
);
|
|
38
|
+
|
|
32
39
|
const emitShowToast = useSubscriberFor(SHOW_TOAST_EVENT);
|
|
33
40
|
|
|
34
41
|
const { message, extraMessage, style } = useOfflineHandlerProps(online);
|
|
35
42
|
|
|
36
43
|
const showOnlineToast = useCallback(() => {
|
|
37
|
-
logger.
|
|
44
|
+
logger.info({
|
|
45
|
+
message: "OfflineHandler: device is back online, emit toast",
|
|
46
|
+
data: { online, prevOnline },
|
|
47
|
+
});
|
|
38
48
|
|
|
39
49
|
emitShowToast({
|
|
40
50
|
id: OFFLINE_STATUS_TOAST_ID,
|
|
@@ -43,10 +53,13 @@ export const OfflineHandler = ({ children }: Props) => {
|
|
|
43
53
|
style,
|
|
44
54
|
timeout: timers.goingOnline,
|
|
45
55
|
});
|
|
46
|
-
}, [emitShowToast, message, extraMessage, style]);
|
|
56
|
+
}, [emitShowToast, message, extraMessage, style, online, prevOnline]);
|
|
47
57
|
|
|
48
58
|
const showOfflineToast = useCallback(() => {
|
|
49
|
-
logger.
|
|
59
|
+
logger.info({
|
|
60
|
+
message: "OfflineHandler: device is offline, emit toast",
|
|
61
|
+
data: { online, prevOnline, stillOfflineAfterResumeCount },
|
|
62
|
+
});
|
|
50
63
|
|
|
51
64
|
emitShowToast({
|
|
52
65
|
id: OFFLINE_STATUS_TOAST_ID,
|
|
@@ -55,7 +68,15 @@ export const OfflineHandler = ({ children }: Props) => {
|
|
|
55
68
|
style,
|
|
56
69
|
timeout: timers.goingOffline,
|
|
57
70
|
});
|
|
58
|
-
}, [
|
|
71
|
+
}, [
|
|
72
|
+
emitShowToast,
|
|
73
|
+
message,
|
|
74
|
+
extraMessage,
|
|
75
|
+
style,
|
|
76
|
+
online,
|
|
77
|
+
prevOnline,
|
|
78
|
+
stillOfflineAfterResumeCount,
|
|
79
|
+
]);
|
|
59
80
|
|
|
60
81
|
useEffect(() => {
|
|
61
82
|
// On mount there is no previous state yet: only surface the offline toast
|
|
@@ -79,5 +100,22 @@ export const OfflineHandler = ({ children }: Props) => {
|
|
|
79
100
|
}
|
|
80
101
|
}, [online, prevOnline, showOfflineToast, showOnlineToast]);
|
|
81
102
|
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
// NetworkStatusProvider bumps this counter when the app resumed while offline
|
|
105
|
+
// and is still offline after the verification delay: remind the user.
|
|
106
|
+
if (
|
|
107
|
+
prevStillOfflineAfterResumeCount !== undefined &&
|
|
108
|
+
stillOfflineAfterResumeCount > prevStillOfflineAfterResumeCount &&
|
|
109
|
+
!online
|
|
110
|
+
) {
|
|
111
|
+
showOfflineToast();
|
|
112
|
+
}
|
|
113
|
+
}, [
|
|
114
|
+
stillOfflineAfterResumeCount,
|
|
115
|
+
prevStillOfflineAfterResumeCount,
|
|
116
|
+
online,
|
|
117
|
+
showOfflineToast,
|
|
118
|
+
]);
|
|
119
|
+
|
|
82
120
|
return children;
|
|
83
121
|
};
|
|
@@ -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/const";
|
|
63
65
|
|
|
64
66
|
type Props = {
|
|
65
67
|
Player: React.ComponentType<any>;
|
|
@@ -188,12 +190,10 @@ const renderApplePlayer = ({
|
|
|
188
190
|
return null;
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
// Title/Subtitle are not resolved here: `ProgramInfo` reads them from the
|
|
194
|
+
// player manager's content channel via `usePlayerContent`.
|
|
193
195
|
return (
|
|
194
196
|
<ProgramInfo
|
|
195
|
-
title={title}
|
|
196
|
-
subtitle={summary}
|
|
197
197
|
plugin_configuration={pluginConfiguration}
|
|
198
198
|
videoStyle={videoStyle}
|
|
199
199
|
/>
|
|
@@ -282,12 +282,16 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
log_debug(
|
|
285
|
-
`onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${playNextOverlayState.entry.title}, id: ${playNextOverlayState.entry.id}`
|
|
285
|
+
`onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${playNextOverlayState.entry.title}, id: ${playNextOverlayState.entry.id}, outgoing item id: ${item?.id}`
|
|
286
286
|
);
|
|
287
287
|
|
|
288
|
+
playNextOverlayState.hidePlayNext();
|
|
289
|
+
|
|
290
|
+
playerEvent(PLAYER_EVENTS.PlayNextTriggered, { id: item?.id });
|
|
291
|
+
|
|
288
292
|
setIsLoadingNextVideo(true);
|
|
289
293
|
playEntry(playNextOverlayState.entry);
|
|
290
|
-
}, [playNextOverlayState]);
|
|
294
|
+
}, [playNextOverlayState, item?.id]);
|
|
291
295
|
|
|
292
296
|
const playNextData: Nullable<PlayNextData> = React.useMemo(() => {
|
|
293
297
|
if (!playNextOverlayState) {
|
|
@@ -400,10 +404,17 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
400
404
|
const { eventType } = event;
|
|
401
405
|
|
|
402
406
|
if (!isLanguageOverlayVisible && eventType === "menu") {
|
|
407
|
+
// First Back dismisses Play Next, the next one closes the player
|
|
408
|
+
if (playNextOverlayState) {
|
|
409
|
+
playNextOverlayState.handleUserCancelPlayNext();
|
|
410
|
+
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
403
414
|
close();
|
|
404
415
|
}
|
|
405
416
|
},
|
|
406
|
-
[close]
|
|
417
|
+
[close, playNextOverlayState]
|
|
407
418
|
);
|
|
408
419
|
|
|
409
420
|
// Effects
|
|
@@ -495,7 +506,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
495
506
|
const isAudioContent = isAudioItem(item);
|
|
496
507
|
|
|
497
508
|
useEffect(() => {
|
|
498
|
-
if (!isAudioContent) {
|
|
509
|
+
if (!isAudioContent && isApplePlatform()) {
|
|
499
510
|
sendQuickBrickEvent(
|
|
500
511
|
QUICK_BRICK_EVENTS.IDLE_TIMER_DISABLED as QuickBrickEvent,
|
|
501
512
|
{
|
|
@@ -7,12 +7,11 @@ import * as React from "react";
|
|
|
7
7
|
import { Text, TextStyle, View, ViewStyle } from "react-native";
|
|
8
8
|
import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
|
|
9
9
|
|
|
10
|
+
import { usePlayerContent } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerContent";
|
|
10
11
|
import * as programStyles from "./styles";
|
|
11
12
|
|
|
12
13
|
type Props = {
|
|
13
14
|
logo?: string;
|
|
14
|
-
title: string;
|
|
15
|
-
subtitle: string;
|
|
16
15
|
isAudioContent: boolean;
|
|
17
16
|
styles: {};
|
|
18
17
|
plugin_configuration: {
|
|
@@ -28,13 +27,13 @@ const defaultColor = "#EFEFEF";
|
|
|
28
27
|
|
|
29
28
|
function ProgramInfoComponent({
|
|
30
29
|
logo,
|
|
31
|
-
title = "",
|
|
32
|
-
subtitle = "",
|
|
33
30
|
isAudioContent,
|
|
34
31
|
styles,
|
|
35
32
|
plugin_configuration = {},
|
|
36
33
|
videoStyle = {},
|
|
37
34
|
}: Props) {
|
|
35
|
+
const { title = "", summary: subtitle = "" } = usePlayerContent();
|
|
36
|
+
|
|
38
37
|
const platformKey = styleKeys.style_namespace;
|
|
39
38
|
|
|
40
39
|
const colorTitle =
|
|
@@ -17,16 +17,37 @@ jest.mock(
|
|
|
17
17
|
);
|
|
18
18
|
|
|
19
19
|
jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
|
|
20
|
+
// Defaults to `false` for the hardware-back-handling tests below, which
|
|
21
|
+
// key off `isTV`. The ProgramInfo/content-context describe block below
|
|
22
|
+
// overrides this per test (`isTV()` is also what PlayerContainer's
|
|
23
|
+
// `playerContentValue` uses to pick TV vs mobile data keys, decoupled
|
|
24
|
+
// from `isTvOSPlatform` below).
|
|
20
25
|
isTV: jest.fn(() => false),
|
|
21
26
|
isAndroidTVPlatform: jest.fn(() => false),
|
|
22
|
-
isTvOSPlatform
|
|
27
|
+
// `isTvOSPlatform` backs a module-level constant in PlayerContainer.tsx that
|
|
28
|
+
// is read once at import time, so it must already be `true` here in order
|
|
29
|
+
// to exercise `renderApplePlayer`'s tvOS-only ProgramInfo branch in this
|
|
30
|
+
// test file. It does not affect the hardware-back-handling tests below,
|
|
31
|
+
// which key off `isTV` instead.
|
|
32
|
+
isTvOSPlatform: jest.fn(() => true),
|
|
33
|
+
isApplePlatform: jest.fn(() => true),
|
|
23
34
|
platformSelect: jest.fn(({ native }) => native),
|
|
24
35
|
}));
|
|
25
36
|
|
|
26
|
-
jest.mock("@applicaster/zapp-react-native-utils/playerUtils", () =>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
jest.mock("@applicaster/zapp-react-native-utils/playerUtils", () => {
|
|
38
|
+
// Keep the real data-key resolver (createConfigValueGetter,
|
|
39
|
+
// resolvePlayerContentText, VIDEO_PLAYER_CONTENT_DATA_KEYS) so the tvOS
|
|
40
|
+
// ProgramInfo data-key tests exercise actual resolution logic, not a stub.
|
|
41
|
+
const actual = jest.requireActual(
|
|
42
|
+
"@applicaster/zapp-react-native-utils/playerUtils"
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...actual,
|
|
47
|
+
isAudioItem: jest.fn(() => false),
|
|
48
|
+
isInlineTV: jest.fn(() => false),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
30
51
|
|
|
31
52
|
jest.mock(
|
|
32
53
|
"@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent",
|
|
@@ -75,6 +96,11 @@ jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
|
|
|
75
96
|
sendQuickBrickEvent: jest.fn(),
|
|
76
97
|
}));
|
|
77
98
|
|
|
99
|
+
// Stubbed so PlayerContainer can render without ProgramInfo's own
|
|
100
|
+
// redux/style plumbing. It no longer needs to capture Title/Subtitle: those
|
|
101
|
+
// come from the player manager's content channel, and the key-set selection
|
|
102
|
+
// they depend on is covered directly in
|
|
103
|
+
// `playerManager/__tests__/playerContent.test.ts`.
|
|
78
104
|
jest.mock("../ProgramInfo", () => ({
|
|
79
105
|
ProgramInfo: () => null,
|
|
80
106
|
}));
|
|
@@ -142,14 +168,33 @@ jest.mock(
|
|
|
142
168
|
jest.mock("../PlayerContainerContext", () => {
|
|
143
169
|
const ReactActual = require("react");
|
|
144
170
|
|
|
171
|
+
const PlayerContainerContext = ReactActual.createContext({
|
|
172
|
+
isLanguageOverlayVisible: false,
|
|
173
|
+
bottomFocusableId: "bottom",
|
|
174
|
+
showComponentsContainer: false,
|
|
175
|
+
refs: null,
|
|
176
|
+
});
|
|
177
|
+
|
|
145
178
|
return {
|
|
146
|
-
PlayerContainerContext
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
})
|
|
152
|
-
PlayerContainerContextProvider: ({ children }) =>
|
|
179
|
+
PlayerContainerContext,
|
|
180
|
+
// Mirrors just enough of the real provider for these tests: renders an
|
|
181
|
+
// actual `PlayerContainerContext.Provider` so the real
|
|
182
|
+
// `PlayerContainerContext.Consumer` inside PlayerContainer.tsx sees the
|
|
183
|
+
// container fields a real provider would give it, instead of a bare
|
|
184
|
+
// `({ children }) => children` passthrough.
|
|
185
|
+
PlayerContainerContextProvider: ({ children }: any) =>
|
|
186
|
+
ReactActual.createElement(
|
|
187
|
+
PlayerContainerContext.Provider,
|
|
188
|
+
{
|
|
189
|
+
value: {
|
|
190
|
+
isLanguageOverlayVisible: false,
|
|
191
|
+
bottomFocusableId: "bottom",
|
|
192
|
+
showComponentsContainer: false,
|
|
193
|
+
refs: null,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
children
|
|
197
|
+
),
|
|
153
198
|
};
|
|
154
199
|
});
|
|
155
200
|
|
|
@@ -200,7 +245,14 @@ const { __player: mockPlayer } = jest.requireMock(
|
|
|
200
245
|
"@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer"
|
|
201
246
|
);
|
|
202
247
|
|
|
203
|
-
const
|
|
248
|
+
const { isTV: mockIsTV } = jest.requireMock(
|
|
249
|
+
"@applicaster/zapp-react-native-utils/reactUtils"
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Renders children so that `renderApplePlayer`'s returned <ProgramInfo>
|
|
253
|
+
// element (passed as this component's `children` prop) actually mounts —
|
|
254
|
+
// otherwise React never invokes the ProgramInfo function component at all.
|
|
255
|
+
const Player = React.forwardRef((props: any, _ref) => props.children ?? null);
|
|
204
256
|
|
|
205
257
|
const item = {
|
|
206
258
|
id: "entry-1",
|
|
@@ -234,6 +286,7 @@ const pressHardwareBack = () => {
|
|
|
234
286
|
describe("PlayerContainer hardware back handling", () => {
|
|
235
287
|
beforeEach(() => {
|
|
236
288
|
jest.clearAllMocks();
|
|
289
|
+
mockIsTV.mockReturnValue(false);
|
|
237
290
|
mockBackHandlerRef.current = null;
|
|
238
291
|
mockPlayer.getPluginConfiguration.mockReturnValue({});
|
|
239
292
|
mockPlayer.isPaused.mockReturnValue(false);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as R from "ramda";
|
|
3
2
|
import { FlatList, StyleSheet, View } from "react-native";
|
|
3
|
+
import { SafeAreaView, EdgeMode, Edge } from "react-native-safe-area-context";
|
|
4
4
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
5
5
|
import { RiverItem } from "../RiverItem";
|
|
6
6
|
import { RiverFooter } from "../RiverFooter";
|
|
@@ -40,6 +40,12 @@ type Props = {
|
|
|
40
40
|
riverId?: string;
|
|
41
41
|
getStaticComponentFeed: any;
|
|
42
42
|
stickyHeaderIndices?: number[];
|
|
43
|
+
safeArea?:
|
|
44
|
+
| {
|
|
45
|
+
edges: Record<Edge, EdgeMode>;
|
|
46
|
+
mode: "padding" | "margin" | undefined;
|
|
47
|
+
}
|
|
48
|
+
| undefined;
|
|
43
49
|
};
|
|
44
50
|
|
|
45
51
|
const styles = StyleSheet.create({
|
|
@@ -52,7 +58,7 @@ const getNearestValue = (value, optionA, optionB) =>
|
|
|
52
58
|
Math.abs(value - optionA) < Math.abs(value - optionB) ? optionA : optionB;
|
|
53
59
|
|
|
54
60
|
const scrollIndicatorInsets = { right: 1 };
|
|
55
|
-
const keyExtractor =
|
|
61
|
+
const keyExtractor = (item) => item.id.toString();
|
|
56
62
|
|
|
57
63
|
function ComponentsMapComponent(props: Props) {
|
|
58
64
|
const {
|
|
@@ -64,6 +70,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
64
70
|
riverId,
|
|
65
71
|
getStaticComponentFeed,
|
|
66
72
|
stickyHeaderIndices,
|
|
73
|
+
safeArea,
|
|
67
74
|
} = props;
|
|
68
75
|
|
|
69
76
|
const flatListRef = React.useRef<FlatList | null>(null);
|
|
@@ -77,7 +84,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
77
84
|
|
|
78
85
|
const riverComponents = React.useMemo(() => {
|
|
79
86
|
if (feed?.entry?.length < components.length) {
|
|
80
|
-
return
|
|
87
|
+
return components.slice(0, feed?.entry?.length);
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
return components;
|
|
@@ -92,7 +99,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
92
99
|
const { loadingState, onLoadFinished, onLoadFailed, shouldShowLoadingError } =
|
|
93
100
|
useLoadingState(riverComponents.length, onLoadDone);
|
|
94
101
|
|
|
95
|
-
const theme = useTheme();
|
|
102
|
+
const theme = useTheme<BaseThemePropertiesMobile>();
|
|
96
103
|
|
|
97
104
|
const renderRiverItem = React.useCallback(
|
|
98
105
|
({ item, index }) => {
|
|
@@ -111,7 +118,10 @@ function ComponentsMapComponent(props: Props) {
|
|
|
111
118
|
};
|
|
112
119
|
|
|
113
120
|
return (
|
|
114
|
-
<ScreenLoadingMeasurementsListItemWrapper
|
|
121
|
+
<ScreenLoadingMeasurementsListItemWrapper
|
|
122
|
+
index={index}
|
|
123
|
+
testID={`component-${item.id}`}
|
|
124
|
+
>
|
|
115
125
|
<RiverItem {...riverItemProps} />
|
|
116
126
|
</ScreenLoadingMeasurementsListItemWrapper>
|
|
117
127
|
);
|
|
@@ -121,21 +131,13 @@ function ComponentsMapComponent(props: Props) {
|
|
|
121
131
|
|
|
122
132
|
const screenStyle = React.useMemo(
|
|
123
133
|
() => ({
|
|
124
|
-
paddingTop: ifEmptyUseFallback(
|
|
125
|
-
screenConfig.paddingTop,
|
|
126
|
-
R.prop("screen_padding_top")(theme)
|
|
127
|
-
),
|
|
128
|
-
paddingBottom: ifEmptyUseFallback(
|
|
129
|
-
screenConfig.paddingBottom,
|
|
130
|
-
R.prop("screen_padding_bottom")(theme)
|
|
131
|
-
),
|
|
132
134
|
paddingLeft: ifEmptyUseFallback(
|
|
133
135
|
screenConfig.paddingLeft,
|
|
134
|
-
|
|
136
|
+
theme.screen_padding_left
|
|
135
137
|
),
|
|
136
138
|
paddingRight: ifEmptyUseFallback(
|
|
137
139
|
screenConfig.paddingRight,
|
|
138
|
-
|
|
140
|
+
theme.screen_padding_right
|
|
139
141
|
),
|
|
140
142
|
}),
|
|
141
143
|
[riverId]
|
|
@@ -266,51 +268,79 @@ function ComponentsMapComponent(props: Props) {
|
|
|
266
268
|
numberOfComponents={riverComponents.length}
|
|
267
269
|
>
|
|
268
270
|
<ViewportTracker>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
271
|
+
<>
|
|
272
|
+
<FlatList
|
|
273
|
+
ListHeaderComponent={
|
|
274
|
+
<SafeAreaView
|
|
275
|
+
mode={safeArea?.mode ?? "margin"}
|
|
276
|
+
style={{
|
|
277
|
+
paddingTop: ifEmptyUseFallback(
|
|
278
|
+
screenConfig.paddingTop,
|
|
279
|
+
theme.screen_padding_top
|
|
280
|
+
),
|
|
281
|
+
height: StyleSheet.hairlineWidth,
|
|
282
|
+
}}
|
|
283
|
+
edges={{ top: safeArea?.edges?.top ?? "off" }}
|
|
284
|
+
/>
|
|
285
|
+
}
|
|
286
|
+
testID="components-map-flat-list"
|
|
287
|
+
ref={(ref) => {
|
|
288
|
+
flatListRef.current = ref;
|
|
289
|
+
}}
|
|
290
|
+
// Fix for WebView rerender crashes on Android API 28+
|
|
291
|
+
// https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
|
|
292
|
+
overScrollMode={isAndroid ? "never" : "auto"}
|
|
293
|
+
scrollIndicatorInsets={scrollIndicatorInsets}
|
|
294
|
+
extraData={feed}
|
|
295
|
+
stickyHeaderIndices={stickyHeaderIndices}
|
|
296
|
+
removeClippedSubviews={isAndroid}
|
|
297
|
+
onLayout={handleOnLayout}
|
|
298
|
+
initialNumToRender={3}
|
|
299
|
+
maxToRenderPerBatch={10}
|
|
300
|
+
windowSize={12}
|
|
301
|
+
keyExtractor={keyExtractor}
|
|
302
|
+
renderItem={renderRiverItem}
|
|
303
|
+
data={riverComponents}
|
|
304
|
+
contentContainerStyle={contentContainerStyle}
|
|
305
|
+
ListFooterComponent={
|
|
306
|
+
<>
|
|
307
|
+
<RiverFooter
|
|
308
|
+
flatListHeight={flatListHeight}
|
|
309
|
+
loadingState={loadingState}
|
|
310
|
+
/>
|
|
311
|
+
<SafeAreaView
|
|
312
|
+
mode={safeArea?.mode ?? "margin"}
|
|
313
|
+
style={{
|
|
314
|
+
paddingBottom: ifEmptyUseFallback(
|
|
315
|
+
screenConfig.paddingBottom,
|
|
316
|
+
theme.screen_padding_bottom
|
|
317
|
+
),
|
|
318
|
+
height: StyleSheet.hairlineWidth,
|
|
319
|
+
}}
|
|
320
|
+
edges={{ bottom: safeArea?.edges?.bottom ?? "off" }}
|
|
321
|
+
/>
|
|
322
|
+
</>
|
|
323
|
+
}
|
|
324
|
+
refreshControl={refreshControl}
|
|
325
|
+
onScrollBeginDrag={onScrollBeginDrag}
|
|
326
|
+
onScroll={onScroll}
|
|
327
|
+
onMomentumScrollEnd={_onMomentumScrollEnd}
|
|
328
|
+
onScrollEndDrag={_onScrollEndDrag}
|
|
329
|
+
scrollEventThrottle={16}
|
|
330
|
+
{...scrollViewExtraProps}
|
|
331
|
+
onEndReached={
|
|
332
|
+
/* When wrapped in a parent ScrollView (e.g. tabs),
|
|
304
333
|
this FlatList doesn't scroll so onEndReached can fire repeatedly;
|
|
305
334
|
skip it here and let the parent ScrollView emit scroll-end instead. */
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
335
|
+
isScreenWrappedInContainer
|
|
336
|
+
? undefined
|
|
337
|
+
: () => {
|
|
338
|
+
if (!hasUserScrolledRef.current) return;
|
|
339
|
+
emitScrollEndReached();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/>
|
|
343
|
+
</>
|
|
314
344
|
</ViewportTracker>
|
|
315
345
|
</ScreenLoadingMeasurements>
|
|
316
346
|
</ComponentsMapRefContext.Provider>
|
|
@@ -320,6 +350,5 @@ function ComponentsMapComponent(props: Props) {
|
|
|
320
350
|
}
|
|
321
351
|
|
|
322
352
|
export const ComponentsMap = React.memo(
|
|
323
|
-
withComponentsMapProvider(ComponentsMapComponent)
|
|
324
|
-
R.equals
|
|
353
|
+
withComponentsMapProvider(ComponentsMapComponent)
|
|
325
354
|
);
|
|
@@ -20,6 +20,8 @@ type Props = ZappScreenProps & {
|
|
|
20
20
|
setIsScreenWrappedInContainer: (isInsideContainer: boolean) => void;
|
|
21
21
|
screenContext: ZappRiver;
|
|
22
22
|
river: ZappRiver;
|
|
23
|
+
/** Result channel for screens presented as a modal; forwarded to the resolved screen. */
|
|
24
|
+
resultCallback?: hookCallback;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export class RiverComponent extends React.Component<Props> {
|
|
@@ -64,6 +66,7 @@ export class RiverComponent extends React.Component<Props> {
|
|
|
64
66
|
isInsideContainer,
|
|
65
67
|
groupId,
|
|
66
68
|
scrollViewExtraProps,
|
|
69
|
+
resultCallback,
|
|
67
70
|
} = this.props;
|
|
68
71
|
|
|
69
72
|
const { id, type } = river;
|
|
@@ -77,6 +80,7 @@ export class RiverComponent extends React.Component<Props> {
|
|
|
77
80
|
})}
|
|
78
81
|
screenId={id}
|
|
79
82
|
screenType={type}
|
|
83
|
+
resultCallback={resultCallback}
|
|
80
84
|
/>
|
|
81
85
|
);
|
|
82
86
|
}
|