@applicaster/zapp-react-native-ui-components 12.0.0-alpha.7431051513 → 12.0.0-alpha.7528906949

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 (85) hide show
  1. package/Components/BaseFocusable/index.tsx +3 -3
  2. package/Components/Cell/Cell.tsx +5 -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/ScreenLayoutContextProvider.tsx +2 -2
  11. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -50
  12. package/Components/Layout/TV/__tests__/index.test.tsx +19 -75
  13. package/Components/Layout/TV/index.tsx +12 -11
  14. package/Components/Layout/TV/index.web.tsx +33 -0
  15. package/Components/Layout/index.tsx +15 -11
  16. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  17. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +51 -16
  18. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  19. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
  20. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  21. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  22. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  23. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  24. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  25. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  26. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
  27. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
  28. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  29. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  30. package/Components/MasterCell/utils/index.ts +17 -30
  31. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  32. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  33. package/Components/Navigator/StackNavigator.tsx +54 -0
  34. package/Components/Navigator/index.ts +1 -0
  35. package/Components/PlayerContainer/PlayerContainer.tsx +17 -6
  36. package/Components/River/ComponentsMap/ComponentsMap.tsx +92 -22
  37. package/Components/River/RefreshControl.tsx +55 -1
  38. package/Components/River/TV/River.tsx +0 -23
  39. package/Components/RouteManager/index.tsx +2 -2
  40. package/Components/Screen/TV/hooks/index.ts +3 -0
  41. package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
  42. package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
  43. package/Components/Screen/TV/index.web.tsx +186 -0
  44. package/Components/Screen/__tests__/Screen.test.tsx +3 -0
  45. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +4 -0
  46. package/Components/Screen/index.tsx +64 -25
  47. package/Components/ScreenResolver/index.tsx +3 -1
  48. package/Components/Transitioner/Scene.tsx +24 -19
  49. package/Components/Transitioner/Transitioner.tsx +21 -28
  50. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +6 -2
  51. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  52. package/Components/VideoLive/LiveImageManager.ts +28 -1
  53. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +0 -5
  54. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +59 -42
  55. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +46 -32
  56. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +98 -81
  57. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +23 -4
  58. package/Components/VideoModal/ModalAnimation/utils.ts +6 -4
  59. package/Components/VideoModal/PlayerDetails.tsx +3 -3
  60. package/Components/VideoModal/PlayerWrapper.tsx +67 -60
  61. package/Components/VideoModal/VideoModal.tsx +17 -17
  62. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +7 -0
  63. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +32 -12
  64. package/Components/VideoModal/utils.ts +5 -2
  65. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  66. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  67. package/Components/Viewport/utils.js +1 -1
  68. package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
  69. package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
  70. package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
  71. package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
  72. package/Contexts/FocusableGroupContext/index.ts +7 -0
  73. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
  74. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  75. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  76. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  77. package/Contexts/ScreenContext/index.tsx +94 -25
  78. package/Contexts/ScreenDataContext/index.tsx +20 -0
  79. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  80. package/events/index.ts +1 -0
  81. package/package.json +5 -5
  82. package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
  83. /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
  84. /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
  85. /package/Contexts/{index.js → index.ts} +0 -0
@@ -54,7 +54,10 @@ import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
54
54
  import { usePlayNextOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayNextOverlay";
55
55
  import { PlayNextState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/OverlaysObserver";
56
56
 
57
- import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
57
+ import {
58
+ useModalAnimationContext,
59
+ PlayerAnimationStateEnum,
60
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
58
61
 
59
62
  type Props = {
60
63
  Player: React.ComponentType<any>;
@@ -65,6 +68,7 @@ type Props = {
65
68
  mode?: VideoModalMode;
66
69
  loadPipesData: (url: string, options: {}) => void;
67
70
  disableCastAction?: boolean;
71
+ groupId?: string;
68
72
  };
69
73
 
70
74
  type ScreenStyles = ZappRiver["styles"] & {
@@ -197,6 +201,7 @@ const PlayerContainerComponent = (props: Props) => {
197
201
  style: videoStyle,
198
202
  mode = VideoModalMode.MAXIMIZED,
199
203
  disableCastAction = false,
204
+ groupId,
200
205
  } = props;
201
206
 
202
207
  // Hooks
@@ -231,6 +236,9 @@ const PlayerContainerComponent = (props: Props) => {
231
236
  const screenData = useTargetScreenData(item);
232
237
  const { setVisible: showNavBar } = useSetNavbarState();
233
238
 
239
+ const { isActiveGesture, startComponentsAnimation, setPlayerAnimationState } =
240
+ useModalAnimationContext();
241
+
234
242
  const playerEvent = (event, ...args) => {
235
243
  playerManager.invokeHandler(event, ...args);
236
244
  };
@@ -243,8 +251,12 @@ const PlayerContainerComponent = (props: Props) => {
243
251
  return;
244
252
  }
245
253
 
246
- navigator.goBack();
247
254
  showNavBar(true);
255
+
256
+ // Hack to close player screen on mobile only (tv handles that in InteractionManager)
257
+ if (!isTV()) {
258
+ navigator.goBack();
259
+ }
248
260
  }, [isModal, navigator.goBack, showNavBar]);
249
261
 
250
262
  const playEntry = (entry) => navigator.replaceTop(entry, { mode });
@@ -448,7 +460,7 @@ const PlayerContainerComponent = (props: Props) => {
448
460
  if (disableMiniPlayer) {
449
461
  navigator.closeVideoModal();
450
462
  } else {
451
- navigator.minimiseVideoModal();
463
+ setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
452
464
  }
453
465
  }
454
466
 
@@ -600,9 +612,6 @@ const PlayerContainerComponent = (props: Props) => {
600
612
  pluginConfiguration,
601
613
  };
602
614
 
603
- const { isActiveGesture, startComponentsAnimation } =
604
- useModalAnimationContext();
605
-
606
615
  return (
607
616
  <PlayerStateContext.Provider value={value}>
608
617
  <PlayerContainerContextProvider
@@ -626,6 +635,7 @@ const PlayerContainerComponent = (props: Props) => {
626
635
  style={styles.focusableGroup}
627
636
  preferredFocus
628
637
  shouldUsePreferredFocus
638
+ groupId={groupId}
629
639
  >
630
640
  {/* Video player and components */}
631
641
  <View
@@ -668,6 +678,7 @@ const PlayerContainerComponent = (props: Props) => {
668
678
  setNextVideoPreloadThresholdPercentage={
669
679
  setNextVideoPreloadThresholdPercentage
670
680
  }
681
+ startComponentsAnimation={startComponentsAnimation}
671
682
  >
672
683
  {renderApplePlayer(applePlayerProps)}
673
684
  </Player>
@@ -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!`,
@@ -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,186 @@
1
+ import * as React from "react";
2
+ import { last, pathOr } 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 { useScreenBackgroundColor } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
13
+ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
14
+
15
+ import { NavBarContainer } from "../../Layout/TV/NavBarContainer";
16
+ import { ScreenResolver } from "../../ScreenResolver";
17
+ import { useInitialFocus, useRestoreFocus } from "./hooks";
18
+
19
+ const styles = StyleSheet.create({
20
+ container: {
21
+ position: "absolute",
22
+ top: 0,
23
+ left: 0,
24
+ right: 0,
25
+ bottom: 0,
26
+ },
27
+ });
28
+
29
+ type Components = {
30
+ NavBar: React.ComponentType<any>;
31
+ Background: React.ComponentType<any>;
32
+ };
33
+
34
+ type Props = {
35
+ route: string;
36
+ Components: Components;
37
+ };
38
+
39
+ const getPluginType = pathOr("unknown_plugin_type", [
40
+ "state",
41
+ "screen",
42
+ "plugin_type",
43
+ ]);
44
+
45
+ export const isPresentFullScreen = pathOr(false, [
46
+ "state",
47
+ "screen",
48
+ "module",
49
+ "presentFullScreen",
50
+ ]);
51
+
52
+ const isPlayerPlugin = (routeState): boolean =>
53
+ getPluginType(routeState) === "player";
54
+
55
+ const isLoginPlugin = (routeState): boolean =>
56
+ getPluginType(routeState) === "login";
57
+
58
+ const getActiveRiver = ({
59
+ screenId,
60
+ screenType,
61
+ routeState,
62
+ rivers,
63
+ fallbackActiveRiver,
64
+ }) => {
65
+ if (rivers[screenId]) {
66
+ if (screenType === "hooks") {
67
+ const navigations = routeState?.state?.entry?.payload?.navigations;
68
+
69
+ if (navigations) {
70
+ return routeState.state.entry.payload;
71
+ }
72
+ }
73
+
74
+ return routeState?.state?.screen;
75
+ }
76
+
77
+ // screenId is not presented into rivers, use fallback instead
78
+ return fallbackActiveRiver;
79
+ };
80
+
81
+ const getNavigations = ({
82
+ screenId,
83
+ screenType,
84
+ routeState,
85
+ rivers,
86
+ fallbackNavigations,
87
+ }) => {
88
+ if (rivers[screenId]) {
89
+ // river presents
90
+ if (screenType === "hooks") {
91
+ const navigations = routeState?.state?.entry?.payload?.navigations;
92
+
93
+ if (navigations) {
94
+ return navigations;
95
+ }
96
+ }
97
+
98
+ return routeState?.state?.screen?.navigations;
99
+ }
100
+
101
+ // screenId is not presented into rivers, use fallback instead
102
+ return fallbackNavigations;
103
+ };
104
+
105
+ export const Screen = ({ route, Components }: Props) => {
106
+ if (isNilOrEmpty(route)) {
107
+ throw Error("Required props: route is missing");
108
+ }
109
+
110
+ const navigator = useNavigation();
111
+ const { plugins = [], rivers = [] } = usePickFromState(["plugins", "rivers"]);
112
+
113
+ const pathAttributes = getPathAttributes({ pathname: route });
114
+ const routeState = navigator.getStackForPathname(route);
115
+
116
+ const { screenType, screenId } = last(pathAttributes);
117
+
118
+ const backgroundColor = useScreenBackgroundColor(screenId);
119
+
120
+ useInitialFocus();
121
+ useRestoreFocus();
122
+
123
+ const activeRiver = getActiveRiver({
124
+ screenId,
125
+ screenType,
126
+ routeState,
127
+ rivers,
128
+ fallbackActiveRiver: navigator.activeRiver,
129
+ });
130
+
131
+ const navigationProps = getNavigationProps({
132
+ navigator: {
133
+ // @ts-ignore
134
+ activeRiver,
135
+ // @ts-ignore
136
+ screenTitle: routeState.state.entry?.title || routeState.screen?.name,
137
+ },
138
+ category: "menu",
139
+ });
140
+
141
+ const navigations = getNavigations({
142
+ screenId,
143
+ screenType,
144
+ routeState,
145
+ rivers,
146
+ fallbackNavigations: navigator.activeRiver.navigations,
147
+ });
148
+
149
+ const menuPlugin = resolveNavigationPlugin({
150
+ category: "menu",
151
+ navigations,
152
+ plugins,
153
+ }) as QuickBrickPlugin;
154
+
155
+ const NavBar = (Components.NavBar ||
156
+ menuPlugin.module) as React.ComponentType<any>;
157
+
158
+ const isPlayer = isPlayerPlugin(routeState);
159
+ const isLogin = isLoginPlugin(routeState);
160
+ const isFullScreen = isPresentFullScreen(routeState);
161
+
162
+ const isNavBarVisible = !isPlayer && !isLogin && !isFullScreen;
163
+
164
+ const screenData =
165
+ screenType === "hooks" || isPlayer
166
+ ? routeState?.state?.entry
167
+ : routeState?.state?.screen;
168
+
169
+ return (
170
+ <View style={[styles.container, { backgroundColor }]}>
171
+ <NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
172
+ <NavBar
173
+ isVisible={isNavBarVisible}
174
+ menuPlugin={menuPlugin}
175
+ navigationProps={navigationProps}
176
+ />
177
+ </NavBarContainer>
178
+ <ScreenResolver
179
+ screenType={screenType}
180
+ screenId={screenId}
181
+ screenData={screenData}
182
+ groupId={route}
183
+ />
184
+ </View>
185
+ );
186
+ };