@applicaster/zapp-react-native-ui-components 12.0.0-alpha.3977160850 → 12.0.0-alpha.4337072244

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 (82) hide show
  1. package/Components/BaseFocusable/index.tsx +1 -1
  2. package/Components/Cell/Cell.tsx +7 -7
  3. package/Components/Cell/CellWithFocusable.tsx +38 -19
  4. package/Components/Cell/TvOSCellComponent.tsx +2 -2
  5. package/Components/ErrorScreen/index.tsx +1 -1
  6. package/Components/FocusableCell/index.tsx +7 -7
  7. package/Components/FocusableGroup/index.tsx +1 -0
  8. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  9. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  10. package/Components/HandlePlayable/HandlePlayable.tsx +3 -0
  11. package/Components/HookRenderer/index.tsx +1 -3
  12. package/Components/Image/Image.android.tsx +3 -25
  13. package/Components/Image/Image.tsx +18 -2
  14. package/Components/Image/utils/index.ts +39 -0
  15. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  16. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -10
  17. package/Components/Layout/TV/__tests__/index.test.tsx +18 -86
  18. package/Components/Layout/TV/index.tsx +43 -102
  19. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  20. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
  21. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  22. package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
  23. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  24. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +17 -2
  25. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
  26. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  27. package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -1
  28. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  29. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  30. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  31. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +4 -1
  32. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
  33. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  34. package/Components/MasterCell/utils/index.ts +18 -7
  35. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  36. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  37. package/Components/PlayerContainer/PlayerContainer.tsx +64 -31
  38. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -1
  39. package/Components/PlayerImageBackground/index.tsx +34 -8
  40. package/Components/River/ComponentsMap/ComponentsMap.tsx +86 -22
  41. package/Components/River/RefreshControl.tsx +55 -1
  42. package/Components/River/TV/River.tsx +0 -23
  43. package/Components/River/__tests__/componentsMap.test.js +7 -0
  44. package/Components/RouteManager/index.tsx +2 -2
  45. package/Components/Screen/TV/hooks/index.ts +3 -0
  46. package/Components/Screen/TV/hooks/useInitialFocus.ts +67 -0
  47. package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
  48. package/Components/Screen/TV/index.web.tsx +174 -0
  49. package/Components/Screen/__tests__/Screen.test.tsx +5 -1
  50. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +14 -0
  51. package/Components/Screen/index.tsx +48 -28
  52. package/Components/ScreenResolver/index.tsx +3 -1
  53. package/Components/Tabs/TV/styles.ts +1 -1
  54. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -28
  55. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +45 -3
  56. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +18 -28
  57. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +78 -0
  58. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +342 -0
  59. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +151 -0
  60. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +438 -0
  61. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +154 -0
  62. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  63. package/Components/VideoModal/ModalAnimation/utils.ts +299 -0
  64. package/Components/VideoModal/PlayerWrapper.tsx +66 -43
  65. package/Components/VideoModal/VideoModal.tsx +25 -42
  66. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +6 -1
  67. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +246 -166
  68. package/Components/VideoModal/utils.ts +3 -3
  69. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +1 -1
  70. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  71. package/Components/Viewport/utils.js +1 -1
  72. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  73. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  74. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  75. package/Contexts/ScreenContext/index.tsx +94 -25
  76. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  77. package/Decorators/ComponentsMapWrapper/withComponentsMapProvider.tsx +7 -2
  78. package/events/index.ts +1 -0
  79. package/package.json +5 -5
  80. package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
  81. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  82. /package/Contexts/{index.js → index.ts} +0 -0
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
+ import { MeasurementPortalContext } from "./MeasurementsPortal";
3
4
 
4
5
  type Props = {
5
6
  Component: React.ElementType;
@@ -19,13 +20,16 @@ const _MeasurementsPortal = (
19
20
  { Component, onLayout, componentProps, containerStyle }: Props,
20
21
  ref
21
22
  ) => (
22
- <View
23
- onLayout={onLayout}
24
- style={[styles.container, containerStyle]}
25
- ref={ref}
26
- >
27
- <Component {...componentProps} />
28
- </View>
23
+ // hack for cell to determine if cell is in measurement mode
24
+ <MeasurementPortalContext.Provider value={{}}>
25
+ <View
26
+ onLayout={onLayout}
27
+ style={[styles.container, containerStyle]}
28
+ ref={ref}
29
+ >
30
+ <Component {...componentProps} />
31
+ </View>
32
+ </MeasurementPortalContext.Provider>
29
33
  );
30
34
 
31
35
  export const MeasurementsPortal = React.forwardRef(_MeasurementsPortal);
@@ -35,7 +35,16 @@ const MeasurementsPortal = ({ Component, onLayout, componentProps }: Props) => {
35
35
  );
36
36
  };
37
37
 
38
- const MeasurementPortalContext = React.createContext(null);
38
+ type MeasurementPortalContextType = {
39
+ measureComponent?: (
40
+ comp: React.ElementType,
41
+ props: Record<any, any>
42
+ ) => Promise<any>;
43
+ measuringInProgress?: boolean;
44
+ };
45
+
46
+ const MeasurementPortalContext =
47
+ React.createContext<null | MeasurementPortalContextType>(null);
39
48
 
40
49
  const MeasurementsPortalContextProvider = ({ children }) => {
41
50
  const Component = React.useRef(View);
@@ -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"] & {
@@ -145,34 +151,30 @@ const renderApplePlayer = ({
145
151
  }) => {
146
152
  const { title, summary } = item || {};
147
153
 
148
- if (!isApplePlatform()) {
154
+ if (!isAppleTV() || !player) {
155
+ return null;
156
+ }
157
+
158
+ if (isAudioContent) {
159
+ return (
160
+ <AudioPlayer
161
+ audio_item={item}
162
+ plugin_configuration={pluginConfiguration}
163
+ />
164
+ );
165
+ }
166
+
167
+ if (!playNextData && player.isPaused() && player.isAd() === false) {
149
168
  return null;
150
169
  }
151
170
 
152
171
  return (
153
- <>
154
- {!playNextData && (
155
- <>
156
- {!isAudioContent &&
157
- !!player?.isPaused() &&
158
- !!player?.isAd() === false && (
159
- <ProgramInfo
160
- title={title}
161
- subtitle={summary}
162
- plugin_configuration={pluginConfiguration}
163
- videoStyle={videoStyle}
164
- />
165
- )}
166
- </>
167
- )}
168
-
169
- {isAudioContent && (
170
- <AudioPlayer
171
- audio_item={item}
172
- plugin_configuration={pluginConfiguration}
173
- />
174
- )}
175
- </>
172
+ <ProgramInfo
173
+ title={title}
174
+ subtitle={summary}
175
+ plugin_configuration={pluginConfiguration}
176
+ videoStyle={videoStyle}
177
+ />
176
178
  );
177
179
  };
178
180
 
@@ -199,6 +201,7 @@ const PlayerContainerComponent = (props: Props) => {
199
201
  style: videoStyle,
200
202
  mode = VideoModalMode.MAXIMIZED,
201
203
  disableCastAction = false,
204
+ groupId,
202
205
  } = props;
203
206
 
204
207
  // Hooks
@@ -233,6 +236,9 @@ const PlayerContainerComponent = (props: Props) => {
233
236
  const screenData = useTargetScreenData(item);
234
237
  const { setVisible: showNavBar } = useSetNavbarState();
235
238
 
239
+ const { isActiveGesture, startComponentsAnimation, setPlayerAnimationState } =
240
+ useModalAnimationContext();
241
+
236
242
  const playerEvent = (event, ...args) => {
237
243
  playerManager.invokeHandler(event, ...args);
238
244
  };
@@ -245,7 +251,6 @@ const PlayerContainerComponent = (props: Props) => {
245
251
  return;
246
252
  }
247
253
 
248
- navigator.goBack();
249
254
  showNavBar(true);
250
255
  }, [isModal, navigator.goBack, showNavBar]);
251
256
 
@@ -339,6 +344,18 @@ const PlayerContainerComponent = (props: Props) => {
339
344
  }
340
345
  }, [item?.id, item?.extensions?.resumeTime, player]);
341
346
 
347
+ const onVideoFullscreenPlayerWillPresent = React.useCallback(() => {
348
+ playerManager.invokeHandler("toggleFullscreen", {
349
+ fullscreen: true,
350
+ });
351
+ }, []);
352
+
353
+ const onVideoFullscreenPlayerWillDismiss = React.useCallback(() => {
354
+ playerManager.invokeHandler("toggleFullscreen", {
355
+ fullscreen: false,
356
+ });
357
+ }, []);
358
+
342
359
  // Util methods
343
360
  const toggleFullscreen = (event) => {
344
361
  const { fullscreen = false } = event || {};
@@ -381,17 +398,27 @@ const PlayerContainerComponent = (props: Props) => {
381
398
  id: playerContainerId,
382
399
  listener: {
383
400
  onVideoEnd,
384
- onError: onError,
385
- onLoad: onLoad,
401
+ onError,
402
+ onLoad,
386
403
  onPlayerClose: close,
387
404
  onPlayerDetached: close,
405
+ onVideoFullscreenPlayerWillDismiss,
406
+ onVideoFullscreenPlayerWillPresent,
388
407
  },
389
408
  });
390
409
 
391
410
  return () => {
392
411
  player.removeListener(playerContainerId);
393
412
  };
394
- }, [player, onVideoEnd]);
413
+ }, [
414
+ player,
415
+ onVideoEnd,
416
+ onError,
417
+ onLoad,
418
+ close,
419
+ onVideoFullscreenPlayerWillDismiss,
420
+ onVideoFullscreenPlayerWillPresent,
421
+ ]);
395
422
 
396
423
  useEffect(() => {
397
424
  // TODO: We should not use playerManager here to connect buttons to player container
@@ -428,7 +455,7 @@ const PlayerContainerComponent = (props: Props) => {
428
455
  if (disableMiniPlayer) {
429
456
  navigator.closeVideoModal();
430
457
  } else {
431
- navigator.minimiseVideoModal();
458
+ setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
432
459
  }
433
460
  }
434
461
 
@@ -603,6 +630,7 @@ const PlayerContainerComponent = (props: Props) => {
603
630
  style={styles.focusableGroup}
604
631
  preferredFocus
605
632
  shouldUsePreferredFocus
633
+ groupId={groupId}
606
634
  >
607
635
  {/* Video player and components */}
608
636
  <View
@@ -621,9 +649,13 @@ const PlayerContainerComponent = (props: Props) => {
621
649
  }}
622
650
  focused={isInlineTV ? true : undefined}
623
651
  autoplay={true}
624
- controls={isAppleTV() && !isInlineTV}
652
+ controls={false}
625
653
  disableCastAction={disableCastAction}
626
- docked={navigator.isVideoModalDocked()}
654
+ docked={
655
+ navigator.isVideoModalDocked() &&
656
+ !startComponentsAnimation &&
657
+ !isActiveGesture
658
+ }
627
659
  entry={item}
628
660
  fullscreen={mode === VideoModalMode.FULLSCREEN}
629
661
  inline={inline}
@@ -641,6 +673,7 @@ const PlayerContainerComponent = (props: Props) => {
641
673
  setNextVideoPreloadThresholdPercentage={
642
674
  setNextVideoPreloadThresholdPercentage
643
675
  }
676
+ startComponentsAnimation={startComponentsAnimation}
644
677
  >
645
678
  {renderApplePlayer(applePlayerProps)}
646
679
  </Player>
@@ -4,7 +4,9 @@ import { styleKeys } from "@applicaster/zapp-react-native-utils/styleKeysUtils";
4
4
  import { transformColorCode as fixColorHexCode } from "@applicaster/zapp-react-native-utils/transform";
5
5
  import * as R from "ramda";
6
6
  import * as React from "react";
7
- import { Image, Text, TextStyle, View, ViewStyle } from "react-native";
7
+ import { Text, TextStyle, View, ViewStyle } from "react-native";
8
+ import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
9
+
8
10
  import * as programStyles from "./styles";
9
11
 
10
12
  type Props = {
@@ -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,81 @@ 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
+ }
275
+ }, []);
217
276
 
218
277
  if (shouldShowLoadingError) {
219
278
  return <RiverError />;
@@ -230,6 +289,9 @@ function ComponentsMapComponent(props: Props) {
230
289
  >
231
290
  <ViewportTracker>
232
291
  <FlatList
292
+ ref={(ref) => {
293
+ flatListRef.current = ref;
294
+ }}
233
295
  // Fix for WebView rerender crashes on Android API 28+
234
296
  // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
235
297
  overScrollMode={Platform.OS === "android" ? "never" : "auto"}
@@ -249,8 +311,10 @@ function ComponentsMapComponent(props: Props) {
249
311
  }
250
312
  ListFooterComponent={renderFooter}
251
313
  refreshControl={refreshControl}
252
- onScroll={onScrollFlatList}
253
- onMomentumScrollEnd={onMomentumScrollEnd}
314
+ onScrollBeginDrag={onScrollBeginDrag}
315
+ onScroll={onScroll}
316
+ onMomentumScrollEnd={_onMomentumScrollEnd}
317
+ onScrollEndDrag={_onScrollEndDrag}
254
318
  scrollEventThrottle={16}
255
319
  {...scrollViewExtraProps}
256
320
  />
@@ -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 - top;
138
+ case "on_top_screen":
139
+ // navbar height + status bar height
140
+ return offset + height;
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
  }));
@@ -83,6 +89,7 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
83
89
  useNavigation: jest.fn(() => ({ currentRoute: "/river/A1234" })),
84
90
  useRoute: jest.fn(() => ({ pathname: "/river/A1234", screenData })),
85
91
  useProfilerLogging: jest.fn(),
92
+ useIsScreenActive: jest.fn().mockReturnValue(true),
86
93
  }));
87
94
 
88
95
  jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
@@ -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 } = useRoute();
17
+ const { pathname, screenData: _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={screenData}
28
+ screenData={props.screenData}
29
29
  />
30
30
  </TestId>
31
31
  );
@@ -0,0 +1,3 @@
1
+ export { useInitialFocus } from "./useInitialFocus";
2
+
3
+ export { useRestoreFocus } from "./useRestoreFocus";