@applicaster/zapp-react-native-ui-components 12.0.0-alpha.5135101838 → 12.0.0-alpha.5208030571

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 (31) hide show
  1. package/Components/BaseFocusable/index.tsx +1 -1
  2. package/Components/Cell/Cell.tsx +5 -1
  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/index.tsx +124 -25
  10. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  11. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  12. package/Components/PlayerContainer/PlayerContainer.tsx +11 -6
  13. package/Components/River/ComponentsMap/ComponentsMap.tsx +85 -22
  14. package/Components/River/TV/River.tsx +0 -1
  15. package/Components/Screen/TV/index.web.tsx +151 -0
  16. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +4 -0
  17. package/Components/Screen/index.tsx +35 -24
  18. package/Components/ScreenResolver/index.tsx +3 -1
  19. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +0 -5
  20. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +11 -4
  21. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +9 -2
  22. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +1 -1
  23. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  24. package/Components/Viewport/utils.js +1 -1
  25. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  26. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  27. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  28. package/Contexts/ScreenContext/index.tsx +94 -25
  29. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  30. package/package.json +5 -5
  31. /package/Contexts/{index.js → index.ts} +0 -0
@@ -73,7 +73,7 @@ export class BaseFocusable<
73
73
  getId(props?: { id: string; groupId: string }) {
74
74
  const { id, groupId = null } = props || this.props;
75
75
 
76
- return groupId ? `${groupId}-${id}` : id;
76
+ return groupId ? `groupId:${groupId}___${id}` : id;
77
77
  }
78
78
 
79
79
  getRect() {
@@ -207,7 +207,11 @@ export class CellComponent extends React.Component<Props> {
207
207
 
208
208
  const { id } = item;
209
209
 
210
- const focusableId = R.join("-", [component?.id, id, index]);
210
+ const focusableId = R.join("-", [
211
+ `componentId:${component?.id}`,
212
+ `id:${id}`,
213
+ `index:${index}`,
214
+ ]);
211
215
 
212
216
  const handleFocus = (focusable, mouse) => {
213
217
  const focusFn = onFocus || noop;
@@ -6,12 +6,20 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
6
  import { isAppleTV } from "../../Helpers/Platform";
7
7
  import { useCellState } from "../MasterCell/utils";
8
8
 
9
- const useCellFocusedState = (groupId: string, id: string) => {
9
+ const useCellFocusedState = (
10
+ skipFocusManagerRegistration: boolean,
11
+ groupId: string,
12
+ id: string
13
+ ) => {
10
14
  const [currentCellFocused, setCurrentCellFocused] = React.useState(false);
11
15
 
12
16
  React.useEffect(() => {
13
- const isGroupItemFocused = () =>
14
- setCurrentCellFocused(focusManager.isGroupItemFocused(groupId, id));
17
+ const isGroupItemFocused = () => {
18
+ if (!skipFocusManagerRegistration) {
19
+ const isFocused = focusManager.isGroupItemFocused(groupId, id);
20
+ setCurrentCellFocused(isFocused);
21
+ }
22
+ };
15
23
 
16
24
  const handler = () => {
17
25
  // tvOS hack for properly checking focus
@@ -29,7 +37,7 @@ const useCellFocusedState = (groupId: string, id: string) => {
29
37
  return () => {
30
38
  focusManager.removeHandler(FOCUS_EVENTS.FOCUS, handler);
31
39
  };
32
- }, [groupId]);
40
+ }, [groupId, skipFocusManagerRegistration]);
33
41
 
34
42
  return currentCellFocused;
35
43
  };
@@ -65,7 +73,11 @@ export function CellWithFocusable(props: Props) {
65
73
  selected,
66
74
  } = props;
67
75
 
68
- const isFocused = useCellFocusedState(groupId, id);
76
+ const isFocused = useCellFocusedState(
77
+ skipFocusManagerRegistration,
78
+ groupId,
79
+ id
80
+ );
69
81
 
70
82
  const state = useCellState({
71
83
  id: item.id,
@@ -26,6 +26,7 @@ type Props = {
26
26
  nextFocusLeft?: any;
27
27
  nextFocusRight?: any;
28
28
  onLayout?: any;
29
+ excludeFromFocusSearching?: boolean;
29
30
  };
30
31
 
31
32
  export class FocusableGroup extends BaseFocusable<Props> {
@@ -50,7 +50,11 @@ describe("getTransformedPreset should return the passed components if smartCompo
50
50
  );
51
51
 
52
52
  expect(transformed_preset).toBeDefined();
53
- expect(transformed_preset.id).toEqual("comp10"); // id was transformed
53
+
54
+ expect(transformed_preset.id).toEqual(
55
+ `${mockComp.id}-${mockPreset.feed_url}-0`
56
+ ); // id was transformed
57
+
54
58
  expect(transformed_preset.data.source).toEqual(mockPreset.feed_url); // source was replaced with preset feed_url.
55
59
  });
56
60
  });
@@ -117,7 +121,10 @@ describe("getTransformedPreset should return the passed components if smartCompo
117
121
 
118
122
  expect(Array.isArray(result)).toBe(true);
119
123
  expect(result).toHaveLength(2);
120
- expect(result[0].id).toEqual("smart10");
124
+
125
+ expect(result[0].id).toEqual(
126
+ `${mockSmartComp.id}-${mockFeeds.preset_source.data.entry[0].feed_url}-0`
127
+ );
121
128
 
122
129
  expect(result[0].data.source).toEqual(
123
130
  mockFeeds.preset_source.data.entry[0].feed_url
@@ -287,7 +294,7 @@ describe("getTransformedPreset should return the passed components if smartCompo
287
294
  mockComponents[0],
288
295
  {
289
296
  ...mockLayoutPresets.preset_one,
290
- id: mockComponents[1].id + 1,
297
+ id: `${mockComponents[1].id}-${mockPresetEntry[0].feed_url}-1`,
291
298
  data: { source: mockPresetEntry[0].feed_url },
292
299
  },
293
300
  ];
@@ -344,12 +351,12 @@ describe("getTransformedPreset should return the passed components if smartCompo
344
351
  const mockTransformedComponents = [
345
352
  {
346
353
  ...mockLayoutPresets.preset_one,
347
- id: mockComponents[0].id + 0,
354
+ id: `${mockComponents[0].id}-${mockPresetEntry1[0].feed_url}-0`,
348
355
  data: { source: mockPresetEntry1[0].feed_url },
349
356
  },
350
357
  {
351
358
  ...mockLayoutPresets.preset_one,
352
- id: mockComponents[1].id + 1,
359
+ id: `${mockComponents[1].id}-${mockPresetEntry2[0].feed_url}-1`,
353
360
  data: { source: mockPresetEntry2[0].feed_url },
354
361
  },
355
362
  ];
@@ -412,7 +419,7 @@ describe("getTransformedPreset should return the passed components if smartCompo
412
419
  const mockTransformedComponents = mockPresetEntry.map((preset, i) => {
413
420
  return {
414
421
  ...mockLayoutPresets[preset.preset_name],
415
- id: mockComponents[0].id + i,
422
+ id: `${mockComponents[0].id}-${preset.feed_url}-${i}`,
416
423
  data: { source: preset.feed_url },
417
424
  };
418
425
  });
@@ -41,7 +41,7 @@ export const getTransformedPreset = (
41
41
  }
42
42
 
43
43
  const updatedPresetComponent = produce(presetComponent, (draft) => {
44
- draft.id = `${componentId}${index}`;
44
+ draft.id = `${componentId}-${preset?.feed_url}-${index}`;
45
45
  draft.data.source = preset?.feed_url;
46
46
 
47
47
  if (draft.ui_components) {
@@ -19,6 +19,7 @@ type Props = {
19
19
  modalHeight?: number;
20
20
  modalWidth?: number;
21
21
  mode: VideoModalMode;
22
+ groupId?: string;
22
23
  };
23
24
 
24
25
  const YOUTUBE_PLUGIN_ID = "youtube-player-qb";
@@ -93,6 +94,7 @@ export function HandlePlayable({
93
94
  modalHeight,
94
95
  modalWidth,
95
96
  mode,
97
+ groupId,
96
98
  }: Props): React.ReactElement | null {
97
99
  const state = usePickFromState();
98
100
 
@@ -167,6 +169,7 @@ export function HandlePlayable({
167
169
  isModal={isModal}
168
170
  mode={mode}
169
171
  disableCastAction={playerModuleProperties?.disableCastAction}
172
+ groupId={groupId}
170
173
  />
171
174
  );
172
175
  }
@@ -60,7 +60,7 @@ export function ScreenLayoutContextProvider(props: {
60
60
  initialState
61
61
  );
62
62
 
63
- const screeenLayoutValue = React.useMemo(
63
+ const screenLayoutValue = React.useMemo(
64
64
  () => ({
65
65
  ...screenLayout,
66
66
  setScreenLayout,
@@ -69,7 +69,7 @@ export function ScreenLayoutContextProvider(props: {
69
69
  );
70
70
 
71
71
  return (
72
- <ScreenLayoutContext.Provider value={screeenLayoutValue}>
72
+ <ScreenLayoutContext.Provider value={screenLayoutValue}>
73
73
  {props.children}
74
74
  </ScreenLayoutContext.Provider>
75
75
  );
@@ -1,11 +1,15 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
+ import { View, StyleSheet } from "react-native";
3
4
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
5
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
6
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
7
+ import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
8
+ import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
6
9
 
7
10
  import {
8
11
  getNavigationProps,
12
+ getPathAttributes,
9
13
  resolveNavigationPlugin,
10
14
  routeIsPlayerScreen,
11
15
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
@@ -17,9 +21,25 @@ import { ScreenContainer } from "./ScreenContainer";
17
21
  import { TransitionOverlay } from "./TransitionOverlay";
18
22
 
19
23
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
20
- import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
21
24
  import { PathnameContext } from "../../../Contexts/PathnameContext";
22
- import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
25
+
26
+ import { FocusableGroup } from "../../FocusableGroup";
27
+ import { Screen } from "../../Screen/TV/index.web";
28
+
29
+ export const ScreenDataContext = React.createContext(null);
30
+
31
+ const styles = StyleSheet.create({
32
+ container: {
33
+ position: "absolute",
34
+ width: 1920,
35
+ height: 1080,
36
+ backgroundColor: "black",
37
+ // opacity: 0.5,
38
+
39
+ borderWidth: 4,
40
+ borderColor: "magenta",
41
+ },
42
+ });
23
43
 
24
44
  type Components = {
25
45
  NavBar: React.ComponentType<any>;
@@ -27,16 +47,73 @@ type Components = {
27
47
  };
28
48
 
29
49
  type ComponentsExtraProps = {
30
- Background?: Record<string, any>;
31
50
  NavBar?: Record<string, any>;
51
+ Background?: Record<string, any>;
32
52
  };
33
53
 
34
54
  type Props = {
35
55
  Components: Components;
36
- ComponentsExtraProps?: ComponentsExtraProps;
37
- children: React.ReactNode;
56
+ componentsExtraProps?: ComponentsExtraProps;
57
+ };
58
+
59
+ const StackNavigator = ({ Components }: Props) => {
60
+ // @ts-ignore
61
+ const { mainStack, currentRoute } = useNavigation();
62
+
63
+ // console.log("debug_2", "DEBUG", { mainStack });
64
+
65
+ const renderScreen = ({ route, action, key, state }, index) => {
66
+ const pathAttributes = getPathAttributes({ pathname: route });
67
+
68
+ if (R.isEmpty(pathAttributes)) {
69
+ return null;
70
+ }
71
+
72
+ // console.log("debug_2", "StackNavigator - renderScreen", {
73
+ // index,
74
+ // route,
75
+ // state,
76
+ // key,
77
+ // action,
78
+ // });
79
+
80
+ const id = `${route}`;
81
+
82
+ return (
83
+ <FocusableGroup
84
+ id={id}
85
+ preferredFocus={route === currentRoute}
86
+ excludeFromFocusSearching
87
+ key={id}
88
+ >
89
+ <View style={styles.container}>
90
+ <ScreenDataContext.Provider value={state}>
91
+ <PathnameContext.Provider value={route}>
92
+ <Screen route={route} Components={Components} />
93
+ </PathnameContext.Provider>
94
+ </ScreenDataContext.Provider>
95
+ </View>
96
+ </FocusableGroup>
97
+ );
98
+ };
99
+
100
+ return mainStack.map(renderScreen);
101
+ };
102
+
103
+ const TransitionerTV = ({ componentsExtraProps, Components }: Props) => {
104
+ return (
105
+ <>
106
+ <ScreenLayoutContextProvider>
107
+ <StackNavigator
108
+ componentsExtraProps={componentsExtraProps}
109
+ Components={Components}
110
+ />
111
+ </ScreenLayoutContextProvider>
112
+ </>
113
+ );
38
114
  };
39
115
 
116
+ // @ts-ignore
40
117
  const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
41
118
  const navigator = useNavigation();
42
119
  const theme = useTheme();
@@ -98,32 +175,54 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
98
175
  }
99
176
 
100
177
  return (
101
- <LayoutContainer>
102
- <ScreenLayoutContextProvider>
103
- <Components.Background
104
- backgroundColor={backgroundColor}
105
- backgroundImageUrl={backgroundImageUrl}
106
- {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
107
- >
108
- {!isTVOS ? navBarContainer : null}
109
- {navBarReady ? (
110
- /**
178
+ // @ts-ignore
179
+ <TransitionerTV
180
+ componentsExtraProps={ComponentsExtraProps}
181
+ Components={Components}
182
+ >
183
+ <LayoutContainer>
184
+ <ScreenLayoutContextProvider>
185
+ <Components.Background
186
+ backgroundColor={backgroundColor}
187
+ backgroundImageUrl={backgroundImageUrl}
188
+ {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
189
+ >
190
+ {!isTVOS ? navBarContainer : null}
191
+ {navBarReady ? (
192
+ /**
111
193
  Need to re-mount this component on each navigation event
112
194
  It's a hack to work around the fact that we don't support
113
195
  correct screen rendering in our navigator (shared currentRoute state)
114
196
  **/
115
- <PathnameContext.Provider value={navigator.currentRoute}>
116
- <ScreenContainer>{children}</ScreenContainer>
117
- </PathnameContext.Provider>
118
- ) : null}
119
- {/** Render NavBarContainer over ScreenContainer to let tvOS native
197
+ <PathnameContext.Provider value={navigator.currentRoute}>
198
+ <ScreenContainer>{children}</ScreenContainer>
199
+ </PathnameContext.Provider>
200
+ ) : null}
201
+ {/** Render NavBarContainer over ScreenContainer to let tvOS native
120
202
  focus manager works correctly to focus nav bar items first **/}
121
- {isTVOS ? navBarContainer : null}
122
- <TransitionOverlay />
123
- </Components.Background>
124
- </ScreenLayoutContextProvider>
125
- </LayoutContainer>
203
+ {isTVOS ? navBarContainer : null}
204
+ <TransitionOverlay />
205
+ </Components.Background>
206
+ </ScreenLayoutContextProvider>
207
+ </LayoutContainer>
208
+ </TransitionerTV>
126
209
  );
127
210
  };
128
211
 
129
212
  export default Layout;
213
+
214
+ /// Transitioner:
215
+ /*
216
+ Component: Transitioner
217
+ Responsibilities:
218
+ - Transitione between screens.
219
+ Tehere is no transition on Tv so no need for transitioner.
220
+
221
+ Component: StackNavigator
222
+ Responsibilities:
223
+ - Render the screens in the stack.
224
+ - Render the NavBar? I guess navbar should be part of the screen
225
+ - Render the screen.
226
+
227
+ Q
228
+ */
@@ -43,14 +43,16 @@ function Image({
43
43
  [source, error]
44
44
  );
45
45
 
46
+ const _source = shouldRenderImg
47
+ ? withDimensions(source)
48
+ : defaultPlaceHolderImage;
49
+
46
50
  return (
47
51
  // @ts-ignore
48
52
  <MemoizedImage
49
53
  style={style as ImageStyle}
50
54
  onError={() => setErrorState(true)}
51
- source={
52
- shouldRenderImg ? withDimensions(source) : defaultPlaceHolderImage
53
- }
55
+ source={_source?.uri ? _source : undefined}
54
56
  {...R.omit(["source"], otherProps)}
55
57
  />
56
58
  );
@@ -39,6 +39,6 @@ describe("image with no source", () => {
39
39
  );
40
40
 
41
41
  const testInstance = testRenderer.root;
42
- expect(testInstance.findByType(Image).props.source).toEqual({ uri: "" });
42
+ expect(testInstance.findByType(Image).props.source).toBeUndefined();
43
43
  });
44
44
  });
@@ -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,7 +251,6 @@ const PlayerContainerComponent = (props: Props) => {
243
251
  return;
244
252
  }
245
253
 
246
- navigator.goBack();
247
254
  showNavBar(true);
248
255
  }, [isModal, navigator.goBack, showNavBar]);
249
256
 
@@ -448,7 +455,7 @@ const PlayerContainerComponent = (props: Props) => {
448
455
  if (disableMiniPlayer) {
449
456
  navigator.closeVideoModal();
450
457
  } else {
451
- navigator.minimiseVideoModal();
458
+ setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
452
459
  }
453
460
  }
454
461
 
@@ -600,9 +607,6 @@ const PlayerContainerComponent = (props: Props) => {
600
607
  pluginConfiguration,
601
608
  };
602
609
 
603
- const { isActiveGesture, startComponentsAnimation } =
604
- useModalAnimationContext();
605
-
606
610
  return (
607
611
  <PlayerStateContext.Provider value={value}>
608
612
  <PlayerContainerContextProvider
@@ -626,6 +630,7 @@ const PlayerContainerComponent = (props: Props) => {
626
630
  style={styles.focusableGroup}
627
631
  preferredFocus
628
632
  shouldUsePreferredFocus
633
+ groupId={groupId}
629
634
  >
630
635
  {/* Video player and components */}
631
636
  <View
@@ -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, Animated, 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,67 @@ 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 = 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 = 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
+ }, []);
217
262
 
218
263
  if (shouldShowLoadingError) {
219
264
  return <RiverError />;
@@ -230,6 +275,9 @@ function ComponentsMapComponent(props: Props) {
230
275
  >
231
276
  <ViewportTracker>
232
277
  <FlatList
278
+ ref={(ref) => {
279
+ flatListRef.current = ref;
280
+ }}
233
281
  // Fix for WebView rerender crashes on Android API 28+
234
282
  // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
235
283
  overScrollMode={Platform.OS === "android" ? "never" : "auto"}
@@ -249,8 +297,23 @@ function ComponentsMapComponent(props: Props) {
249
297
  }
250
298
  ListFooterComponent={renderFooter}
251
299
  refreshControl={refreshControl}
252
- onScroll={onScrollFlatList}
253
- onMomentumScrollEnd={onMomentumScrollEnd}
300
+ onScrollBeginDrag={onScrollBeginDrag}
301
+ onScroll={Animated.event(
302
+ [
303
+ {
304
+ nativeEvent: {
305
+ contentOffset: {
306
+ y: scrollY,
307
+ },
308
+ },
309
+ },
310
+ ],
311
+ {
312
+ useNativeDriver: false,
313
+ }
314
+ )}
315
+ onMomentumScrollEnd={_onMomentumScrollEnd}
316
+ onScrollEndDrag={_onScrollEndDrag}
254
317
  scrollEventThrottle={16}
255
318
  {...scrollViewExtraProps}
256
319
  />
@@ -112,7 +112,6 @@ export const River = (props: Props) => {
112
112
 
113
113
  focusManager.setupInitialFocus(groupId, true);
114
114
  focusManager.setInitialFocus();
115
- focusManager.enableAutomaticFocus();
116
115
  }
117
116
  }, [river, componentsMapExtraProps]);
118
117