@applicaster/zapp-react-native-ui-components 13.0.0-alpha.3013227535 → 13.0.0-alpha.3044917147

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 (159) hide show
  1. package/Components/AppContainer/__tests__/AppContainer.test.js +4 -6
  2. package/Components/AudioPlayer/AudioPlayer.tsx +5 -9
  3. package/Components/AudioPlayer/AudioPlayerLayout.tsx +8 -9
  4. package/Components/AudioPlayer/__tests__/Runtime.test.js +5 -8
  5. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayer.test.js.snap +54 -111
  6. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayerLayout.test.js.snap +36 -20
  7. package/Components/AudioPlayer/__tests__/artWork.test.js +3 -5
  8. package/Components/AudioPlayer/__tests__/audioPlayer.test.js +3 -5
  9. package/Components/AudioPlayer/__tests__/audioPlayerLayout.test.js +7 -8
  10. package/Components/AudioPlayer/__tests__/channel.test.js +3 -5
  11. package/Components/AudioPlayer/__tests__/summary.test.js +3 -5
  12. package/Components/AudioPlayer/__tests__/title.test.js +3 -5
  13. package/Components/AudioPlayer/helpers.tsx +1 -2
  14. package/Components/BaseFocusable/index.tsx +1 -2
  15. package/Components/Cell/Cell.tsx +4 -13
  16. package/Components/Cell/CellWithFocusable.tsx +50 -59
  17. package/Components/Cell/CellWrapper.tsx +18 -0
  18. package/Components/Cell/TvOSCellComponent.tsx +20 -22
  19. package/Components/Cell/__tests__/CellWIthFocusable.test.js +13 -6
  20. package/Components/Cell/index.js +3 -1
  21. package/Components/Cell/styles.ts +17 -0
  22. package/Components/CellRendererResolver/__tests__/cellRendererResolver.test.js +46 -48
  23. package/Components/CellRendererResolver/index.ts +2 -1
  24. package/Components/ComponentResolver/__tests__/__snapshots__/componentResolver.test.js.snap +1 -42
  25. package/Components/ComponentResolver/__tests__/componentResolver.test.js +16 -22
  26. package/Components/ContentScreen/__tests__/contentScreen.test.js +3 -3
  27. package/Components/ErrorScreen/index.tsx +14 -6
  28. package/Components/Focusable/Focusable.tsx +1 -0
  29. package/Components/Focusable/FocusableTvOS.tsx +5 -1
  30. package/Components/Focusable/index.android.tsx +10 -3
  31. package/Components/FocusableCell/index.tsx +1 -1
  32. package/Components/FocusableGroup/FocusableTvOS.tsx +34 -86
  33. package/Components/FocusableGroup/index.tsx +0 -3
  34. package/Components/FocusableList/index.tsx +26 -9
  35. package/Components/FocusableScrollView/index.tsx +43 -13
  36. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +3 -0
  37. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +4 -2
  38. package/Components/GeneralContentScreen/utils/useEventAlerts.ts +30 -0
  39. package/Components/HandlePlayable/HandlePlayable.tsx +21 -27
  40. package/Components/Layout/TV/LayoutBackground.tsx +28 -0
  41. package/Components/Layout/TV/NavBarContainer.tsx +19 -23
  42. package/Components/Layout/TV/ScreenContainer.tsx +8 -2
  43. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +5 -0
  44. package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +2 -1
  45. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +3 -2
  46. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +3 -2
  47. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +15 -10
  48. package/Components/Layout/TV/__tests__/index.test.tsx +8 -2
  49. package/Components/Layout/TV/index.tsx +6 -20
  50. package/Components/Layout/TV/index.web.tsx +4 -1
  51. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -3
  52. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +4 -3
  53. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +11 -7
  54. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +2 -2
  55. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/const.ts +3 -0
  56. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +6 -6
  57. package/Components/MasterCell/SharedUI/ProgressBar/hooks/useFillInPercent.ts +2 -8
  58. package/Components/MasterCell/__tests__/__snapshots__/elementMapper.test.js.snap +4 -12
  59. package/Components/MasterCell/__tests__/__snapshots__/masterCell.test.js.snap +0 -229
  60. package/Components/MasterCell/__tests__/elementMapper.test.js +18 -5
  61. package/Components/MasterCell/__tests__/masterCell.test.js +26 -26
  62. package/Components/MasterCell/hooks/useAsyncRendering/MasterCellAsyncRenderManager.ts +2 -2
  63. package/Components/MasterCell/utils/behaviorProvider.ts +136 -0
  64. package/Components/MasterCell/utils/index.ts +8 -134
  65. package/Components/ModalComponent/BottomSheetModalContent.tsx +66 -54
  66. package/Components/ModalComponent/Button/Item.tsx +6 -5
  67. package/Components/ModalComponent/Button/assets.ts +1 -1
  68. package/Components/ModalComponent/Button/index.tsx +45 -62
  69. package/Components/ModalComponent/Header/index.tsx +12 -11
  70. package/Components/ModalComponent/utils.ts +55 -7
  71. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +2 -2
  72. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +2 -2
  73. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -5
  74. package/Components/OfflineHandler/utils/index.ts +1 -1
  75. package/Components/PlayerContainer/PlayerContainer.tsx +63 -54
  76. package/Components/PlayerContainer/ProgramInfo/index.tsx +2 -2
  77. package/Components/PlayerImageBackground/index.tsx +1 -1
  78. package/Components/River/ComponentsMap/ComponentsMap.tsx +53 -68
  79. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +78 -51
  80. package/Components/River/RiverFooter.tsx +39 -9
  81. package/Components/River/RiverItem.tsx +43 -3
  82. package/Components/River/TV/index.tsx +6 -1
  83. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +107 -32
  84. package/Components/River/__tests__/__snapshots__/river.test.js.snap +20 -82
  85. package/Components/River/__tests__/componentsMap.test.js +3 -4
  86. package/Components/River/__tests__/river.test.js +75 -40
  87. package/Components/RouteManager/TestId.tsx +1 -5
  88. package/Components/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +10 -4
  89. package/Components/RouteManager/__tests__/routeManager.test.js +29 -14
  90. package/Components/RouteManager/__tests__/testId.test.js +4 -6
  91. package/Components/Screen/TV/__tests__/index.web.test.tsx +26 -0
  92. package/Components/Screen/TV/index.web.tsx +5 -6
  93. package/Components/Screen/__tests__/Screen.test.tsx +22 -14
  94. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -2
  95. package/Components/Screen/hooks.ts +53 -0
  96. package/Components/Screen/index.tsx +16 -43
  97. package/Components/ScreenResolver/__tests__/screenResolver.test.js +91 -70
  98. package/Components/Tabs/Tab.tsx +5 -3
  99. package/Components/TextInputTv/index.tsx +2 -1
  100. package/Components/TopMarginApplicator/TopMarginApplicator.tsx +2 -3
  101. package/Components/TrackedView/index.tsx +1 -0
  102. package/Components/Transitioner/AnimationManager.js +15 -15
  103. package/Components/Transitioner/Scene.tsx +1 -1
  104. package/Components/Transitioner/Transitioner.tsx +3 -3
  105. package/Components/Transitioner/__tests__/Scene.test.js +25 -14
  106. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +12 -68
  107. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +2 -2
  108. package/Components/Transitioner/__tests__/transitioner.test.js +13 -4
  109. package/Components/VideoLive/PlayerLiveImageComponent.tsx +7 -3
  110. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  111. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +1 -1
  112. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +46 -30
  113. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +3 -3
  114. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +45 -21
  115. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +13 -6
  116. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +108 -0
  117. package/Components/VideoModal/ModalAnimation/const.ts +5 -0
  118. package/Components/VideoModal/ModalAnimation/index.ts +2 -0
  119. package/Components/VideoModal/ModalAnimation/utils.ts +30 -0
  120. package/Components/VideoModal/PlayerDetails.tsx +18 -10
  121. package/Components/VideoModal/PlayerWrapper.tsx +37 -24
  122. package/Components/VideoModal/VideoModal.tsx +19 -4
  123. package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +2 -40
  124. package/Components/VideoModal/utils.ts +3 -4
  125. package/Components/Viewport/ViewportAware/__tests__/__snapshots__/viewportAware.test.js.snap +24 -36
  126. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +12 -18
  127. package/Components/Viewport/__tests__/utils.test.js +5 -4
  128. package/Components/ZappUIComponent/Placeholder.tsx +3 -1
  129. package/Components/ZappUIComponent/__tests__/{ZappUIComponent.test.js → ZappUIComponent.test.tsx} +26 -10
  130. package/Components/ZappUIComponent/index.tsx +4 -17
  131. package/Components/default-cell-renderer/__tests__/defaultCellRenderer.test.tsx +20 -17
  132. package/Components/default-cell-renderer/viewTrees/tv/DefaultCell/__tests__/DefaultCell.test.tsx +15 -11
  133. package/Contexts/ComponentsMapOffsetContext/index.tsx +49 -0
  134. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +4 -10
  135. package/Contexts/HeaderOffsetContext/index.tsx +4 -6
  136. package/Contexts/ScreenContext/index.tsx +3 -10
  137. package/Contexts/ScreenLayoutContext/index.tsx +5 -3
  138. package/Decorators/Navigator/__tests__/navigator.test.js +16 -16
  139. package/Decorators/Navigator/index.tsx +1 -1
  140. package/Decorators/RiverResolver/__tests__/riverResolver.test.tsx +18 -24
  141. package/Decorators/RiverResolver/index.tsx +6 -8
  142. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +29 -22
  143. package/Decorators/ZappPipesDataConnector/index.tsx +4 -30
  144. package/package.json +7 -11
  145. package/tsconfig.json +2 -3
  146. package/.babelrc +0 -8
  147. package/Components/Cell/CellStyles/FallbackCellStyle/index.js +0 -157
  148. package/Components/Cell/CellStyles/Hero/index.js +0 -111
  149. package/Components/Cell/CellStyles/ScreenSelector/index.js +0 -68
  150. package/Components/Cell/CellStyles/cellStylesResolver.ts +0 -19
  151. package/Components/Cell/CellStyles/colors.js +0 -40
  152. package/Components/Cell/CellStyles/index.js +0 -15
  153. package/Components/CellRendererResolver/__tests__/__snapshots__/cellRendererResolver.test.js.snap +0 -53
  154. package/Components/RouteManager/__tests__/__snapshots__/testId.test.js.snap +0 -17
  155. package/Components/ScreenResolver/__tests__/__snapshots__/screenResolver.test.js.snap +0 -17
  156. package/Components/ZappUIComponent/__tests__/__snapshots__/ZappUIComponent.test.js.snap +0 -91
  157. package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +0 -338
  158. package/Decorators/Navigator/__tests__/__snapshots__/navigator.test.js.snap +0 -18
  159. package/Decorators/RiverResolver/__tests__/__snapshots__/riverResolver.test.tsx.snap +0 -28
@@ -1,17 +1,22 @@
1
1
  import * as React from "react";
2
+ import { useEffect, useReducer } from "react";
2
3
  // @ts-ignore
3
- import { View, ViewStyle, TVMenuControl } from "react-native";
4
+ import { TVMenuControl, View, ViewStyle } from "react-native";
4
5
  import * as R from "ramda";
5
6
  import uuid from "uuid/v4";
6
7
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
7
8
  import {
8
- isApplePlatform,
9
- platformSelect,
10
9
  isTV,
10
+ platformSelect,
11
+ isAndroidTVPlatform,
12
+ isTvOSPlatform,
11
13
  } from "@applicaster/zapp-react-native-utils/reactUtils";
14
+ import {
15
+ isAudioItem,
16
+ isInlineTV as isInlineTVUtil,
17
+ } from "@applicaster/zapp-react-native-utils/playerUtils";
12
18
 
13
19
  import { TVEventHandlerComponent } from "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent";
14
- import { useEffect, useReducer } from "react";
15
20
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
16
21
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
17
22
  import {
@@ -20,7 +25,7 @@ import {
20
25
  } from "@applicaster/zapp-react-native-utils/reactHooks";
21
26
 
22
27
  import { PlayerStateContext } from "../../Contexts/PlayerStateContext";
23
- import { isAppleTV } from "../../Helpers/Platform";
28
+
24
29
  import {
25
30
  QUICK_BRICK_EVENTS,
26
31
  QuickBrickEvent,
@@ -29,10 +34,10 @@ import {
29
34
  import { ProgramInfo } from "./ProgramInfo";
30
35
  import { AudioPlayer } from "../AudioPlayer";
31
36
  import {
32
- playerContainerLogger,
33
37
  log_debug,
34
38
  log_info,
35
39
  log_warning,
40
+ playerContainerLogger,
36
41
  } from "./logger";
37
42
  import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
38
43
  import { useSetNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
@@ -53,8 +58,8 @@ import { usePlayNextOverlay } from "@applicaster/zapp-react-native-utils/appUtil
53
58
  import { PlayNextState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/OverlaysObserver";
54
59
 
55
60
  import {
56
- useModalAnimationContext,
57
61
  PlayerAnimationStateEnum,
62
+ useModalAnimationContext,
58
63
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
59
64
 
60
65
  type Props = {
@@ -93,6 +98,25 @@ export type PlayNextData = {
93
98
 
94
99
  const focusableBottomContainerId = "player-container-bottom";
95
100
 
101
+ const isAndroidTV = isAndroidTVPlatform();
102
+
103
+ const isTvOS = isTvOSPlatform();
104
+
105
+ // height for container with additional content below player
106
+ const INLINE_CONTAINER_CONTENT_HEIGHT = 400;
107
+
108
+ const withBorderHack = () => {
109
+ if (isAndroidTV) {
110
+ /* @HACK: see GH#7269 */
111
+ return {
112
+ borderWidth: 1,
113
+ borderColor: "transparent",
114
+ };
115
+ }
116
+
117
+ return {};
118
+ };
119
+
96
120
  // Styles
97
121
  const webStyles = {
98
122
  focusableGroup: {
@@ -110,7 +134,7 @@ const webStyles = {
110
134
  flex: "initial",
111
135
  },
112
136
  inlineRiver: {
113
- height: 400,
137
+ height: INLINE_CONTAINER_CONTENT_HEIGHT,
114
138
  },
115
139
  };
116
140
 
@@ -128,7 +152,7 @@ const nativeStyles = {
128
152
  flex: 1,
129
153
  },
130
154
  inlineRiver: {
131
- height: 400,
155
+ height: INLINE_CONTAINER_CONTENT_HEIGHT,
132
156
  },
133
157
  };
134
158
 
@@ -148,12 +172,11 @@ const renderApplePlayer = ({
148
172
  videoStyle,
149
173
  playNextData,
150
174
  }) => {
151
- const { title, summary } = item || {};
152
-
153
- if (!isAppleTV() || !player) {
175
+ if (!isTvOS || !player) {
154
176
  return null;
155
177
  }
156
178
 
179
+ // render audio_player for tvos
157
180
  if (isAudioContent) {
158
181
  return (
159
182
  <AudioPlayer
@@ -167,6 +190,8 @@ const renderApplePlayer = ({
167
190
  return null;
168
191
  }
169
192
 
193
+ const { title, summary } = item || {};
194
+
170
195
  return (
171
196
  <ProgramInfo
172
197
  title={title}
@@ -177,21 +202,6 @@ const renderApplePlayer = ({
177
202
  );
178
203
  };
179
204
 
180
- const renderRestPlayers = ({ item, showAudioPlayer, pluginConfiguration }) => {
181
- if (isApplePlatform()) {
182
- return null;
183
- }
184
-
185
- return (
186
- showAudioPlayer && (
187
- <AudioPlayer
188
- audio_item={item}
189
- plugin_configuration={pluginConfiguration}
190
- />
191
- )
192
- );
193
- };
194
-
195
205
  const PlayerContainerComponent = (props: Props) => {
196
206
  const {
197
207
  Player,
@@ -321,7 +331,7 @@ const PlayerContainerComponent = (props: Props) => {
321
331
 
322
332
  setState({ error: errorObj });
323
333
 
324
- if (!isAppleTV()) {
334
+ if (!isTvOS) {
325
335
  setTimeout(() => {
326
336
  close();
327
337
  }, 800);
@@ -335,9 +345,9 @@ const PlayerContainerComponent = (props: Props) => {
335
345
 
336
346
  const resumeTime = Number(item?.extensions?.resumeTime);
337
347
 
338
- // TODO: This is temp hack, will be removed on next pr with player refactoring
348
+ // Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast
339
349
  if (
340
- !!playerManager.getActivePlayer()?.getContinueWatchingOffset === false &&
350
+ !playerManager.getActivePlayer()?.hasResumePosition() &&
341
351
  !isNaN(resumeTime)
342
352
  ) {
343
353
  player?.seekTo(resumeTime);
@@ -478,9 +488,7 @@ const PlayerContainerComponent = (props: Props) => {
478
488
  // TODO: Skip hook call on TV platform
479
489
  useBackHandler(backHandler);
480
490
 
481
- const isAudioContent =
482
- typeof item?.content?.type === "string" &&
483
- item?.content?.type.includes("audio");
491
+ const isAudioContent = isAudioItem(item);
484
492
 
485
493
  useEffect(() => {
486
494
  if (!isAudioContent) {
@@ -514,6 +522,12 @@ const PlayerContainerComponent = (props: Props) => {
514
522
  useEffect(() => {
515
523
  playerEvent("source_changed", { item });
516
524
 
525
+ return () => {
526
+ playerEvent("player_did_close", { item });
527
+ };
528
+ }, [item?.id, Player]);
529
+
530
+ useEffect(() => {
517
531
  if (!isModal) {
518
532
  showNavBar(false);
519
533
  }
@@ -523,13 +537,11 @@ const PlayerContainerComponent = (props: Props) => {
523
537
  }
524
538
 
525
539
  return () => {
526
- playerEvent("player_did_close", { item });
527
-
528
540
  if (!isModal) {
529
541
  showNavBar(true);
530
542
  }
531
543
  };
532
- }, [showNavBar, /* added as suggested: */ item?.id, isModal, Player]);
544
+ }, [showNavBar, isModal, Player]);
533
545
 
534
546
  useEffect(() => {
535
547
  if (prevItemId && !R.equals(prevItemId, item?.id)) {
@@ -550,10 +562,7 @@ const PlayerContainerComponent = (props: Props) => {
550
562
 
551
563
  const uri = item?.content ? item.content?.src : null;
552
564
 
553
- const isInlineTV =
554
- screenData?.ui_components &&
555
- screenData?.ui_components?.length > 0 &&
556
- isTV();
565
+ const isInlineTV = isInlineTVUtil(screenData);
557
566
 
558
567
  const inline =
559
568
  [VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(mode) ||
@@ -578,7 +587,7 @@ const PlayerContainerComponent = (props: Props) => {
578
587
  );
579
588
  }
580
589
 
581
- if (screen_background_color) {
590
+ if (screen_background_color && mode !== VideoModalMode.FULLSCREEN) {
582
591
  updatedStyles.playerScreen.backgroundColor = screen_background_color;
583
592
  }
584
593
 
@@ -596,6 +605,7 @@ const PlayerContainerComponent = (props: Props) => {
596
605
  tv_component_container_height,
597
606
  screen_background_color,
598
607
  isInlineTV,
608
+ mode,
599
609
  ]);
600
610
 
601
611
  const applePlayerProps = {
@@ -607,12 +617,6 @@ const PlayerContainerComponent = (props: Props) => {
607
617
  playNextData,
608
618
  };
609
619
 
610
- const restPlayerProps = {
611
- item,
612
- showAudioPlayer: isAudioContent,
613
- pluginConfiguration,
614
- };
615
-
616
620
  return (
617
621
  <PlayerStateContext.Provider value={value}>
618
622
  <PlayerContainerContextProvider
@@ -640,7 +644,14 @@ const PlayerContainerComponent = (props: Props) => {
640
644
  testID={"player-screen-container"}
641
645
  >
642
646
  {/* Player container */}
643
- <View style={styles.playerWrapper} testID={"player-wrapper"}>
647
+ <View
648
+ style={[
649
+ styles.playerWrapper,
650
+ // eslint-disable-next-line react-native/no-inline-styles, react-native/no-color-literals
651
+ withBorderHack(),
652
+ ]}
653
+ testID={"player-wrapper"}
654
+ >
644
655
  <PlayerFocusableWrapperView
645
656
  nextFocusDown={context.bottomFocusableId}
646
657
  >
@@ -681,12 +692,10 @@ const PlayerContainerComponent = (props: Props) => {
681
692
  </Player>
682
693
  </PlayerFocusableWrapperView>
683
694
 
684
- {renderRestPlayers(restPlayerProps)}
685
-
686
- {state.error && <ErrorDisplay error={state.error} />}
695
+ {state.error ? <ErrorDisplay error={state.error} /> : null}
687
696
  </View>
688
697
  {/* Components container */}
689
- {isInlineTV && context.showComponentsContainer && (
698
+ {isInlineTV && context.showComponentsContainer ? (
690
699
  <View
691
700
  style={styles.inlineRiver}
692
701
  testID={"inline-river-component-wrapper"}
@@ -706,7 +715,7 @@ const PlayerContainerComponent = (props: Props) => {
706
715
  screenId={screenData.id}
707
716
  key={item.id}
708
717
  groupId={FocusableGroupMainContainerId}
709
- cellTapAction={(event) => onCellTap(event)}
718
+ cellTapAction={onCellTap}
710
719
  extraAnchorPointYOffset={-600}
711
720
  isScreenWrappedInContainer={true}
712
721
  containerHeight={styles.inlineRiver.height}
@@ -717,7 +726,7 @@ const PlayerContainerComponent = (props: Props) => {
717
726
  )}
718
727
  </ComponentFocusableWrapperView>
719
728
  </View>
720
- )}
729
+ ) : null}
721
730
  </View>
722
731
  </FocusableGroup>
723
732
  </TVEventHandlerComponent>
@@ -114,12 +114,12 @@ function ProgramInfoComponent({
114
114
  return (
115
115
  <View style={container}>
116
116
  <View style={programStyles.programInfo.container}>
117
- {logo && (
117
+ {logo ? (
118
118
  <Image
119
119
  source={{ uri: logo }}
120
120
  style={programStyles.programInfo.logo}
121
121
  />
122
- )}
122
+ ) : null}
123
123
  {!isAudioContent && transportInfo}
124
124
  </View>
125
125
  </View>
@@ -39,7 +39,7 @@ const PlayerImageBackgroundComponent = ({
39
39
  return (
40
40
  <View
41
41
  style={
42
- playerAnimationState === PlayerAnimationStateEnum.maximaze
42
+ playerAnimationState === PlayerAnimationStateEnum.maximize
43
43
  ? defaultImageDimensions
44
44
  : style
45
45
  }
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
- import { View, StyleSheet, Platform, FlatList } from "react-native";
3
+ import { View, StyleSheet, 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";
@@ -24,6 +24,10 @@ import { withComponentsMapProvider } from "@applicaster/zapp-react-native-ui-com
24
24
  import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
25
25
  import { useShallow } from "zustand/react/shallow";
26
26
 
27
+ import { isAndroidPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
28
+
29
+ const isAndroid = isAndroidPlatform();
30
+
27
31
  type Props = {
28
32
  feed: ZappFeed;
29
33
  groupId?: string;
@@ -34,6 +38,7 @@ type Props = {
34
38
  getStaticComponentFeed: any;
35
39
  pullToRefreshPipesV1RefreshingStateUpdater: () => boolean;
36
40
  refreshingPipesV1?: boolean;
41
+ stickyHeaderIndices?: number[];
37
42
  };
38
43
 
39
44
  const styles = StyleSheet.create({
@@ -61,6 +66,7 @@ function ComponentsMapComponent(props: Props) {
61
66
  // TODO: Remove when pipes v1 is deprecated.
62
67
  pullToRefreshPipesV1RefreshingStateUpdater,
63
68
  refreshingPipesV1,
69
+ stickyHeaderIndices,
64
70
  } = props;
65
71
 
66
72
  const flatListRef = React.useRef<FlatList | null>(null);
@@ -70,21 +76,6 @@ function ComponentsMapComponent(props: Props) {
70
76
 
71
77
  const [flatListHeight, setFlatListHeight] = React.useState(null);
72
78
 
73
- const {
74
- isAnyLoading,
75
- isAllLoaded,
76
- waitForAllComponents,
77
- onLoadFinished,
78
- onLoadFailed,
79
- shouldShowLoadingError,
80
- isAnyLoaded,
81
- arePreviousComponentsLoaded,
82
- } = useLoadingState(feed?.entry?.length || components.length);
83
-
84
- const theme = useTheme();
85
-
86
- const logTimestamp = useProfilerLogging();
87
-
88
79
  const riverComponents = React.useMemo(() => {
89
80
  if (feed?.entry?.length < components.length) {
90
81
  return R.slice(0, feed?.entry?.length, components);
@@ -93,55 +84,42 @@ function ComponentsMapComponent(props: Props) {
93
84
  return components;
94
85
  }, [components, feed]);
95
86
 
87
+ const logTimestamp = useProfilerLogging();
88
+
89
+ const onLoadDone = React.useCallback(() => {
90
+ logTimestamp(riverId?.toString());
91
+ }, [logTimestamp]);
92
+
93
+ const { loadingState, onLoadFinished, onLoadFailed, shouldShowLoadingError } =
94
+ useLoadingState(riverComponents.length, onLoadDone);
95
+
96
+ const theme = useTheme();
97
+
96
98
  const renderRiverItem = React.useCallback(
97
99
  ({ item, index }) => {
98
- const readyToBeDisplayed = arePreviousComponentsLoaded(index);
99
-
100
- const styles = {
101
- display: !readyToBeDisplayed ? "none" : ("flex" as "none" | "flex"),
100
+ const riverItemProps = {
101
+ riverId,
102
+ item,
103
+ index,
104
+ isScreenWrappedInContainer,
105
+ feed,
106
+ groupId,
107
+ onLoadFailed,
108
+ onLoadFinished,
109
+ getStaticComponentFeed,
110
+ isLast: isLast(index, riverComponents.length),
111
+ loadingState,
102
112
  };
103
113
 
104
114
  return (
105
- <View style={styles}>
106
- <ScreenLoadingMeasurementsListItemWrapper index={index}>
107
- <RiverItem
108
- {...{
109
- readyToBeDisplayed,
110
- riverId,
111
- item,
112
- index,
113
- isScreenWrappedInContainer,
114
- feed,
115
- groupId,
116
- onLoadFailed,
117
- onLoadFinished,
118
- getStaticComponentFeed,
119
- isLast: isLast(index, riverComponents.length),
120
- }}
121
- />
122
- </ScreenLoadingMeasurementsListItemWrapper>
123
- </View>
115
+ <ScreenLoadingMeasurementsListItemWrapper index={index}>
116
+ <RiverItem {...riverItemProps} />
117
+ </ScreenLoadingMeasurementsListItemWrapper>
124
118
  );
125
119
  },
126
- [
127
- feed,
128
- getStaticComponentFeed,
129
- arePreviousComponentsLoaded,
130
- onLoadFailed,
131
- onLoadFinished,
132
- ]
120
+ [feed, getStaticComponentFeed, onLoadFailed, onLoadFinished]
133
121
  );
134
122
 
135
- const renderFooter = React.useCallback(() => {
136
- return (
137
- <RiverFooter
138
- visible={isAnyLoading}
139
- flatListHeight={flatListHeight}
140
- isAnyLoaded={isAnyLoaded || waitForAllComponents}
141
- />
142
- );
143
- }, [flatListHeight, isAnyLoading, isAnyLoaded]);
144
-
145
123
  const screenStyle = React.useMemo(
146
124
  () => ({
147
125
  paddingTop: ifEmptyUseFallback(
@@ -179,12 +157,6 @@ function ComponentsMapComponent(props: Props) {
179
157
 
180
158
  usePipesCacheReset(riverId, riverComponents);
181
159
 
182
- React.useEffect(() => {
183
- if (isAllLoaded) {
184
- logTimestamp(riverId?.toString());
185
- }
186
- }, [isAllLoaded]);
187
-
188
160
  const refreshControl = React.useMemo(
189
161
  () =>
190
162
  pullToRefreshEnabled ? (
@@ -280,6 +252,11 @@ function ComponentsMapComponent(props: Props) {
280
252
  }
281
253
  }, []);
282
254
 
255
+ const contentContainerStyle = React.useMemo(
256
+ () => (isScreenWrappedInContainer ? {} : screenStyle),
257
+ [isScreenWrappedInContainer, screenStyle]
258
+ );
259
+
283
260
  if (shouldShowLoadingError) {
284
261
  return <RiverError />;
285
262
  }
@@ -291,7 +268,7 @@ function ComponentsMapComponent(props: Props) {
291
268
  <View style={styles.container}>
292
269
  <ScreenLoadingMeasurements
293
270
  riverId={riverId}
294
- numberOfComponents={feed?.entry?.length || components.length}
271
+ numberOfComponents={riverComponents.length}
295
272
  >
296
273
  <ViewportTracker>
297
274
  <FlatList
@@ -300,11 +277,12 @@ function ComponentsMapComponent(props: Props) {
300
277
  }}
301
278
  // Fix for WebView rerender crashes on Android API 28+
302
279
  // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
303
- overScrollMode={Platform.OS === "android" ? "never" : "auto"}
280
+ overScrollMode={isAndroid ? "never" : "auto"}
304
281
  scrollIndicatorInsets={scrollIndicatorInsets}
305
282
  extraData={feed}
283
+ stickyHeaderIndices={stickyHeaderIndices}
306
284
  onLayout={handleOnLayout}
307
- removeClippedSubviews
285
+ removeClippedSubviews={isAndroid}
308
286
  initialNumToRender={3}
309
287
  maxToRenderPerBatch={10}
310
288
  windowSize={12}
@@ -312,16 +290,23 @@ function ComponentsMapComponent(props: Props) {
312
290
  keyExtractor={keyExtractor}
313
291
  renderItem={renderRiverItem}
314
292
  data={riverComponents}
315
- contentContainerStyle={
316
- isScreenWrappedInContainer ? {} : screenStyle
293
+ contentContainerStyle={contentContainerStyle}
294
+ ListFooterComponent={
295
+ <RiverFooter
296
+ flatListHeight={flatListHeight}
297
+ loadingState={loadingState}
298
+ />
317
299
  }
318
- ListFooterComponent={renderFooter}
319
300
  refreshControl={refreshControl}
320
301
  onScrollBeginDrag={onScrollBeginDrag}
321
302
  onScroll={onScroll}
322
303
  onMomentumScrollEnd={_onMomentumScrollEnd}
323
304
  onScrollEndDrag={_onScrollEndDrag}
324
305
  scrollEventThrottle={16}
306
+ maintainVisibleContentPosition={{
307
+ minIndexForVisible: 0,
308
+ autoscrollToTopThreshold: 10,
309
+ }}
325
310
  {...scrollViewExtraProps}
326
311
  />
327
312
  </ViewportTracker>
@@ -1,50 +1,86 @@
1
1
  import * as React from "react";
2
- import * as R from "ramda";
3
-
4
- const allTrue = R.all(R.equals(true));
5
- const anyFalse = R.any(R.equals(false));
6
- const anyTrue = R.any(R.equals(true));
2
+ import { isNil, set, lensIndex, T, slice } from "ramda";
3
+ import { BehaviorSubject } from "rxjs";
4
+ import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
7
5
 
8
6
  const reducer = (state, { payload }) => {
9
- if (!R.isNil(payload) && !state[payload]) {
10
- return R.set(R.lensIndex(payload), true)(state);
7
+ if (!isNil(payload) && !state[payload]) {
8
+ return set(lensIndex(payload), true)(state);
11
9
  }
12
10
 
13
11
  return state;
14
12
  };
15
13
 
16
- type Return = {
17
- isAnyLoading: boolean;
18
- isAllLoaded: boolean;
14
+ type LoadingState = {
15
+ index: number;
16
+ done: boolean;
19
17
  waitForAllComponents: boolean;
18
+ };
19
+
20
+ type Return = {
21
+ loadingState: BehaviorSubject<LoadingState>;
20
22
  onLoadFinished: (index: number) => void;
21
23
  onLoadFailed: ({ error, index }: { error: Error; index: number }) => void;
22
24
  shouldShowLoadingError: boolean;
23
- isAnyLoaded: boolean;
24
25
  arePreviousComponentsLoaded: (index: number) => boolean;
25
26
  };
26
27
 
27
- type Action = { payload: { index: number } };
28
-
29
- type Loaded = true;
30
- type Loading = false;
31
-
32
- type LoadingState = Array<Loaded | Loading>;
33
-
34
28
  // TODO: Take this value from Zapp configuration, when feature is added to GeneralScreen
35
29
  const SHOULD_FAIL_ON_COMPONENT_LOADING = false;
36
30
 
37
- export const useLoadingState = (count: number): Return => {
31
+ const createLoadingStateObservable = (count: number) =>
32
+ new BehaviorSubject<LoadingState>({
33
+ index: -1,
34
+ done: count === 0,
35
+ waitForAllComponents: SHOULD_FAIL_ON_COMPONENT_LOADING,
36
+ });
37
+
38
+ export const useLoadingState = (
39
+ count: number,
40
+ onLoadDone: () => void
41
+ ): Return => {
42
+ const componentStateRef = React.useRef(new Array(count).fill(false));
38
43
  const [loadingError, setLoadingError] = React.useState(null);
39
44
 
40
- const [componentsState, dispatch] = React.useReducer<
41
- React.Reducer<LoadingState, Action>
42
- >(reducer, new Array(count).fill(false));
45
+ const loadingState = useRefWithInitialValue<BehaviorSubject<LoadingState>>(
46
+ () => createLoadingStateObservable(count)
47
+ );
48
+
49
+ const arePreviousComponentsLoaded = React.useCallback((index) => {
50
+ if (index === 0) {
51
+ return true;
52
+ }
43
53
 
44
- const handleComponentLoaded = React.useCallback((index) => {
45
- dispatch({ payload: index });
54
+ const componentsBefore = slice(0, index, componentStateRef.current);
55
+
56
+ return componentsBefore.every(T);
46
57
  }, []);
47
58
 
59
+ const dispatch = React.useCallback(({ payload }) => {
60
+ const newState = reducer(componentStateRef.current, { payload });
61
+ componentStateRef.current = newState;
62
+ const isDone = arePreviousComponentsLoaded(count - 1);
63
+
64
+ const state = loadingState.current.getValue();
65
+
66
+ const newLoadingState = {
67
+ ...state,
68
+ index: state.index < payload ? payload : state.index,
69
+ done: isDone,
70
+ };
71
+
72
+ loadingState.current.next(newLoadingState);
73
+
74
+ if (isDone) {
75
+ onLoadDone();
76
+ }
77
+ }, []);
78
+
79
+ const handleComponentLoaded = React.useCallback(
80
+ (index) => dispatch({ payload: index }),
81
+ []
82
+ );
83
+
48
84
  const handleComponentLoadErrorWhenNeedToFail = React.useCallback(
49
85
  ({ error }) => {
50
86
  if (error !== loadingError) {
@@ -55,35 +91,26 @@ export const useLoadingState = (count: number): Return => {
55
91
  );
56
92
 
57
93
  const handleComponentLoadErrorWhenNoNeedToFail = React.useCallback(
58
- ({ index }) => {
59
- handleComponentLoaded(index);
60
- },
94
+ ({ index }) => handleComponentLoaded(index),
61
95
  []
62
96
  );
63
97
 
64
- const arePreviousComponentsLoaded = React.useCallback(
65
- (index) => {
66
- if (index === 0) {
67
- return true;
68
- }
69
-
70
- const componentsBefore = R.slice(0, index, componentsState);
71
-
72
- return allTrue(componentsBefore);
73
- },
74
- [componentsState]
98
+ return React.useMemo(
99
+ () => ({
100
+ loadingState: loadingState.current,
101
+ onLoadFinished: handleComponentLoaded,
102
+ onLoadFailed: SHOULD_FAIL_ON_COMPONENT_LOADING
103
+ ? handleComponentLoadErrorWhenNeedToFail
104
+ : handleComponentLoadErrorWhenNoNeedToFail,
105
+ shouldShowLoadingError: SHOULD_FAIL_ON_COMPONENT_LOADING && loadingError,
106
+ arePreviousComponentsLoaded,
107
+ }),
108
+ [
109
+ loadingError,
110
+ handleComponentLoaded,
111
+ handleComponentLoadErrorWhenNeedToFail,
112
+ handleComponentLoadErrorWhenNoNeedToFail,
113
+ arePreviousComponentsLoaded,
114
+ ]
75
115
  );
76
-
77
- return {
78
- isAnyLoading: anyFalse(componentsState),
79
- isAllLoaded: allTrue(componentsState),
80
- onLoadFinished: handleComponentLoaded,
81
- onLoadFailed: SHOULD_FAIL_ON_COMPONENT_LOADING
82
- ? handleComponentLoadErrorWhenNeedToFail
83
- : handleComponentLoadErrorWhenNoNeedToFail,
84
- shouldShowLoadingError: SHOULD_FAIL_ON_COMPONENT_LOADING && loadingError,
85
- isAnyLoaded: anyTrue(componentsState),
86
- waitForAllComponents: SHOULD_FAIL_ON_COMPONENT_LOADING,
87
- arePreviousComponentsLoaded,
88
- };
89
116
  };