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

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 (80) hide show
  1. package/Components/BaseFocusable/index.tsx +1 -1
  2. package/Components/Cell/Cell.tsx +5 -5
  3. package/Components/Cell/CellWithFocusable.tsx +17 -5
  4. package/Components/FocusableGroup/index.tsx +1 -0
  5. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  6. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  7. package/Components/HandlePlayable/HandlePlayable.tsx +3 -0
  8. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  9. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -50
  10. package/Components/Layout/TV/__tests__/index.test.tsx +19 -75
  11. package/Components/Layout/TV/index.tsx +12 -11
  12. package/Components/Layout/TV/index.web.tsx +33 -0
  13. package/Components/Layout/index.tsx +15 -11
  14. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  15. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +88 -112
  16. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  17. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  18. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  19. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -1
  20. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  21. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  22. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  23. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  24. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +4 -1
  25. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
  26. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  27. package/Components/MasterCell/utils/index.ts +33 -35
  28. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  29. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  30. package/Components/Navigator/StackNavigator.tsx +48 -0
  31. package/Components/Navigator/index.ts +1 -0
  32. package/Components/PlayerContainer/PlayerContainer.tsx +23 -3
  33. package/Components/PlayerImageBackground/index.tsx +34 -8
  34. package/Components/River/ComponentsMap/ComponentsMap.tsx +92 -22
  35. package/Components/River/RefreshControl.tsx +55 -1
  36. package/Components/River/TV/River.tsx +0 -23
  37. package/Components/River/__tests__/componentsMap.test.js +6 -0
  38. package/Components/RouteManager/index.tsx +2 -2
  39. package/Components/Screen/TV/hooks/index.ts +3 -0
  40. package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
  41. package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
  42. package/Components/Screen/TV/index.web.tsx +179 -0
  43. package/Components/Screen/__tests__/Screen.test.tsx +3 -0
  44. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +4 -0
  45. package/Components/Screen/index.tsx +64 -25
  46. package/Components/ScreenResolver/index.tsx +3 -1
  47. package/Components/Transitioner/Scene.tsx +24 -19
  48. package/Components/Transitioner/Transitioner.tsx +21 -28
  49. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +6 -2
  50. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  51. package/Components/VideoLive/LiveImageManager.ts +18 -1
  52. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +20 -5
  53. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +0 -5
  54. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +78 -0
  55. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +352 -0
  56. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +158 -0
  57. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +461 -0
  58. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +154 -0
  59. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  60. package/Components/VideoModal/ModalAnimation/utils.ts +299 -0
  61. package/Components/VideoModal/PlayerDetails.tsx +3 -3
  62. package/Components/VideoModal/PlayerWrapper.tsx +77 -47
  63. package/Components/VideoModal/VideoModal.tsx +23 -40
  64. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +13 -1
  65. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +151 -119
  66. package/Components/VideoModal/utils.ts +8 -5
  67. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  68. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  69. package/Components/Viewport/utils.js +1 -1
  70. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  71. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  72. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  73. package/Contexts/ScreenContext/index.tsx +94 -25
  74. package/Contexts/ScreenDataContext/index.tsx +20 -0
  75. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  76. package/events/index.ts +1 -0
  77. package/package.json +5 -5
  78. package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
  79. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  80. /package/Contexts/{index.js → index.ts} +0 -0
@@ -0,0 +1,48 @@
1
+ import * as React from "react";
2
+ import { isEmpty } from "ramda";
3
+
4
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
+ import { getPathAttributes } from "@applicaster/zapp-react-native-utils/navigationUtils";
6
+ import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
7
+ import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
8
+ import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
9
+
10
+ import { Screen } from "../Screen/TV/index.web";
11
+
12
+ type Components = {
13
+ NavBar: React.ComponentType<any>;
14
+ Background: React.ComponentType<any>;
15
+ };
16
+
17
+ type Props = {
18
+ Components: Components;
19
+ };
20
+
21
+ export const StackNavigator = ({ Components }: Props) => {
22
+ const { mainStack, currentRoute } = useNavigation();
23
+
24
+ const renderScreen = ({ route, state }) => {
25
+ const pathAttributes = getPathAttributes({ pathname: route });
26
+
27
+ if (isEmpty(pathAttributes)) {
28
+ return null;
29
+ }
30
+
31
+ return (
32
+ <FocusableGroup
33
+ id={route}
34
+ preferredFocus={route === currentRoute}
35
+ excludeFromFocusSearching
36
+ key={route}
37
+ >
38
+ <ScreenDataContext.Provider value={state}>
39
+ <PathnameContext.Provider value={route}>
40
+ <Screen route={route} Components={Components} />
41
+ </PathnameContext.Provider>
42
+ </ScreenDataContext.Provider>
43
+ </FocusableGroup>
44
+ );
45
+ };
46
+
47
+ return <>{mainStack.map(renderScreen)}</>;
48
+ };
@@ -0,0 +1 @@
1
+ export { StackNavigator } from "./StackNavigator";
@@ -54,6 +54,11 @@ 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 {
58
+ useModalAnimationContext,
59
+ PlayerAnimationStateEnum,
60
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
61
+
57
62
  type Props = {
58
63
  Player: React.ComponentType<any>;
59
64
  PlayerLoadingView?: React.ComponentType<any>; // 👀 we are not receiving this prop
@@ -63,6 +68,7 @@ type Props = {
63
68
  mode?: VideoModalMode;
64
69
  loadPipesData: (url: string, options: {}) => void;
65
70
  disableCastAction?: boolean;
71
+ groupId?: string;
66
72
  };
67
73
 
68
74
  type ScreenStyles = ZappRiver["styles"] & {
@@ -195,6 +201,7 @@ const PlayerContainerComponent = (props: Props) => {
195
201
  style: videoStyle,
196
202
  mode = VideoModalMode.MAXIMIZED,
197
203
  disableCastAction = false,
204
+ groupId,
198
205
  } = props;
199
206
 
200
207
  // Hooks
@@ -229,6 +236,9 @@ const PlayerContainerComponent = (props: Props) => {
229
236
  const screenData = useTargetScreenData(item);
230
237
  const { setVisible: showNavBar } = useSetNavbarState();
231
238
 
239
+ const { isActiveGesture, startComponentsAnimation, setPlayerAnimationState } =
240
+ useModalAnimationContext();
241
+
232
242
  const playerEvent = (event, ...args) => {
233
243
  playerManager.invokeHandler(event, ...args);
234
244
  };
@@ -241,8 +251,12 @@ const PlayerContainerComponent = (props: Props) => {
241
251
  return;
242
252
  }
243
253
 
244
- navigator.goBack();
245
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
+ }
246
260
  }, [isModal, navigator.goBack, showNavBar]);
247
261
 
248
262
  const playEntry = (entry) => navigator.replaceTop(entry, { mode });
@@ -446,7 +460,7 @@ const PlayerContainerComponent = (props: Props) => {
446
460
  if (disableMiniPlayer) {
447
461
  navigator.closeVideoModal();
448
462
  } else {
449
- navigator.minimiseVideoModal();
463
+ setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
450
464
  }
451
465
  }
452
466
 
@@ -621,6 +635,7 @@ const PlayerContainerComponent = (props: Props) => {
621
635
  style={styles.focusableGroup}
622
636
  preferredFocus
623
637
  shouldUsePreferredFocus
638
+ groupId={groupId}
624
639
  >
625
640
  {/* Video player and components */}
626
641
  <View
@@ -641,7 +656,11 @@ const PlayerContainerComponent = (props: Props) => {
641
656
  autoplay={true}
642
657
  controls={false}
643
658
  disableCastAction={disableCastAction}
644
- docked={navigator.isVideoModalDocked()}
659
+ docked={
660
+ navigator.isVideoModalDocked() &&
661
+ !startComponentsAnimation &&
662
+ !isActiveGesture
663
+ }
645
664
  entry={item}
646
665
  fullscreen={mode === VideoModalMode.FULLSCREEN}
647
666
  inline={inline}
@@ -659,6 +678,7 @@ const PlayerContainerComponent = (props: Props) => {
659
678
  setNextVideoPreloadThresholdPercentage={
660
679
  setNextVideoPreloadThresholdPercentage
661
680
  }
681
+ startComponentsAnimation={startComponentsAnimation}
662
682
  >
663
683
  {renderApplePlayer(applePlayerProps)}
664
684
  </Player>
@@ -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
+ };