@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4400210593 → 12.0.0-alpha.4408649484

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 (95) hide show
  1. package/Components/BaseFocusable/index.tsx +3 -3
  2. package/Components/Cell/Cell.tsx +10 -5
  3. package/Components/Cell/CellWithFocusable.tsx +23 -9
  4. package/Components/Cell/TvOSCellComponent.tsx +3 -7
  5. package/Components/Focusable/Focusable.tsx +6 -1
  6. package/Components/FocusableGroup/index.tsx +11 -2
  7. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  8. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  9. package/Components/HandlePlayable/HandlePlayable.tsx +3 -0
  10. package/Components/Layout/TV/ScreenContainer.tsx +84 -5
  11. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  12. package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +38 -28
  13. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +25 -13
  14. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -50
  15. package/Components/Layout/TV/__tests__/index.test.tsx +20 -75
  16. package/Components/Layout/TV/index.tsx +14 -67
  17. package/Components/Layout/TV/index.web.tsx +33 -0
  18. package/Components/Layout/index.tsx +15 -11
  19. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  20. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +88 -112
  21. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  22. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
  23. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  24. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  25. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -1
  26. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  27. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  28. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  29. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  30. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
  31. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
  32. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  33. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  34. package/Components/MasterCell/utils/index.ts +33 -35
  35. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  36. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  37. package/Components/Navigator/StackNavigator.tsx +57 -0
  38. package/Components/Navigator/index.ts +1 -0
  39. package/Components/PlayerContainer/PlayerContainer.tsx +23 -3
  40. package/Components/PlayerImageBackground/index.tsx +34 -8
  41. package/Components/River/ComponentsMap/ComponentsMap.tsx +92 -22
  42. package/Components/River/RefreshControl.tsx +55 -1
  43. package/Components/River/TV/River.tsx +0 -23
  44. package/Components/River/__tests__/componentsMap.test.js +6 -0
  45. package/Components/RouteManager/index.tsx +2 -2
  46. package/Components/Screen/TV/hooks/index.ts +3 -0
  47. package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
  48. package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
  49. package/Components/Screen/TV/index.web.tsx +169 -0
  50. package/Components/Screen/__tests__/Screen.test.tsx +3 -0
  51. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +4 -0
  52. package/Components/Screen/index.tsx +64 -25
  53. package/Components/ScreenResolver/index.tsx +3 -1
  54. package/Components/Transitioner/Scene.tsx +24 -19
  55. package/Components/Transitioner/Transitioner.tsx +21 -28
  56. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +6 -2
  57. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  58. package/Components/VideoLive/LiveImageManager.ts +28 -1
  59. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +20 -5
  60. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +0 -5
  61. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +78 -0
  62. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +352 -0
  63. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +158 -0
  64. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +455 -0
  65. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +162 -0
  66. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  67. package/Components/VideoModal/ModalAnimation/utils.ts +301 -0
  68. package/Components/VideoModal/PlayerDetails.tsx +3 -3
  69. package/Components/VideoModal/PlayerWrapper.tsx +77 -47
  70. package/Components/VideoModal/VideoModal.tsx +23 -40
  71. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +13 -1
  72. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +157 -105
  73. package/Components/VideoModal/utils.ts +8 -5
  74. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  75. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  76. package/Components/Viewport/utils.js +1 -1
  77. package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
  78. package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
  79. package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
  80. package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
  81. package/Contexts/FocusableGroupContext/index.ts +7 -0
  82. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
  83. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  84. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  85. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  86. package/Contexts/ScreenContext/index.tsx +94 -25
  87. package/Contexts/ScreenDataContext/index.tsx +20 -0
  88. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  89. package/events/index.ts +1 -0
  90. package/package.json +5 -5
  91. package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
  92. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  93. /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
  94. /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
  95. /package/Contexts/{index.js → index.ts} +0 -0
@@ -1,38 +1,64 @@
1
1
  import React, { PropsWithChildren } from "react";
2
- import { ImageBackground } from "react-native";
2
+ import { ImageBackground, View } from "react-native";
3
3
 
4
4
  import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
5
+ import {
6
+ AnimationComponent,
7
+ ComponentAnimationType,
8
+ useModalAnimationContext,
9
+ PlayerAnimationStateEnum,
10
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
5
11
 
6
12
  type Props = PropsWithChildren<{
7
13
  entry: ZappEntry;
8
14
  style?: { [K: string]: any };
9
15
  imageStyle?: { [K: string]: any };
10
16
  imageKey?: string;
17
+ defaultImageDimensions?: { [K: string]: any };
11
18
  }>;
12
19
 
20
+ const imageSize = { width: "100%", height: "100%" };
21
+
13
22
  const PlayerImageBackgroundComponent = ({
14
23
  entry,
15
24
  children,
16
25
  style,
17
26
  imageStyle,
18
27
  imageKey,
28
+ defaultImageDimensions,
19
29
  }: Props) => {
20
30
  const source = React.useMemo(
21
31
  () => ({ uri: imageSrcFromMediaItem(entry, imageKey) }),
22
32
  [imageKey, entry]
23
33
  );
24
34
 
35
+ const { playerAnimationState } = useModalAnimationContext();
36
+
25
37
  if (!source) return <>{children}</>;
26
38
 
27
39
  return (
28
- <ImageBackground
29
- resizeMode="cover"
30
- style={style}
31
- imageStyle={imageStyle}
32
- source={source}
40
+ <View
41
+ style={
42
+ playerAnimationState === PlayerAnimationStateEnum.maximaze
43
+ ? defaultImageDimensions
44
+ : style
45
+ }
33
46
  >
34
- {children}
35
- </ImageBackground>
47
+ <AnimationComponent
48
+ style={style}
49
+ animationType={ComponentAnimationType.player}
50
+ additionalData={defaultImageDimensions}
51
+ >
52
+ <ImageBackground
53
+ resizeMode="cover"
54
+ style={imageSize}
55
+ imageStyle={imageStyle}
56
+ source={source}
57
+ >
58
+ {children}
59
+ </ImageBackground>
60
+ </AnimationComponent>
61
+ </View>
36
62
  );
37
63
  };
38
64
 
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
- import { View, FlatList, StyleSheet, Platform } from "react-native";
3
+ import { View, StyleSheet, Platform, FlatList } from "react-native";
4
4
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
5
5
  import { RiverItem } from "../RiverItem";
6
6
  import { RiverFooter } from "../RiverFooter";
@@ -21,7 +21,8 @@ import {
21
21
  import { useScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData";
22
22
  import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
23
23
  import { withComponentsMapProvider } from "@applicaster/zapp-react-native-ui-components/Decorators/ComponentsMapWrapper";
24
- import { useScreenScrollStore } from "@applicaster/zapp-react-native-utils/screenState";
24
+ import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
25
+ import { useShallow } from "zustand/react/shallow";
25
26
 
26
27
  type Props = {
27
28
  feed: ZappFeed;
@@ -41,8 +42,10 @@ const styles = StyleSheet.create({
41
42
  },
42
43
  });
43
44
 
44
- const scrollIndicatorInsets = { right: 1 };
45
+ const getNearestValue = (value, optionA, optionB) =>
46
+ Math.abs(value - optionA) < Math.abs(value - optionB) ? optionA : optionB;
45
47
 
48
+ const scrollIndicatorInsets = { right: 1 };
46
49
  const keyExtractor = R.compose(String, R.prop("id"));
47
50
 
48
51
  function ComponentsMapComponent(props: Props) {
@@ -60,6 +63,7 @@ function ComponentsMapComponent(props: Props) {
60
63
  refreshingPipesV1,
61
64
  } = props;
62
65
 
66
+ const flatListRef = React.useRef<FlatList | null>(null);
63
67
  const screenConfig = useScreenConfiguration(riverId);
64
68
  const screenData = useScreenData(riverId);
65
69
  const pullToRefreshEnabled = screenData?.rules?.pull_to_refresh_enabled;
@@ -194,26 +198,87 @@ function ComponentsMapComponent(props: Props) {
194
198
  []
195
199
  );
196
200
 
197
- const onScrollFlatList = React.useCallback(
198
- ({ nativeEvent }, isScrollingDone = false) => {
199
- const {
200
- contentOffset: { y },
201
- } = nativeEvent;
201
+ const navBarStore = useScreenContextV2()._navBarStore;
202
202
 
203
- useScreenScrollStore.setState({
204
- scrollingVelocity: isScrollingDone ? 0 : 1,
205
- scrollPosition: y,
206
- });
207
- },
208
- []
203
+ const scrollY = navBarStore(
204
+ useShallow(({ scrollYAnimated }) => scrollYAnimated)
209
205
  );
210
206
 
211
- const onMomentumScrollEnd = React.useCallback(
212
- ({ nativeEvent }) => {
213
- onScrollFlatList({ nativeEvent }, true);
214
- },
215
- [onScrollFlatList]
216
- );
207
+ const canMomentum = React.useRef(false);
208
+
209
+ const onScrollBeginDrag = React.useCallback(() => {
210
+ canMomentum.current = true;
211
+ }, []);
212
+
213
+ /* onMomentumScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
214
+ const _onMomentumScrollEnd = React.useCallback(({ nativeEvent }) => {
215
+ if (!canMomentum.current) return;
216
+ canMomentum.current = false;
217
+
218
+ const { height: headerHeight, scrollState } = navBarStore.getState();
219
+
220
+ const offsetY = Math.max(0, nativeEvent.contentOffset.y);
221
+
222
+ if (
223
+ !(scrollState === 0 || scrollState === -headerHeight) &&
224
+ flatListRef.current
225
+ ) {
226
+ const offset =
227
+ getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
228
+ -headerHeight
229
+ ? Math.round(offsetY + scrollState + headerHeight)
230
+ : Math.round(offsetY + scrollState);
231
+
232
+ flatListRef.current.scrollToOffset({
233
+ animated: true,
234
+ offset,
235
+ });
236
+ }
237
+ }, []);
238
+
239
+ /* onScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
240
+ const _onScrollEndDrag = React.useCallback(({ nativeEvent }) => {
241
+ const { height: headerHeight, scrollState } = navBarStore.getState();
242
+
243
+ const offsetY = Math.max(0, nativeEvent.contentOffset.y);
244
+
245
+ if (
246
+ !(scrollState === 0 || scrollState === -headerHeight) &&
247
+ flatListRef.current &&
248
+ nativeEvent.velocity.y === 0
249
+ ) {
250
+ const offset =
251
+ getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
252
+ -headerHeight
253
+ ? Math.round(offsetY + scrollState + headerHeight)
254
+ : Math.round(offsetY + scrollState);
255
+
256
+ flatListRef.current.scrollToOffset({
257
+ animated: true,
258
+ offset,
259
+ });
260
+ }
261
+ }, []);
262
+
263
+ const onScroll = React.useCallback((event) => {
264
+ const {
265
+ nativeEvent: {
266
+ contentOffset: { y },
267
+ layoutMeasurement: { height },
268
+ contentSize: { height: contentHeight },
269
+ },
270
+ } = event;
271
+
272
+ if (y >= 0 && y + height <= contentHeight) {
273
+ scrollY.setValue(y);
274
+ } else {
275
+ if (y <= 0) {
276
+ scrollY.setValue(0);
277
+ } else if (y + height > contentHeight) {
278
+ scrollY.setValue(contentHeight - height);
279
+ }
280
+ }
281
+ }, []);
217
282
 
218
283
  if (shouldShowLoadingError) {
219
284
  return <RiverError />;
@@ -230,6 +295,9 @@ function ComponentsMapComponent(props: Props) {
230
295
  >
231
296
  <ViewportTracker>
232
297
  <FlatList
298
+ ref={(ref) => {
299
+ flatListRef.current = ref;
300
+ }}
233
301
  // Fix for WebView rerender crashes on Android API 28+
234
302
  // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
235
303
  overScrollMode={Platform.OS === "android" ? "never" : "auto"}
@@ -249,8 +317,10 @@ function ComponentsMapComponent(props: Props) {
249
317
  }
250
318
  ListFooterComponent={renderFooter}
251
319
  refreshControl={refreshControl}
252
- onScroll={onScrollFlatList}
253
- onMomentumScrollEnd={onMomentumScrollEnd}
320
+ onScrollBeginDrag={onScrollBeginDrag}
321
+ onScroll={onScroll}
322
+ onMomentumScrollEnd={_onMomentumScrollEnd}
323
+ onScrollEndDrag={_onScrollEndDrag}
254
324
  scrollEventThrottle={16}
255
325
  {...scrollViewExtraProps}
256
326
  />
@@ -1,5 +1,9 @@
1
1
  import * as React from "react";
2
- import { RefreshControl as RNRefreshControl } from "react-native";
2
+ import {
3
+ Platform,
4
+ RefreshControl as RNRefreshControl,
5
+ StyleSheet,
6
+ } from "react-native";
3
7
  import * as R from "ramda";
4
8
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
5
9
  import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
@@ -8,10 +12,30 @@ import { useSendAnalyticsEventWithFunction } from "@applicaster/zapp-react-nativ
8
12
  import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
9
13
  import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
10
14
  import { useDispatch } from "react-redux";
15
+ import { useShallow } from "zustand/react/shallow";
16
+ import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
17
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
11
18
 
12
19
  const BRIGHTNESS_THRESHOLD = 160;
13
20
  const ABOVE_DEFAULT_COLOR = "gray";
14
21
  const BELOW_DEFAULT_COLOR = "white";
22
+ const BASE_INDICATOR_HEIGHT = 40;
23
+ const BASE_INDICATOR_OFFSET = 16;
24
+
25
+ /** Return spacing required to position indicator below the navbar bottom line (Android only) */
26
+ const getRefreshControlOffset = () => {
27
+ return Platform.OS === "ios"
28
+ ? 0
29
+ : BASE_INDICATOR_HEIGHT + BASE_INDICATOR_OFFSET;
30
+ };
31
+
32
+ const styles = StyleSheet.create({
33
+ // eslint-disable-next-line react-native/no-color-literals
34
+ control: {
35
+ zIndex: 1,
36
+ backgroundColor: "transparent",
37
+ },
38
+ });
15
39
 
16
40
  const getBrightness = (RGBcolor) => {
17
41
  const RGBvalues = RGBcolor.substring(
@@ -91,6 +115,34 @@ export const usePullToRefresh = (
91
115
  };
92
116
  };
93
117
 
118
+ /** Returns the offset for the progress view of the RefreshControl component
119
+ * based on navbar content position */
120
+ export const useGetProgressViewOffset = () => {
121
+ const { _navBarStore } = useScreenContextV2();
122
+ const { top } = useSafeAreaInsets();
123
+
124
+ const offset = getRefreshControlOffset();
125
+
126
+ const { height, contentPosition } = _navBarStore(
127
+ useShallow(({ contentPosition, height }) => ({ contentPosition, height }))
128
+ );
129
+
130
+ if (height === 0) {
131
+ return offset;
132
+ }
133
+
134
+ switch (contentPosition) {
135
+ case "on_bottom_status_bar":
136
+ // navbar height
137
+ return offset + height;
138
+ case "on_top_screen":
139
+ // navbar height + status bar height
140
+ return offset + height + top;
141
+ default:
142
+ return offset;
143
+ }
144
+ };
145
+
94
146
  export function RefreshControl(props: {
95
147
  pullToRefreshPipesV1RefreshingStateUpdater?: (refreshing: boolean) => void;
96
148
  refreshingPipesV1?: boolean;
@@ -211,6 +263,7 @@ export function RefreshControl(props: {
211
263
  refreshing={refreshing}
212
264
  onRefresh={onRefreshHandler}
213
265
  colors={[preparedIndicatorColor]}
266
+ progressViewOffset={useGetProgressViewOffset()}
214
267
  progressBackgroundColor={preparedIndicatorBackgroundColor}
215
268
  // @ts-ignore in RN68 this `size` became string
216
269
  size={indicatorSize}
@@ -222,6 +275,7 @@ export function RefreshControl(props: {
222
275
  : null
223
276
  }
224
277
  titleColor={preparedTitleUnderIndicatorColor}
278
+ style={styles.control}
225
279
  />
226
280
  );
227
281
  }
@@ -7,14 +7,10 @@ import * as R from "ramda";
7
7
  import {
8
8
  useFeedLoader,
9
9
  useLayoutVersion,
10
- useNavigationPluginData,
11
10
  } from "@applicaster/zapp-react-native-utils/reactHooks";
12
11
  import { GeneralContentScreen } from "../../GeneralContentScreen";
13
12
  import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
14
13
  import { utilsLogger } from "@applicaster/zapp-react-native-utils/logger";
15
- import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils";
16
- import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
17
- import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
18
14
  import {
19
15
  useNavbarState,
20
16
  useSetNavbarState,
@@ -51,8 +47,6 @@ export const River = (props: Props) => {
51
47
  const { setTitle: setScreenTitle, setSummary: setScreenSummary } =
52
48
  useSetNavbarState();
53
49
 
54
- const navigationPluginData = useNavigationPluginData();
55
-
56
50
  const rivers = useRivers();
57
51
  const isV2 = useLayoutVersion({ isV2: true });
58
52
 
@@ -99,23 +93,6 @@ export const River = (props: Props) => {
99
93
  }
100
94
  }, [feedData, screenData, screenTitle, screenSummary]);
101
95
 
102
- React.useEffect(() => {
103
- if (
104
- isWeb() &&
105
- toBooleanWithDefaultFalse(
106
- navigationPluginData?.rules?.focus_on_content
107
- ) &&
108
- river &&
109
- river.type !== "general_content"
110
- ) {
111
- const groupId = R.pathOr("river", ["groupId"], componentsMapExtraProps);
112
-
113
- focusManager.setupInitialFocus(groupId, true);
114
- focusManager.setInitialFocus();
115
- focusManager.enableAutomaticFocus();
116
- }
117
- }, [river, componentsMapExtraProps]);
118
-
119
96
  if (!river) {
120
97
  utilsLogger.error({
121
98
  message: `River ${screenData.id} not found!`,
@@ -6,6 +6,12 @@ import configureStore from "redux-mock-store";
6
6
 
7
7
  import * as themeUtils from "@applicaster/zapp-react-native-utils/theme";
8
8
 
9
+ // silences the errors related to React-native-gesture-handler in tests
10
+ jest.mock(
11
+ "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation",
12
+ () => () => null
13
+ );
14
+
9
15
  jest.mock("react-native-safe-area-context", () => ({
10
16
  useSafeAreaInsets: () => ({ top: 50 }),
11
17
  }));
@@ -14,7 +14,7 @@ type Props = { pathname: string; screenData: Record<string, any> };
14
14
  * @param {Props}: see props above
15
15
  */
16
16
  export function RouteManager(props: Props) {
17
- const { pathname, screenData: _screenData } = useRoute();
17
+ const { pathname, screenData } = useRoute();
18
18
  const pathAttributes = getPathAttributes({ pathname });
19
19
 
20
20
  const { screenType, screenId } = R.last(pathAttributes);
@@ -25,7 +25,7 @@ export function RouteManager(props: Props) {
25
25
  screenType={screenType}
26
26
  screenId={screenId}
27
27
  {...props}
28
- screenData={props.screenData}
28
+ screenData={screenData}
29
29
  />
30
30
  </TestId>
31
31
  );
@@ -0,0 +1,3 @@
1
+ export { useInitialFocus } from "./useInitialFocus";
2
+
3
+ export { useRestoreFocus } from "./useRestoreFocus";
@@ -0,0 +1,70 @@
1
+ import * as React from "react";
2
+ import {
3
+ useNavigationPluginData,
4
+ useIsNavBarVisible,
5
+ useRoute,
6
+ } from "@applicaster/zapp-react-native-utils/reactHooks";
7
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
8
+ import {
9
+ setFocusOnContent,
10
+ setFocusOnMenu,
11
+ } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux";
12
+
13
+ type Return =
14
+ | {
15
+ onContent: true;
16
+ onMenu: false;
17
+ }
18
+ | {
19
+ onContent: false;
20
+ onMenu: true;
21
+ };
22
+
23
+ const getInitialFocus = (focusOnContent, isNavBarVisible): Return => {
24
+ if (focusOnContent) {
25
+ return {
26
+ onContent: true,
27
+ onMenu: false,
28
+ };
29
+ }
30
+
31
+ if (!focusOnContent && isNavBarVisible) {
32
+ // put focus on navbar
33
+ return {
34
+ onContent: false,
35
+ onMenu: true,
36
+ };
37
+ }
38
+
39
+ // we don't have navbar, put focus on content anyway
40
+ return {
41
+ onContent: true,
42
+ onMenu: false,
43
+ };
44
+ };
45
+
46
+ export const useInitialFocus = (): void => {
47
+ const navigationPluginData = useNavigationPluginData();
48
+
49
+ const focusOnContent = toBooleanWithDefaultFalse(
50
+ navigationPluginData?.rules?.focus_on_content
51
+ );
52
+
53
+ const isNavBarVisible = useIsNavBarVisible();
54
+
55
+ const { pathname: currentRoute } = useRoute();
56
+
57
+ React.useEffect(() => {
58
+ const initialFocus = getInitialFocus(focusOnContent, isNavBarVisible);
59
+
60
+ if (initialFocus.onContent) {
61
+ setFocusOnContent(currentRoute);
62
+
63
+ return;
64
+ }
65
+
66
+ if (initialFocus.onMenu) {
67
+ setFocusOnMenu(currentRoute);
68
+ }
69
+ }, []);
70
+ };
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+ import { isNil } from "ramda";
3
+ import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
4
+ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
5
+ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
6
+
7
+ export const useRestoreFocus = (): void => {
8
+ const isScreenActive = useIsScreenActive();
9
+ const previousIsScreenActive = usePrevious(isScreenActive);
10
+
11
+ React.useEffect(() => {
12
+ if (isNil(previousIsScreenActive)) {
13
+ // it's initial mounting of the screen, do nothing here
14
+ return;
15
+ }
16
+
17
+ if (!previousIsScreenActive && isScreenActive) {
18
+ // restore focus when we go back from next screen
19
+ focusManager.setInitialFocus();
20
+ }
21
+ }, [previousIsScreenActive, isScreenActive]);
22
+ };
@@ -0,0 +1,169 @@
1
+ import * as React from "react";
2
+ import { last } from "ramda";
3
+ import { View, StyleSheet } from "react-native";
4
+ import {
5
+ getNavigationProps,
6
+ getPathAttributes,
7
+ resolveNavigationPlugin,
8
+ } from "@applicaster/zapp-react-native-utils/navigationUtils";
9
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
10
+ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
11
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
12
+ import {
13
+ useNavbarState,
14
+ useScreenBackgroundColor,
15
+ } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
16
+ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
17
+
18
+ import { NavBarContainer } from "../../Layout/TV/NavBarContainer";
19
+ import { ScreenResolver } from "../../ScreenResolver";
20
+ import { useInitialFocus, useRestoreFocus } from "./hooks";
21
+ import { isPlayerPlugin } from "@applicaster/zapp-react-native-utils/pluginUtils";
22
+
23
+ const styles = StyleSheet.create({
24
+ container: {
25
+ position: "absolute",
26
+ top: 0,
27
+ left: 0,
28
+ right: 0,
29
+ bottom: 0,
30
+ },
31
+ });
32
+
33
+ type Components = {
34
+ NavBar: React.ComponentType<any>;
35
+ Background: React.ComponentType<any>;
36
+ };
37
+
38
+ type Props = {
39
+ route: string;
40
+ Components: Components;
41
+ };
42
+
43
+ const getActiveRiver = ({
44
+ screenId,
45
+ screenType,
46
+ routeState,
47
+ rivers,
48
+ fallbackActiveRiver,
49
+ }) => {
50
+ if (rivers[screenId]) {
51
+ if (screenType === "hooks") {
52
+ const navigations = routeState?.state?.entry?.payload?.navigations;
53
+
54
+ if (navigations) {
55
+ return routeState.state.entry.payload;
56
+ }
57
+ }
58
+
59
+ return routeState?.state?.screen;
60
+ }
61
+
62
+ // screenId is not presented into rivers, use fallback instead
63
+ return fallbackActiveRiver;
64
+ };
65
+
66
+ const getNavigations = ({
67
+ screenId,
68
+ screenType,
69
+ routeState,
70
+ rivers,
71
+ fallbackNavigations,
72
+ }) => {
73
+ if (rivers[screenId]) {
74
+ // river presents
75
+ if (screenType === "hooks") {
76
+ const navigations = routeState?.state?.entry?.payload?.navigations;
77
+
78
+ if (navigations) {
79
+ return navigations;
80
+ }
81
+ }
82
+
83
+ return routeState?.state?.screen?.navigations;
84
+ }
85
+
86
+ // screenId is not presented into rivers, use fallback instead
87
+ return fallbackNavigations;
88
+ };
89
+
90
+ export const Screen = ({ route, Components }: Props) => {
91
+ if (isNilOrEmpty(route)) {
92
+ throw Error("Required props: route is missing");
93
+ }
94
+
95
+ const navigator = useNavigation();
96
+ const { plugins = [], rivers = [] } = usePickFromState(["plugins", "rivers"]);
97
+
98
+ const pathAttributes = getPathAttributes({ pathname: route });
99
+ const routeState = navigator.getStackForPathname(route);
100
+
101
+ const { screenType, screenId } = last(pathAttributes);
102
+
103
+ const backgroundColor = useScreenBackgroundColor(screenId);
104
+
105
+ useInitialFocus();
106
+ useRestoreFocus();
107
+
108
+ const activeRiver = getActiveRiver({
109
+ screenId,
110
+ screenType,
111
+ routeState,
112
+ rivers,
113
+ fallbackActiveRiver: navigator.activeRiver,
114
+ });
115
+
116
+ const navigationProps = getNavigationProps({
117
+ navigator: {
118
+ // @ts-ignore
119
+ activeRiver,
120
+ // @ts-ignore
121
+ screenTitle: routeState.state.entry?.title || routeState.screen?.name,
122
+ },
123
+ category: "menu",
124
+ });
125
+
126
+ const navigations = getNavigations({
127
+ screenId,
128
+ screenType,
129
+ routeState,
130
+ rivers,
131
+ fallbackNavigations: navigator.activeRiver.navigations,
132
+ });
133
+
134
+ const menuPlugin = resolveNavigationPlugin({
135
+ category: "menu",
136
+ navigations,
137
+ plugins,
138
+ }) as QuickBrickPlugin;
139
+
140
+ const NavBar = (Components.NavBar ||
141
+ menuPlugin.module) as React.ComponentType<any>;
142
+
143
+ const isPlayer = isPlayerPlugin(routeState);
144
+
145
+ const { visible: isNavBarVisible } = useNavbarState();
146
+
147
+ const screenData =
148
+ screenType === "hooks" || isPlayer
149
+ ? routeState?.state?.entry
150
+ : routeState?.state?.screen;
151
+
152
+ return (
153
+ <View style={[styles.container, { backgroundColor }]}>
154
+ <NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
155
+ <NavBar
156
+ isVisible={isNavBarVisible}
157
+ menuPlugin={menuPlugin}
158
+ navigationProps={navigationProps}
159
+ />
160
+ </NavBarContainer>
161
+ <ScreenResolver
162
+ screenType={screenType}
163
+ screenId={screenId}
164
+ screenData={screenData}
165
+ groupId={route}
166
+ />
167
+ </View>
168
+ );
169
+ };
@@ -10,6 +10,8 @@ const mock_navBarVisibleFlag = true;
10
10
 
11
11
  const mockIsNavBarVisible = jest.fn(() => mock_navBarVisibleFlag);
12
12
 
13
+ const mockIsOrientationCompatible = jest.fn(() => true);
14
+
13
15
  jest.mock("react-native-safe-area-context", () => ({
14
16
  useSafeAreaInsets: () => ({ top: 44 }),
15
17
  }));
@@ -21,6 +23,7 @@ jest.mock("../../RouteManager", () => ({
21
23
  jest.mock(
22
24
  "@applicaster/zapp-react-native-utils/appUtils/orientationHelper",
23
25
  () => ({
26
+ isOrientationCompatible: mockIsOrientationCompatible,
24
27
  allowedOrientationsForScreen: jest.fn(),
25
28
  getOrientation: jest.fn(),
26
29
  getScreenOrientation: jest.fn(),