@applicaster/zapp-react-native-ui-components 16.0.0-rc.12 → 16.0.0-rc.120

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.
Files changed (166) hide show
  1. package/Components/AudioPlayer/tv/__tests__/audioPlayer.test.js +19 -0
  2. package/Components/AudioPlayer/tv/__tests__/index.test.tsx +120 -0
  3. package/Components/AudioPlayer/tv/index.tsx +5 -1
  4. package/Components/Cell/Cell.tsx +3 -6
  5. package/Components/Cell/TvOSCellComponent.tsx +0 -1
  6. package/Components/CellRendererResolver/index.ts +1 -1
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
  8. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +54 -0
  9. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  10. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  11. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  12. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +188 -0
  15. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  16. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  17. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  18. package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
  19. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +90 -0
  20. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  22. package/Components/GeneralContentScreen/index.ts +2 -0
  23. package/Components/Layout/FullWidthRow.tsx +38 -0
  24. package/Components/LinkHandler/LinkHandler.tsx +2 -7
  25. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +5 -1
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
  28. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
  29. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  30. package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
  31. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
  32. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
  33. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
  34. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  35. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
  36. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -6
  37. package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
  38. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
  39. package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
  40. package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
  41. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
  42. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  43. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  44. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  45. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
  46. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +34 -21
  47. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  48. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  49. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  50. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  51. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  52. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  53. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
  54. package/Components/MasterCell/__tests__/masterCell.test.js +60 -0
  55. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  56. package/Components/MasterCell/dataAdapter.ts +15 -5
  57. package/Components/MasterCell/hooks/index.ts +2 -0
  58. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  59. package/Components/MasterCell/index.tsx +57 -8
  60. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
  61. package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
  62. package/Components/MasterCell/utils/__tests__/withoutFocusedState.test.js +20 -0
  63. package/Components/MasterCell/utils/index.ts +103 -46
  64. package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +56 -0
  65. package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
  66. package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +24 -0
  67. package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
  68. package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +61 -0
  69. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +392 -0
  70. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +772 -0
  71. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +728 -0
  72. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +1028 -0
  73. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +205 -0
  74. package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +164 -0
  75. package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
  76. package/Components/ModalComponent/AudioPlayer/Components/index.ts +9 -0
  77. package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +433 -0
  78. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +563 -0
  79. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  80. package/Components/ModalComponent/BottomSheetModalContent.tsx +440 -50
  81. package/Components/ModalComponent/Button/index.tsx +5 -3
  82. package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
  83. package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
  84. package/Components/ModalComponent/Header/index.tsx +20 -7
  85. package/Components/ModalComponent/Header/utils.ts +1 -1
  86. package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
  87. package/Components/ModalComponent/SortableList.tsx +156 -0
  88. package/Components/ModalComponent/SortableList.web.tsx +34 -0
  89. package/Components/ModalComponent/TextInputContent.tsx +135 -0
  90. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  91. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +343 -0
  92. package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
  93. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +169 -0
  94. package/Components/ModalComponent/__tests__/showTextInput.test.ts +120 -0
  95. package/Components/ModalComponent/index.tsx +18 -9
  96. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +90 -0
  97. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
  98. package/Components/ModalComponent/presets/index.ts +27 -0
  99. package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
  100. package/Components/ModalComponent/presets/standardCell.tsx +41 -0
  101. package/Components/ModalComponent/presets/types.ts +35 -0
  102. package/Components/ModalComponent/showTextInput.ts +54 -0
  103. package/Components/ModalComponent/utils.ts +82 -21
  104. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  105. package/Components/OfflineHandler/__tests__/index.test.tsx +186 -17
  106. package/Components/OfflineHandler/hooks.ts +111 -0
  107. package/Components/OfflineHandler/index.tsx +91 -75
  108. package/Components/OfflineHandler/utils/index.ts +1 -13
  109. package/Components/PlayerContainer/PlayerContainer.tsx +28 -11
  110. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
  111. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +337 -0
  112. package/Components/PreloaderWrapper/index.tsx +1 -1
  113. package/Components/River/ComponentsMap/ComponentsMap.tsx +91 -62
  114. package/Components/River/River.tsx +4 -0
  115. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +142 -89
  116. package/Components/River/__tests__/componentsMap.test.js +8 -3
  117. package/Components/River/__tests__/resultCallback.test.tsx +47 -0
  118. package/Components/Screen/__tests__/Screen.test.tsx +24 -0
  119. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
  120. package/Components/Screen/index.tsx +13 -4
  121. package/Components/Screen/navigationHandler.ts +8 -8
  122. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  123. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  124. package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
  125. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
  126. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
  127. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +21 -4
  128. package/Components/TopCutoffOverlay/index.tsx +2 -2
  129. package/Components/Transitioner/AnimationManager.js +1 -1
  130. package/Components/Transitioner/Scene.tsx +4 -3
  131. package/Components/Transitioner/Transitioner.tsx +98 -49
  132. package/Components/Transitioner/__tests__/transitioner.test.js +372 -3
  133. package/Components/VideoLive/LiveImageManager.ts +9 -3
  134. package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
  135. package/Components/VideoModal/PlayerWrapper.tsx +4 -1
  136. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  137. package/Components/VideoModal/__tests__/playerWrapperStyle.test.ts +58 -0
  138. package/Components/VideoModal/__tests__/playerWrapperUtils.test.ts +16 -0
  139. package/Components/VideoModal/playerWrapperStyle.ts +21 -3
  140. package/Components/VideoModal/playerWrapperUtils.ts +7 -1
  141. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
  142. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  143. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  144. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  145. package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
  146. package/Contexts/ModalNavigationContext/index.tsx +2 -2
  147. package/Decorators/ZappPipesDataConnector/__tests__/screenToolbox.test.ts +347 -0
  148. package/Decorators/ZappPipesDataConnector/__tests__/screenToolboxUnavailable.test.ts +53 -0
  149. package/Decorators/ZappPipesDataConnector/index.tsx +19 -4
  150. package/Decorators/ZappPipesDataConnector/screenToolbox.ts +384 -0
  151. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  152. package/Helpers/index.js +0 -39
  153. package/Hooks/useEntryActionsExpander.ts +63 -0
  154. package/assets/empty-Info.png +0 -0
  155. package/package.json +6 -5
  156. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  157. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  158. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  159. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
  160. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  161. package/Components/Screen/utils.ts +0 -16
  162. package/Helpers/Analytics/index.js +0 -95
  163. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  164. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  165. /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
  166. /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
@@ -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 =
@@ -0,0 +1,337 @@
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
+ // 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).
25
+ isTV: jest.fn(() => false),
26
+ isAndroidTVPlatform: jest.fn(() => false),
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),
34
+ platformSelect: jest.fn(({ native }) => native),
35
+ }));
36
+
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
+ });
51
+
52
+ jest.mock(
53
+ "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent",
54
+ () => ({
55
+ TVEventHandlerComponent: ({ children }) => children,
56
+ })
57
+ );
58
+
59
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/utils", () => ({
60
+ usePrevious: jest.fn(),
61
+ }));
62
+
63
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => {
64
+ const navigator = {
65
+ closeVideoModal: jest.fn(),
66
+ minimiseVideoModal: jest.fn(),
67
+ maximiseVideoModal: jest.fn(),
68
+ fullscreenVideoModal: jest.fn(),
69
+ isVideoModalDocked: jest.fn(() => false),
70
+ goBack: jest.fn(),
71
+ replaceTop: jest.fn(),
72
+ push: jest.fn(),
73
+ setPlayNextOverlay: jest.fn(),
74
+ currentRoute: "/playable/entry-1",
75
+ };
76
+
77
+ const backHandlerRef: { current: (() => boolean) | null } = {
78
+ current: null,
79
+ };
80
+
81
+ return {
82
+ useBackHandler: jest.fn((cb) => {
83
+ backHandlerRef.current = cb;
84
+ }),
85
+ useNavigation: jest.fn(() => navigator),
86
+ __navigator: navigator,
87
+ __backHandlerRef: backHandlerRef,
88
+ };
89
+ });
90
+
91
+ jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
92
+ QUICK_BRICK_EVENTS: {
93
+ IDLE_TIMER_DISABLED: "IDLE_TIMER_DISABLED",
94
+ MOVE_APP_TO_BACKGROUND: "MOVE_APP_TO_BACKGROUND",
95
+ },
96
+ sendQuickBrickEvent: jest.fn(),
97
+ }));
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`.
104
+ jest.mock("../ProgramInfo", () => ({
105
+ ProgramInfo: () => null,
106
+ }));
107
+
108
+ jest.mock("../../AudioPlayer", () => ({
109
+ AudioPlayer: () => null,
110
+ }));
111
+
112
+ jest.mock("../logger", () => ({
113
+ log_debug: jest.fn(),
114
+ log_info: jest.fn(),
115
+ log_warning: jest.fn(),
116
+ playerContainerLogger: { error: jest.fn() },
117
+ }));
118
+
119
+ jest.mock(
120
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer",
121
+ () => {
122
+ const player = {
123
+ addListener: jest.fn(),
124
+ removeListener: jest.fn(),
125
+ isPaused: jest.fn(() => false),
126
+ isAd: jest.fn(() => false),
127
+ seekTo: jest.fn(),
128
+ getPluginConfiguration: jest.fn(() => ({})),
129
+ getOverlayObservable: jest.fn(() => ({ getPlayNextEntry: () => null })),
130
+ };
131
+
132
+ return {
133
+ usePlayer: jest.fn(() => player),
134
+ __player: player,
135
+ };
136
+ }
137
+ );
138
+
139
+ jest.mock(
140
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayNextOverlay",
141
+ () => ({
142
+ usePlayNextOverlay: jest.fn(() => null),
143
+ })
144
+ );
145
+
146
+ jest.mock(
147
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand",
148
+ () => ({
149
+ PlayerNativeCommandTypes: { clearPlayerData: "clearPlayerData" },
150
+ PlayerNativeSendCommand: jest.fn(),
151
+ })
152
+ );
153
+
154
+ jest.mock(
155
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
156
+ () => ({
157
+ useSetNavbarState: jest.fn(() => ({ setVisible: jest.fn() })),
158
+ })
159
+ );
160
+
161
+ jest.mock(
162
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData",
163
+ () => ({
164
+ useTargetScreenData: jest.fn(() => ({ id: "screen-id", styles: {} })),
165
+ })
166
+ );
167
+
168
+ jest.mock("../PlayerContainerContext", () => {
169
+ const ReactActual = require("react");
170
+
171
+ const PlayerContainerContext = ReactActual.createContext({
172
+ isLanguageOverlayVisible: false,
173
+ bottomFocusableId: "bottom",
174
+ showComponentsContainer: false,
175
+ refs: null,
176
+ });
177
+
178
+ return {
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
+ ),
198
+ };
199
+ });
200
+
201
+ jest.mock(
202
+ "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup",
203
+ () => ({
204
+ FocusableGroup: ({ children }) => children,
205
+ })
206
+ );
207
+
208
+ jest.mock("../WappersView/PlayerFocusableWrapperView", () => ({
209
+ PlayerFocusableWrapperView: ({ children }) => children,
210
+ }));
211
+
212
+ jest.mock("../WappersView/ComponentFocusableWrapperView", () => ({
213
+ ComponentFocusableWrapperView: () => null,
214
+ }));
215
+
216
+ jest.mock("../index", () => ({
217
+ FocusableGroupMainContainerId: "player-container-general",
218
+ }));
219
+
220
+ jest.mock(
221
+ "@applicaster/zapp-react-native-utils/navigationUtils/itemTypeMatchers",
222
+ () => ({
223
+ isPlayable: jest.fn(() => true),
224
+ })
225
+ );
226
+
227
+ jest.mock("../../GeneralContentScreen", () => ({
228
+ GeneralContentScreen: () => null,
229
+ }));
230
+
231
+ jest.mock("@applicaster/zapp-react-native-redux", () => ({
232
+ useAppData: jest.fn(() => ({ isTabletPortrait: false })),
233
+ }));
234
+
235
+ import { PlayerContainer, VideoModalMode } from "../PlayerContainer";
236
+
237
+ const { __navigator: mockNavigator, __backHandlerRef: mockBackHandlerRef } =
238
+ jest.requireMock("@applicaster/zapp-react-native-utils/reactHooks");
239
+
240
+ const { playerManager: mockPlayerManager } = jest.requireMock(
241
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager"
242
+ );
243
+
244
+ const { __player: mockPlayer } = jest.requireMock(
245
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer"
246
+ );
247
+
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);
256
+
257
+ const item = {
258
+ id: "entry-1",
259
+ title: "Test entry",
260
+ summary: "",
261
+ content: { src: "https://example.com/video.m3u8" },
262
+ extensions: {},
263
+ } as any;
264
+
265
+ const renderPlayerContainer = (props = {}) =>
266
+ render(
267
+ <PlayerContainer
268
+ Player={Player}
269
+ item={item}
270
+ style={{}}
271
+ loadPipesData={jest.fn()}
272
+ isModal={true}
273
+ mode={VideoModalMode.MAXIMIZED}
274
+ {...props}
275
+ />
276
+ );
277
+
278
+ const pressHardwareBack = () => {
279
+ if (!mockBackHandlerRef.current) {
280
+ throw new Error("back handler was not registered via useBackHandler");
281
+ }
282
+
283
+ return mockBackHandlerRef.current();
284
+ };
285
+
286
+ describe("PlayerContainer hardware back handling", () => {
287
+ beforeEach(() => {
288
+ jest.clearAllMocks();
289
+ mockIsTV.mockReturnValue(false);
290
+ mockBackHandlerRef.current = null;
291
+ mockPlayer.getPluginConfiguration.mockReturnValue({});
292
+ mockPlayer.isPaused.mockReturnValue(false);
293
+ mockPlayer.isAd.mockReturnValue(false);
294
+
295
+ mockPlayer.getOverlayObservable.mockReturnValue({
296
+ getPlayNextEntry: () => null,
297
+ });
298
+
299
+ mockPlayerManager.getPluginConfiguration.mockReturnValue({});
300
+ mockPlayerManager.on.mockReturnThis();
301
+ mockPlayerManager.removeHandler.mockReturnThis();
302
+ });
303
+
304
+ it("minimises the video modal on back press when mini player is enabled", () => {
305
+ renderPlayerContainer();
306
+
307
+ const handled = pressHardwareBack();
308
+
309
+ expect(mockNavigator.minimiseVideoModal).toHaveBeenCalled();
310
+ expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
311
+ expect(handled).toBe(true);
312
+ });
313
+
314
+ it("closes the video modal on back press when mini player is disabled", () => {
315
+ mockPlayer.getPluginConfiguration.mockReturnValue({
316
+ disable_mini_player_when_inline: true,
317
+ });
318
+
319
+ renderPlayerContainer();
320
+
321
+ const handled = pressHardwareBack();
322
+
323
+ expect(mockNavigator.closeVideoModal).toHaveBeenCalled();
324
+ expect(mockNavigator.minimiseVideoModal).not.toHaveBeenCalled();
325
+ expect(handled).toBe(true);
326
+ });
327
+
328
+ it("does not handle back press when the player is already minimised", () => {
329
+ renderPlayerContainer({ mode: VideoModalMode.MINIMIZED });
330
+
331
+ const handled = pressHardwareBack();
332
+
333
+ expect(handled).toBe(false);
334
+ expect(mockNavigator.minimiseVideoModal).not.toHaveBeenCalled();
335
+ expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
336
+ });
337
+ });
@@ -2,7 +2,7 @@ import React from "react";
2
2
 
3
3
  type PreloaderWrapperProps = {
4
4
  showPreloader?: boolean;
5
- children?: React.ReactNode;
5
+ children: React.ReactElement | null;
6
6
  };
7
7
 
8
8
  export const PreloaderWrapper: React.FC<PreloaderWrapperProps> = ({
@@ -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";
@@ -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
- usePipesCacheReset,
12
+ useMarkPipesDataStale,
13
13
  useProfilerLogging,
14
14
  } from "@applicaster/zapp-react-native-utils/reactHooks";
15
15
  import { useLoadingState } from "./hooks/useLoadingState";
@@ -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 = R.compose(String, R.prop("id"));
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 R.slice(0, feed?.entry?.length, components);
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 index={index}>
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
- R.prop("screen_padding_left")(theme)
136
+ theme.screen_padding_left
135
137
  ),
136
138
  paddingRight: ifEmptyUseFallback(
137
139
  screenConfig.paddingRight,
138
- R.prop("screen_padding_right")(theme)
140
+ theme.screen_padding_right
139
141
  ),
140
142
  }),
141
143
  [riverId]
@@ -154,7 +156,7 @@ function ComponentsMapComponent(props: Props) {
154
156
  [flatListHeight]
155
157
  );
156
158
 
157
- usePipesCacheReset(riverId, riverComponents);
159
+ useMarkPipesDataStale(riverId, riverComponents);
158
160
 
159
161
  const refreshControl = React.useMemo(
160
162
  () => (pullToRefreshEnabled ? <RefreshControl /> : null),
@@ -266,51 +268,79 @@ function ComponentsMapComponent(props: Props) {
266
268
  numberOfComponents={riverComponents.length}
267
269
  >
268
270
  <ViewportTracker>
269
- <FlatList
270
- testID="components-map-flat-list"
271
- ref={(ref) => {
272
- flatListRef.current = ref;
273
- }}
274
- // Fix for WebView rerender crashes on Android API 28+
275
- // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
276
- overScrollMode={isAndroid ? "never" : "auto"}
277
- scrollIndicatorInsets={scrollIndicatorInsets}
278
- extraData={feed}
279
- stickyHeaderIndices={stickyHeaderIndices}
280
- removeClippedSubviews={isAndroid}
281
- onLayout={handleOnLayout}
282
- initialNumToRender={3}
283
- maxToRenderPerBatch={10}
284
- windowSize={12}
285
- keyExtractor={keyExtractor}
286
- renderItem={renderRiverItem}
287
- data={riverComponents}
288
- contentContainerStyle={contentContainerStyle}
289
- ListFooterComponent={
290
- <RiverFooter
291
- flatListHeight={flatListHeight}
292
- loadingState={loadingState}
293
- />
294
- }
295
- refreshControl={refreshControl}
296
- onScrollBeginDrag={onScrollBeginDrag}
297
- onScroll={onScroll}
298
- onMomentumScrollEnd={_onMomentumScrollEnd}
299
- onScrollEndDrag={_onScrollEndDrag}
300
- scrollEventThrottle={16}
301
- {...scrollViewExtraProps}
302
- onEndReached={
303
- /* When wrapped in a parent ScrollView (e.g. tabs),
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
- isScreenWrappedInContainer
307
- ? undefined
308
- : () => {
309
- if (!hasUserScrolledRef.current) return;
310
- emitScrollEndReached();
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
  }