@applicaster/zapp-react-native-ui-components 12.0.0-alpha.7725319213 → 12.0.0-alpha.7855034103

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 (111) hide show
  1. package/Components/AudioPlayer/AudioPlayer.tsx +9 -1
  2. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayer.test.js.snap +5 -0
  3. package/Components/AudioPlayer/helpers.tsx +1 -0
  4. package/Components/Cell/Cell.tsx +2 -2
  5. package/Components/Cell/CellWithFocusable.tsx +21 -14
  6. package/Components/Cell/TvOSCellComponent.tsx +2 -2
  7. package/Components/ErrorScreen/index.tsx +1 -1
  8. package/Components/FocusableCell/index.tsx +7 -7
  9. package/Components/FocusableList/FocusableChild.ts +24 -14
  10. package/Components/FocusableList/FocusableItem.tsx +1 -1
  11. package/Components/FocusableList/FocusableListItemWrapper.tsx +76 -0
  12. package/Components/FocusableList/hooks/useCellState.android.ts +3 -1
  13. package/Components/FocusableList/index.tsx +26 -25
  14. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +6 -3
  15. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  16. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  17. package/Components/HookRenderer/index.tsx +1 -6
  18. package/Components/Image/Image.android.tsx +3 -25
  19. package/Components/Image/Image.tsx +18 -2
  20. package/Components/Image/utils/index.ts +39 -0
  21. package/Components/Layout/TV/__tests__/index.test.tsx +1 -34
  22. package/Components/Layout/TV/index.tsx +28 -41
  23. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  24. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +80 -109
  25. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  26. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
  27. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  28. package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
  29. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  30. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +17 -2
  31. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
  32. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  33. package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -1
  34. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  35. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  36. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +6 -14
  37. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  38. package/Components/MasterCell/utils/index.ts +16 -5
  39. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  40. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  41. package/Components/ModalComponent/BottomSheetModalContent.tsx +121 -0
  42. package/Components/ModalComponent/Button/Item.tsx +82 -0
  43. package/Components/ModalComponent/Button/ItemIcon.tsx +60 -0
  44. package/Components/ModalComponent/Button/ItemLabel.tsx +101 -0
  45. package/Components/ModalComponent/Button/assets.ts +4 -0
  46. package/Components/ModalComponent/Button/index.tsx +111 -0
  47. package/Components/ModalComponent/Header/CloseButton.tsx +59 -0
  48. package/Components/ModalComponent/Header/Title.tsx +70 -0
  49. package/Components/ModalComponent/Header/assets.ts +4 -0
  50. package/Components/ModalComponent/Header/index.tsx +71 -0
  51. package/Components/ModalComponent/Header/utils.ts +79 -0
  52. package/Components/ModalComponent/index.tsx +38 -0
  53. package/Components/ModalComponent/utils.ts +126 -0
  54. package/Components/PlayerContainer/PlayerContainer.tsx +60 -31
  55. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -1
  56. package/Components/PlayerImageBackground/index.tsx +34 -8
  57. package/Components/River/ComponentsMap/ComponentsMap.tsx +97 -2
  58. package/Components/River/RefreshControl.tsx +55 -1
  59. package/Components/River/TV/River.tsx +2 -2
  60. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -1
  61. package/Components/River/__tests__/componentsMap.test.js +7 -0
  62. package/Components/River/useScreenConfiguration.ts +2 -2
  63. package/Components/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +1 -0
  64. package/Components/RouteManager/__tests__/routeManager.test.js +9 -2
  65. package/Components/RouteManager/index.tsx +4 -2
  66. package/Components/Screen/__tests__/Screen.test.tsx +88 -74
  67. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +24 -6
  68. package/Components/Screen/__tests__/navigationHandler.test.ts +1 -178
  69. package/Components/Screen/index.tsx +103 -43
  70. package/Components/Screen/navigationHandler.ts +0 -82
  71. package/Components/ScreenResolver/index.tsx +1 -1
  72. package/Components/Tabs/TV/styles.ts +1 -1
  73. package/Components/Transitioner/Scene.tsx +24 -19
  74. package/Components/Transitioner/Transitioner.tsx +9 -2
  75. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +6 -2
  76. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  77. package/Components/VideoLive/LiveImageManager.ts +5 -2
  78. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -28
  79. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +45 -3
  80. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +18 -28
  81. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +78 -0
  82. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +342 -0
  83. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +151 -0
  84. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +438 -0
  85. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +154 -0
  86. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  87. package/Components/VideoModal/ModalAnimation/utils.ts +299 -0
  88. package/Components/VideoModal/PlayerDetails.tsx +6 -3
  89. package/Components/VideoModal/PlayerWrapper.tsx +78 -48
  90. package/Components/VideoModal/VideoModal.tsx +25 -42
  91. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +0 -2
  92. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +12 -1
  93. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +246 -166
  94. package/Components/VideoModal/utils.ts +3 -3
  95. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +26 -21
  96. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  97. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +4 -5
  98. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  99. package/Components/Viewport/utils.js +1 -1
  100. package/Components/ZappUIComponent/index.tsx +22 -12
  101. package/Components/index.js +2 -0
  102. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  103. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  104. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  105. package/Contexts/ScreenContext/index.tsx +95 -29
  106. package/Contexts/ScreenDataContext/index.ts +3 -0
  107. package/Decorators/ComponentsMapWrapper/withComponentsMapProvider.tsx +7 -2
  108. package/events/index.ts +1 -0
  109. package/package.json +5 -5
  110. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  111. /package/Contexts/{index.js → index.ts} +0 -0
@@ -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
@@ -145,34 +150,30 @@ const renderApplePlayer = ({
145
150
  }) => {
146
151
  const { title, summary } = item || {};
147
152
 
148
- if (!isApplePlatform()) {
153
+ if (!isAppleTV() || !player) {
154
+ return null;
155
+ }
156
+
157
+ if (isAudioContent) {
158
+ return (
159
+ <AudioPlayer
160
+ audio_item={item}
161
+ plugin_configuration={pluginConfiguration}
162
+ />
163
+ );
164
+ }
165
+
166
+ if (!playNextData && player.isPaused() && player.isAd() === false) {
149
167
  return null;
150
168
  }
151
169
 
152
170
  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
- </>
171
+ <ProgramInfo
172
+ title={title}
173
+ subtitle={summary}
174
+ plugin_configuration={pluginConfiguration}
175
+ videoStyle={videoStyle}
176
+ />
176
177
  );
177
178
  };
178
179
 
@@ -233,6 +234,9 @@ const PlayerContainerComponent = (props: Props) => {
233
234
  const screenData = useTargetScreenData(item);
234
235
  const { setVisible: showNavBar } = useSetNavbarState();
235
236
 
237
+ const { isActiveGesture, startComponentsAnimation, setPlayerAnimationState } =
238
+ useModalAnimationContext();
239
+
236
240
  const playerEvent = (event, ...args) => {
237
241
  playerManager.invokeHandler(event, ...args);
238
242
  };
@@ -339,6 +343,18 @@ const PlayerContainerComponent = (props: Props) => {
339
343
  }
340
344
  }, [item?.id, item?.extensions?.resumeTime, player]);
341
345
 
346
+ const onVideoFullscreenPlayerWillPresent = React.useCallback(() => {
347
+ playerManager.invokeHandler("toggleFullscreen", {
348
+ fullscreen: true,
349
+ });
350
+ }, []);
351
+
352
+ const onVideoFullscreenPlayerWillDismiss = React.useCallback(() => {
353
+ playerManager.invokeHandler("toggleFullscreen", {
354
+ fullscreen: false,
355
+ });
356
+ }, []);
357
+
342
358
  // Util methods
343
359
  const toggleFullscreen = (event) => {
344
360
  const { fullscreen = false } = event || {};
@@ -381,17 +397,27 @@ const PlayerContainerComponent = (props: Props) => {
381
397
  id: playerContainerId,
382
398
  listener: {
383
399
  onVideoEnd,
384
- onError: onError,
385
- onLoad: onLoad,
400
+ onError,
401
+ onLoad,
386
402
  onPlayerClose: close,
387
403
  onPlayerDetached: close,
404
+ onVideoFullscreenPlayerWillDismiss,
405
+ onVideoFullscreenPlayerWillPresent,
388
406
  },
389
407
  });
390
408
 
391
409
  return () => {
392
410
  player.removeListener(playerContainerId);
393
411
  };
394
- }, [player, onVideoEnd]);
412
+ }, [
413
+ player,
414
+ onVideoEnd,
415
+ onError,
416
+ onLoad,
417
+ close,
418
+ onVideoFullscreenPlayerWillDismiss,
419
+ onVideoFullscreenPlayerWillPresent,
420
+ ]);
395
421
 
396
422
  useEffect(() => {
397
423
  // TODO: We should not use playerManager here to connect buttons to player container
@@ -428,7 +454,7 @@ const PlayerContainerComponent = (props: Props) => {
428
454
  if (disableMiniPlayer) {
429
455
  navigator.closeVideoModal();
430
456
  } else {
431
- navigator.minimiseVideoModal();
457
+ setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
432
458
  }
433
459
  }
434
460
 
@@ -580,8 +606,6 @@ const PlayerContainerComponent = (props: Props) => {
580
606
  pluginConfiguration,
581
607
  };
582
608
 
583
- console.log("Anton player container", {result: {isAppleTV: isAppleTV(), notInlineTv: !isInlineTV}});
584
-
585
609
  return (
586
610
  <PlayerStateContext.Provider value={value}>
587
611
  <PlayerContainerContextProvider
@@ -625,7 +649,11 @@ const PlayerContainerComponent = (props: Props) => {
625
649
  autoplay={true}
626
650
  controls={false}
627
651
  disableCastAction={disableCastAction}
628
- docked={navigator.isVideoModalDocked()}
652
+ docked={
653
+ navigator.isVideoModalDocked() &&
654
+ !startComponentsAnimation &&
655
+ !isActiveGesture
656
+ }
629
657
  entry={item}
630
658
  fullscreen={mode === VideoModalMode.FULLSCREEN}
631
659
  inline={inline}
@@ -643,6 +671,7 @@ const PlayerContainerComponent = (props: Props) => {
643
671
  setNextVideoPreloadThresholdPercentage={
644
672
  setNextVideoPreloadThresholdPercentage
645
673
  }
674
+ startComponentsAnimation={startComponentsAnimation}
646
675
  >
647
676
  {renderApplePlayer(applePlayerProps)}
648
677
  </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,6 +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 { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
25
+ import { useShallow } from "zustand/react/shallow";
24
26
 
25
27
  type Props = {
26
28
  feed: ZappFeed;
@@ -40,8 +42,10 @@ const styles = StyleSheet.create({
40
42
  },
41
43
  });
42
44
 
43
- const scrollIndicatorInsets = { right: 1 };
45
+ const getNearestValue = (value, optionA, optionB) =>
46
+ Math.abs(value - optionA) < Math.abs(value - optionB) ? optionA : optionB;
44
47
 
48
+ const scrollIndicatorInsets = { right: 1 };
45
49
  const keyExtractor = R.compose(String, R.prop("id"));
46
50
 
47
51
  function ComponentsMapComponent(props: Props) {
@@ -59,6 +63,7 @@ function ComponentsMapComponent(props: Props) {
59
63
  refreshingPipesV1,
60
64
  } = props;
61
65
 
66
+ const flatListRef = React.useRef<FlatList | null>(null);
62
67
  const screenConfig = useScreenConfiguration(riverId);
63
68
  const screenData = useScreenData(riverId);
64
69
  const pullToRefreshEnabled = screenData?.rules?.pull_to_refresh_enabled;
@@ -193,6 +198,88 @@ function ComponentsMapComponent(props: Props) {
193
198
  []
194
199
  );
195
200
 
201
+ const navBarStore = useScreenContextV2()._navBarStore;
202
+
203
+ const scrollY = navBarStore(
204
+ useShallow(({ scrollYAnimated }) => scrollYAnimated)
205
+ );
206
+
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
+ }, []);
282
+
196
283
  if (shouldShowLoadingError) {
197
284
  return <RiverError />;
198
285
  }
@@ -208,6 +295,9 @@ function ComponentsMapComponent(props: Props) {
208
295
  >
209
296
  <ViewportTracker>
210
297
  <FlatList
298
+ ref={(ref) => {
299
+ flatListRef.current = ref;
300
+ }}
211
301
  // Fix for WebView rerender crashes on Android API 28+
212
302
  // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
213
303
  overScrollMode={Platform.OS === "android" ? "never" : "auto"}
@@ -227,6 +317,11 @@ function ComponentsMapComponent(props: Props) {
227
317
  }
228
318
  ListFooterComponent={renderFooter}
229
319
  refreshControl={refreshControl}
320
+ onScrollBeginDrag={onScrollBeginDrag}
321
+ onScroll={onScroll}
322
+ onMomentumScrollEnd={_onMomentumScrollEnd}
323
+ onScrollEndDrag={_onScrollEndDrag}
324
+ scrollEventThrottle={16}
230
325
  {...scrollViewExtraProps}
231
326
  />
232
327
  </ViewportTracker>
@@ -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
  }
@@ -4,7 +4,6 @@ import * as React from "react";
4
4
  import { Text } from "react-native";
5
5
  import * as R from "ramda";
6
6
 
7
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
8
7
  import {
9
8
  useFeedLoader,
10
9
  useLayoutVersion,
@@ -20,6 +19,7 @@ import {
20
19
  useNavbarState,
21
20
  useSetNavbarState,
22
21
  } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
22
+ import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
23
23
 
24
24
  type Props = {
25
25
  screenId: string;
@@ -53,7 +53,7 @@ export const River = (props: Props) => {
53
53
 
54
54
  const navigationPluginData = useNavigationPluginData();
55
55
 
56
- const { rivers } = usePickFromState(["rivers"]);
56
+ const rivers = useRivers();
57
57
  const isV2 = useLayoutVersion({ isV2: true });
58
58
 
59
59
  const river = React.useMemo(() => rivers?.[screenId], [screenId]);
@@ -64,7 +64,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
64
64
  refreshControl={null}
65
65
  removeClippedSubviews={true}
66
66
  renderItem={[Function]}
67
- scrollEventThrottle={50}
67
+ scrollEventThrottle={16}
68
68
  scrollIndicatorInsets={
69
69
  Object {
70
70
  "right": 1,
@@ -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", () => ({
@@ -1,10 +1,10 @@
1
1
  import * as R from "ramda";
2
2
 
3
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
3
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
5
4
  import { toCamelCase } from "@applicaster/zapp-react-native-utils/stringUtils";
6
5
  import { useMemo } from "react";
7
6
  import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
7
+ import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
8
8
 
9
9
  type GeneralContentScreen = ZappRiver & {
10
10
  styles: {
@@ -46,7 +46,7 @@ const stylingKeys = [
46
46
  ];
47
47
 
48
48
  export function useScreenConfiguration(screenId: string): ScreenConfiguration {
49
- const { rivers } = usePickFromState(["rivers"]);
49
+ const rivers = useRivers();
50
50
  const isTablet = useIsTablet();
51
51
 
52
52
  return useMemo(() => {
@@ -5,6 +5,7 @@ exports[`<RouteManager /> renders correctly 1`] = `
5
5
  screenId="A1234"
6
6
  >
7
7
  <WithProvider
8
+ pathname="/river/A1234"
8
9
  screenData={
9
10
  Object {
10
11
  "id": "A1234",
@@ -2,11 +2,17 @@ import React from "react";
2
2
  import { shallow } from "enzyme";
3
3
  import { shallowToJson } from "enzyme-to-json";
4
4
 
5
- import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
6
-
7
5
  const pathname = "/river/A1234";
8
6
  const screenData = { id: "A1234" };
9
7
 
8
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
9
+ useRoute: jest.fn(() => ({ pathname: pathname, screenData: screenData })),
10
+ }));
11
+
12
+ const {
13
+ ScreenResolver,
14
+ } = require("@applicaster/zapp-react-native-ui-components/Components/ScreenResolver");
15
+
10
16
  const { RouteManager } = require("../index");
11
17
 
12
18
  describe("<RouteManager />", () => {
@@ -20,6 +26,7 @@ describe("<RouteManager />", () => {
20
26
 
21
27
  it("passes the correct props to ScreenResolver", () => {
22
28
  expect(wrapper.find(ScreenResolver).props()).toEqual({
29
+ pathname: "/river/A1234",
23
30
  screenType: "river",
24
31
  screenId: "A1234",
25
32
  screenData: { id: "A1234" },
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
  import * as R from "ramda";
3
3
 
4
4
  import { getPathAttributes } from "@applicaster/zapp-react-native-utils/navigationUtils";
5
+ import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks";
5
6
  import { ScreenResolver } from "../ScreenResolver";
6
7
  import { TestId } from "./TestId";
7
8
 
@@ -12,7 +13,8 @@ type Props = { pathname: string; screenData: Record<string, any> };
12
13
  * and reconciles data returned from hook plugins if needed
13
14
  * @param {Props}: see props above
14
15
  */
15
- export function RouteManager({ pathname, screenData, ...otherProps }: Props) {
16
+ export function RouteManager(props: Props) {
17
+ const { pathname, screenData } = useRoute();
16
18
  const pathAttributes = getPathAttributes({ pathname });
17
19
 
18
20
  const { screenType, screenId } = R.last(pathAttributes);
@@ -22,8 +24,8 @@ export function RouteManager({ pathname, screenData, ...otherProps }: Props) {
22
24
  <ScreenResolver
23
25
  screenType={screenType}
24
26
  screenId={screenId}
27
+ {...props}
25
28
  screenData={screenData}
26
- {...otherProps}
27
29
  />
28
30
  </TestId>
29
31
  );