@applicaster/zapp-react-native-ui-components 12.0.0-rc.14 → 12.0.0-rc.141

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 (171) 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/BaseFocusable/index.tsx +3 -3
  5. package/Components/Cell/Cell.tsx +40 -12
  6. package/Components/Cell/CellWithFocusable.tsx +40 -19
  7. package/Components/Cell/TvOSCellComponent.tsx +5 -9
  8. package/Components/ErrorScreen/index.tsx +1 -1
  9. package/Components/Focusable/Focusable.tsx +6 -1
  10. package/Components/Focusable/__tests__/index.android.test.tsx +72 -0
  11. package/Components/Focusable/index.android.tsx +15 -9
  12. package/Components/FocusableCell/index.tsx +7 -7
  13. package/Components/FocusableGroup/index.tsx +11 -2
  14. package/Components/FocusableList/FocusableChild.ts +26 -16
  15. package/Components/FocusableList/FocusableItem.tsx +1 -1
  16. package/Components/FocusableList/FocusableListItemWrapper.tsx +76 -0
  17. package/Components/FocusableList/hooks/useCellState.android.ts +3 -1
  18. package/Components/FocusableList/index.tsx +37 -37
  19. package/Components/FocusableScrollView/index.tsx +1 -1
  20. package/Components/FreezeWithCallback/Freeze.tsx +31 -0
  21. package/Components/FreezeWithCallback/LICENSE +21 -0
  22. package/Components/FreezeWithCallback/Suspender.tsx +17 -0
  23. package/Components/FreezeWithCallback/__tests__/index.test.tsx +170 -0
  24. package/Components/FreezeWithCallback/index.tsx +3 -0
  25. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +11 -8
  26. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  27. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  28. package/Components/HandlePlayable/HandlePlayable.tsx +4 -1
  29. package/Components/HookRenderer/index.tsx +1 -6
  30. package/Components/Image/Image.android.tsx +4 -26
  31. package/Components/Image/Image.tsx +18 -2
  32. package/Components/Image/utils/index.ts +39 -0
  33. package/Components/Layout/TV/ScreenContainer.tsx +79 -5
  34. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  35. package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +38 -28
  36. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +25 -13
  37. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -50
  38. package/Components/Layout/TV/__tests__/index.test.tsx +20 -108
  39. package/Components/Layout/TV/index.tsx +15 -84
  40. package/Components/Layout/TV/index.web.tsx +33 -0
  41. package/Components/Layout/index.tsx +15 -11
  42. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +14 -7
  43. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +86 -117
  44. package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +27 -9
  45. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  46. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +16 -1
  47. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
  48. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +3 -0
  49. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +2 -2
  50. package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
  51. package/Components/MasterCell/DefaultComponents/ImageBorderContainer/index.ts +71 -0
  52. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  53. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +37 -2
  54. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
  55. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  56. package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -1
  57. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  58. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  59. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  60. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
  61. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +6 -10
  62. package/Components/MasterCell/SharedUI/ProgressBar/hooks/useFillInPercent.ts +7 -1
  63. package/Components/MasterCell/dataAdapter.ts +21 -7
  64. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  65. package/Components/MasterCell/index.tsx +5 -3
  66. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  67. package/Components/MasterCell/utils/index.ts +31 -34
  68. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  69. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  70. package/Components/ModalComponent/BottomSheetModalContent.tsx +121 -0
  71. package/Components/ModalComponent/Button/Item.tsx +82 -0
  72. package/Components/ModalComponent/Button/ItemIcon.tsx +60 -0
  73. package/Components/ModalComponent/Button/ItemLabel.tsx +101 -0
  74. package/Components/ModalComponent/Button/assets.ts +4 -0
  75. package/Components/ModalComponent/Button/index.tsx +111 -0
  76. package/Components/ModalComponent/Header/CloseButton.tsx +59 -0
  77. package/Components/ModalComponent/Header/Title.tsx +70 -0
  78. package/Components/ModalComponent/Header/assets.ts +4 -0
  79. package/Components/ModalComponent/Header/index.tsx +71 -0
  80. package/Components/ModalComponent/Header/utils.ts +79 -0
  81. package/Components/ModalComponent/index.tsx +38 -0
  82. package/Components/ModalComponent/utils.ts +126 -0
  83. package/Components/Navigator/StackNavigator.tsx +57 -0
  84. package/Components/Navigator/index.ts +1 -0
  85. package/Components/PlayerContainer/PlayerContainer.tsx +80 -39
  86. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -1
  87. package/Components/PlayerImageBackground/index.tsx +34 -8
  88. package/Components/River/ComponentsMap/ComponentsMap.tsx +97 -2
  89. package/Components/River/RefreshControl.tsx +55 -1
  90. package/Components/River/TV/River.tsx +2 -25
  91. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -1
  92. package/Components/River/__tests__/componentsMap.test.js +7 -0
  93. package/Components/River/useScreenConfiguration.ts +2 -2
  94. package/Components/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +1 -0
  95. package/Components/RouteManager/__tests__/routeManager.test.js +9 -2
  96. package/Components/RouteManager/index.tsx +4 -2
  97. package/Components/Screen/TV/hooks/index.ts +1 -0
  98. package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
  99. package/Components/Screen/TV/index.web.tsx +178 -0
  100. package/Components/Screen/__tests__/Screen.test.tsx +43 -75
  101. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +24 -6
  102. package/Components/Screen/__tests__/navigationHandler.test.ts +1 -178
  103. package/Components/Screen/index.tsx +104 -65
  104. package/Components/Screen/navigationHandler.ts +6 -85
  105. package/Components/Screen/orientationHandler.ts +18 -7
  106. package/Components/Screen/utils.ts +16 -0
  107. package/Components/ScreenResolver/index.tsx +10 -13
  108. package/Components/Tabs/TV/styles.ts +1 -1
  109. package/Components/TrackedView/index.tsx +6 -3
  110. package/Components/Transitioner/Scene.tsx +39 -26
  111. package/Components/Transitioner/Transitioner.tsx +56 -48
  112. package/Components/Transitioner/__tests__/Scene.test.js +7 -0
  113. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +12 -4
  114. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +2 -0
  115. package/Components/VideoLive/LiveImageManager.ts +65 -11
  116. package/Components/VideoLive/PlayerLiveImageComponent.tsx +132 -95
  117. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +45 -3
  118. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +24 -27
  119. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +60 -0
  120. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +401 -0
  121. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +172 -0
  122. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +475 -0
  123. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +182 -0
  124. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  125. package/Components/VideoModal/ModalAnimation/utils.ts +302 -0
  126. package/Components/VideoModal/PlayerDetails.tsx +21 -4
  127. package/Components/VideoModal/PlayerWrapper.tsx +115 -55
  128. package/Components/VideoModal/VideoModal.tsx +32 -43
  129. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +6 -1
  130. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +12 -1
  131. package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +2 -0
  132. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +358 -166
  133. package/Components/VideoModal/hooks/useModalSize.ts +10 -12
  134. package/Components/VideoModal/utils.ts +8 -5
  135. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +26 -21
  136. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  137. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +4 -5
  138. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  139. package/Components/Viewport/utils.js +1 -1
  140. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +7 -3
  141. package/Components/ZappFrameworkComponents/BarView/Buttons/BackButton.tsx +7 -2
  142. package/Components/ZappFrameworkComponents/BarView/Buttons/CloseButton.tsx +7 -2
  143. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +87 -0
  144. package/Components/ZappFrameworkComponents/Buttons/ActionButton.tsx +9 -4
  145. package/Components/ZappUIComponent/index.tsx +22 -12
  146. package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +4 -4
  147. package/Components/index.js +2 -0
  148. package/Contexts/CachedDimensionsContext/index.ts +13 -9
  149. package/Contexts/ConfirmDialogState/index.ts +1 -1
  150. package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
  151. package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
  152. package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
  153. package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
  154. package/Contexts/FocusableGroupContext/index.ts +7 -0
  155. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
  156. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  157. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  158. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  159. package/Contexts/ScreenContext/index.tsx +101 -30
  160. package/Contexts/ScreenDataContext/index.tsx +20 -0
  161. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  162. package/Decorators/ComponentsMapWrapper/withComponentsMapProvider.tsx +7 -2
  163. package/const.ts +6 -0
  164. package/events/index.ts +1 -0
  165. package/index.js +3 -0
  166. package/package.json +5 -5
  167. package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
  168. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  169. /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
  170. /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
  171. /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
@@ -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"] & {
@@ -118,6 +124,7 @@ const nativeStyles = {
118
124
  playerScreen: {
119
125
  flex: 1,
120
126
  backgroundColor: "black",
127
+ overflow: "hidden",
121
128
  },
122
129
  playerWrapper: {
123
130
  flex: 1,
@@ -145,34 +152,30 @@ const renderApplePlayer = ({
145
152
  }) => {
146
153
  const { title, summary } = item || {};
147
154
 
148
- if (!isApplePlatform()) {
155
+ if (!isAppleTV() || !player) {
156
+ return null;
157
+ }
158
+
159
+ if (isAudioContent) {
160
+ return (
161
+ <AudioPlayer
162
+ audio_item={item}
163
+ plugin_configuration={pluginConfiguration}
164
+ />
165
+ );
166
+ }
167
+
168
+ if (!playNextData && player.isPaused() && player.isAd() === false) {
149
169
  return null;
150
170
  }
151
171
 
152
172
  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
- </>
173
+ <ProgramInfo
174
+ title={title}
175
+ subtitle={summary}
176
+ plugin_configuration={pluginConfiguration}
177
+ videoStyle={videoStyle}
178
+ />
176
179
  );
177
180
  };
178
181
 
@@ -199,6 +202,7 @@ const PlayerContainerComponent = (props: Props) => {
199
202
  style: videoStyle,
200
203
  mode = VideoModalMode.MAXIMIZED,
201
204
  disableCastAction = false,
205
+ groupId,
202
206
  } = props;
203
207
 
204
208
  // Hooks
@@ -233,6 +237,9 @@ const PlayerContainerComponent = (props: Props) => {
233
237
  const screenData = useTargetScreenData(item);
234
238
  const { setVisible: showNavBar } = useSetNavbarState();
235
239
 
240
+ const { isActiveGesture, startComponentsAnimation, setPlayerAnimationState } =
241
+ useModalAnimationContext();
242
+
236
243
  const playerEvent = (event, ...args) => {
237
244
  playerManager.invokeHandler(event, ...args);
238
245
  };
@@ -245,8 +252,8 @@ const PlayerContainerComponent = (props: Props) => {
245
252
  return;
246
253
  }
247
254
 
248
- navigator.goBack();
249
255
  showNavBar(true);
256
+ navigator.goBack();
250
257
  }, [isModal, navigator.goBack, showNavBar]);
251
258
 
252
259
  const playEntry = (entry) => navigator.replaceTop(entry, { mode });
@@ -339,17 +346,31 @@ const PlayerContainerComponent = (props: Props) => {
339
346
  }
340
347
  }, [item?.id, item?.extensions?.resumeTime, player]);
341
348
 
349
+ const onVideoFullscreenPlayerWillPresent = React.useCallback(() => {
350
+ playerManager.invokeHandler("toggleFullscreen", {
351
+ fullscreen: true,
352
+ });
353
+ }, []);
354
+
355
+ const onVideoFullscreenPlayerWillDismiss = React.useCallback(() => {
356
+ playerManager.invokeHandler("toggleFullscreen", {
357
+ fullscreen: false,
358
+ });
359
+ }, []);
360
+
342
361
  // Util methods
343
362
  const toggleFullscreen = (event) => {
344
363
  const { fullscreen = false } = event || {};
345
364
 
346
- if (fullscreen) {
347
- playerEvent("onFullscreenPlayerWillPresent");
348
- navigator.fullscreenVideoModal();
349
- } else {
350
- playerEvent("onFullscreenPlayerWillDismiss");
351
- navigator.maximiseVideoModal();
352
- }
365
+ setTimeout(() => {
366
+ if (fullscreen) {
367
+ navigator.fullscreenVideoModal();
368
+ playerEvent("onFullscreenPlayerWillPresent");
369
+ } else {
370
+ navigator.maximiseVideoModal();
371
+ playerEvent("onFullscreenPlayerWillDismiss");
372
+ }
373
+ });
353
374
  };
354
375
 
355
376
  const onCellTap = (entry) => {
@@ -381,17 +402,27 @@ const PlayerContainerComponent = (props: Props) => {
381
402
  id: playerContainerId,
382
403
  listener: {
383
404
  onVideoEnd,
384
- onError: onError,
385
- onLoad: onLoad,
405
+ onError,
406
+ onLoad,
386
407
  onPlayerClose: close,
387
408
  onPlayerDetached: close,
409
+ onVideoFullscreenPlayerWillDismiss,
410
+ onVideoFullscreenPlayerWillPresent,
388
411
  },
389
412
  });
390
413
 
391
414
  return () => {
392
415
  player.removeListener(playerContainerId);
393
416
  };
394
- }, [player, onVideoEnd]);
417
+ }, [
418
+ player,
419
+ onVideoEnd,
420
+ onError,
421
+ onLoad,
422
+ close,
423
+ onVideoFullscreenPlayerWillDismiss,
424
+ onVideoFullscreenPlayerWillPresent,
425
+ ]);
395
426
 
396
427
  useEffect(() => {
397
428
  // TODO: We should not use playerManager here to connect buttons to player container
@@ -428,12 +459,16 @@ const PlayerContainerComponent = (props: Props) => {
428
459
  if (disableMiniPlayer) {
429
460
  navigator.closeVideoModal();
430
461
  } else {
431
- navigator.minimiseVideoModal();
462
+ setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
432
463
  }
433
464
  }
434
465
 
435
466
  if (mode === VideoModalMode.FULLSCREEN) {
436
467
  toggleFullscreen({ fullscreen: false });
468
+
469
+ if (!isModal) {
470
+ playerManager.closeNativePlayer();
471
+ }
437
472
  }
438
473
 
439
474
  return !!(
@@ -490,7 +525,7 @@ const PlayerContainerComponent = (props: Props) => {
490
525
  }
491
526
 
492
527
  return () => {
493
- playerEvent("player_did_close");
528
+ playerEvent("player_did_close", { item });
494
529
 
495
530
  if (!isModal) {
496
531
  showNavBar(true);
@@ -603,6 +638,7 @@ const PlayerContainerComponent = (props: Props) => {
603
638
  style={styles.focusableGroup}
604
639
  preferredFocus
605
640
  shouldUsePreferredFocus
641
+ groupId={groupId}
606
642
  >
607
643
  {/* Video player and components */}
608
644
  <View
@@ -621,9 +657,13 @@ const PlayerContainerComponent = (props: Props) => {
621
657
  }}
622
658
  focused={isInlineTV ? true : undefined}
623
659
  autoplay={true}
624
- controls={isAppleTV() && !isInlineTV}
660
+ controls={false}
625
661
  disableCastAction={disableCastAction}
626
- docked={navigator.isVideoModalDocked()}
662
+ docked={
663
+ navigator.isVideoModalDocked() &&
664
+ !startComponentsAnimation &&
665
+ !isActiveGesture
666
+ }
627
667
  entry={item}
628
668
  fullscreen={mode === VideoModalMode.FULLSCREEN}
629
669
  inline={inline}
@@ -641,6 +681,7 @@ const PlayerContainerComponent = (props: Props) => {
641
681
  setNextVideoPreloadThresholdPercentage={
642
682
  setNextVideoPreloadThresholdPercentage
643
683
  }
684
+ startComponentsAnimation={startComponentsAnimation}
644
685
  >
645
686
  {renderApplePlayer(applePlayerProps)}
646
687
  </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,22 +4,18 @@ 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,
11
- useNavigationPluginData,
12
10
  } from "@applicaster/zapp-react-native-utils/reactHooks";
13
11
  import { GeneralContentScreen } from "../../GeneralContentScreen";
14
12
  import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
15
13
  import { utilsLogger } from "@applicaster/zapp-react-native-utils/logger";
16
- import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils";
17
- import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
18
- import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
19
14
  import {
20
15
  useNavbarState,
21
16
  useSetNavbarState,
22
17
  } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
18
+ import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
23
19
 
24
20
  type Props = {
25
21
  screenId: string;
@@ -51,9 +47,7 @@ export const River = (props: Props) => {
51
47
  const { setTitle: setScreenTitle, setSummary: setScreenSummary } =
52
48
  useSetNavbarState();
53
49
 
54
- const navigationPluginData = useNavigationPluginData();
55
-
56
- const { rivers } = usePickFromState(["rivers"]);
50
+ const rivers = useRivers();
57
51
  const isV2 = useLayoutVersion({ isV2: true });
58
52
 
59
53
  const river = React.useMemo(() => rivers?.[screenId], [screenId]);
@@ -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!`,
@@ -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",