@applicaster/zapp-react-native-ui-components 14.0.0-alpha.1661204539 → 14.0.0-alpha.2175196485

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 (90) hide show
  1. package/Components/AnimatedInOut/index.tsx +5 -3
  2. package/Components/AudioPlayer/index.tsx +15 -0
  3. package/Components/AudioPlayer/mobile/Layout.tsx +66 -0
  4. package/Components/AudioPlayer/{__tests__/__snapshots__/audioPlayer.test.js.snap → mobile/__tests__/__snapshots__/audioPlayerMobileLayout.test.js.snap} +2 -2
  5. package/Components/AudioPlayer/mobile/__tests__/audioPlayerMobileLayout.test.js +18 -0
  6. package/Components/AudioPlayer/mobile/index.tsx +18 -0
  7. package/Components/AudioPlayer/tv/Artwork.tsx +41 -0
  8. package/Components/AudioPlayer/{Channel.tsx → tv/Channel.tsx} +7 -7
  9. package/Components/AudioPlayer/tv/Layout.tsx +168 -0
  10. package/Components/AudioPlayer/{Runtime.tsx → tv/Runtime.tsx} +9 -2
  11. package/Components/AudioPlayer/{Summary.tsx → tv/Summary.tsx} +17 -10
  12. package/Components/AudioPlayer/tv/Title.tsx +46 -0
  13. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/Runtime.test.js.snap +4 -4
  14. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/artWork.test.js.snap +9 -4
  15. package/Components/AudioPlayer/tv/__tests__/__snapshots__/audioPlayer.test.js.snap +164 -0
  16. package/Components/AudioPlayer/tv/__tests__/__snapshots__/channel.test.js.snap +19 -0
  17. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/summary.test.js.snap +2 -3
  18. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/title.test.js.snap +2 -3
  19. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/audioPlayer.test.js +7 -3
  20. package/Components/AudioPlayer/{helpers.tsx → tv/helpers.tsx} +3 -4
  21. package/Components/AudioPlayer/{AudioPlayer.tsx → tv/index.tsx} +18 -57
  22. package/Components/AudioPlayer/types.ts +40 -0
  23. package/Components/Cell/index.js +6 -2
  24. package/Components/Focusable/Focusable.tsx +5 -3
  25. package/Components/Focusable/FocusableTvOS.tsx +3 -3
  26. package/Components/Focusable/FocusableiOS.tsx +1 -1
  27. package/Components/Focusable/__tests__/index.android.test.tsx +3 -0
  28. package/Components/Focusable/index.android.tsx +12 -8
  29. package/Components/FocusableList/index.tsx +4 -0
  30. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +0 -2
  31. package/Components/HandlePlayable/HandlePlayable.tsx +25 -9
  32. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -27
  33. package/Components/MasterCell/DefaultComponents/Text/index.tsx +1 -0
  34. package/Components/MasterCell/elementMapper.tsx +1 -2
  35. package/Components/MasterCell/index.tsx +1 -1
  36. package/Components/MasterCell/utils/behaviorProvider.ts +12 -67
  37. package/Components/MasterCell/utils/index.ts +3 -13
  38. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +13 -18
  39. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +9 -0
  40. package/Components/PlayerContainer/PlayerContainer.tsx +62 -66
  41. package/Components/PlayerImageBackground/index.tsx +1 -1
  42. package/Components/River/ComponentsMap/ComponentsMap.tsx +1 -6
  43. package/Components/River/RiverItem.tsx +8 -8
  44. package/Components/River/TV/River.tsx +0 -3
  45. package/Components/River/TV/withTVEventHandler.tsx +1 -1
  46. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -6
  47. package/Components/ScreenRevealManager/ScreenRevealManager.ts +76 -0
  48. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +107 -0
  49. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +96 -0
  50. package/Components/ScreenRevealManager/index.ts +1 -0
  51. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +79 -0
  52. package/Components/Tabs/TV/Tabs.android.tsx +0 -2
  53. package/Components/TopMarginApplicator/TopMarginApplicator.tsx +16 -15
  54. package/Components/Touchable/__tests__/__snapshots__/touchable.test.tsx.snap +34 -0
  55. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +15 -9
  56. package/Components/VideoLive/animationUtils.ts +3 -3
  57. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +9 -1
  58. package/Components/VideoModal/PlayerDetails.tsx +24 -2
  59. package/Components/VideoModal/PlayerWrapper.tsx +26 -142
  60. package/Components/VideoModal/VideoModal.tsx +3 -17
  61. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -7
  62. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +44 -180
  63. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +17 -55
  64. package/Components/VideoModal/hooks/index.ts +0 -2
  65. package/Components/VideoModal/hooks/useDelayedPlayerDetails.ts +15 -26
  66. package/Components/VideoModal/hooks/useModalSize.ts +18 -2
  67. package/Components/VideoModal/utils.ts +6 -0
  68. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +12 -16
  69. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +84 -24
  70. package/Components/Viewport/VisibilitySensor/VisibilitySensor.tsx +3 -3
  71. package/Components/default-cell-renderer/viewTrees/tv/DefaultCell/index.ts +3 -3
  72. package/Decorators/ConfigurationWrapper/withConfigurationProvider.tsx +2 -2
  73. package/Decorators/RiverFeedLoader/index.tsx +2 -8
  74. package/Decorators/RiverFeedLoader/utils/index.ts +2 -7
  75. package/Decorators/ZappPipesDataConnector/index.tsx +2 -16
  76. package/index.d.ts +0 -1
  77. package/package.json +5 -9
  78. package/Components/AudioPlayer/Artwork.tsx +0 -35
  79. package/Components/AudioPlayer/AudioPlayerLayout.tsx +0 -202
  80. package/Components/AudioPlayer/Title.tsx +0 -39
  81. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayerLayout.test.js.snap +0 -66
  82. package/Components/AudioPlayer/__tests__/__snapshots__/channel.test.js.snap +0 -28
  83. package/Components/AudioPlayer/__tests__/audioPlayerLayout.test.js +0 -26
  84. package/Components/AudioPlayer/index.ts +0 -1
  85. package/Components/VideoModal/hooks/useBackgroundColor.ts +0 -10
  86. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/Runtime.test.js +0 -0
  87. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/artWork.test.js +0 -0
  88. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/channel.test.js +0 -0
  89. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/summary.test.js +0 -0
  90. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/title.test.js +0 -0
@@ -1,17 +1,19 @@
1
1
  import * as React from "react";
2
2
  import { useEffect, useReducer } from "react";
3
- // @ts-ignore
4
- import { TVMenuControl, View, ViewStyle } from "react-native";
3
+ import { View, ViewStyle } from "react-native";
5
4
  import * as R from "ramda";
6
5
  import uuid from "uuid/v4";
7
6
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
8
7
  import {
9
- isApplePlatform,
10
8
  isTV,
11
9
  platformSelect,
12
10
  isAndroidTVPlatform,
11
+ isTvOSPlatform,
13
12
  } from "@applicaster/zapp-react-native-utils/reactUtils";
14
- import { isAudioItem } from "@applicaster/zapp-react-native-utils/playerUtils";
13
+ import {
14
+ isAudioItem,
15
+ isInlineTV as isInlineTVUtil,
16
+ } from "@applicaster/zapp-react-native-utils/playerUtils";
15
17
 
16
18
  import { TVEventHandlerComponent } from "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent";
17
19
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
@@ -22,7 +24,7 @@ import {
22
24
  } from "@applicaster/zapp-react-native-utils/reactHooks";
23
25
 
24
26
  import { PlayerStateContext } from "../../Contexts/PlayerStateContext";
25
- import { isAppleTV } from "../../Helpers/Platform";
27
+
26
28
  import {
27
29
  QUICK_BRICK_EVENTS,
28
30
  QuickBrickEvent,
@@ -59,6 +61,11 @@ import {
59
61
  useModalAnimationContext,
60
62
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
61
63
 
64
+ import {
65
+ PlayerNativeCommandTypes,
66
+ PlayerNativeSendCommand,
67
+ } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
68
+
62
69
  type Props = {
63
70
  Player: React.ComponentType<any>;
64
71
  PlayerLoadingView?: React.ComponentType<any>; // 👀 we are not receiving this prop
@@ -85,7 +92,7 @@ export const VideoModalMode = {
85
92
  MAXIMIZED: "MAXIMIZED",
86
93
  MINIMIZED: "MINIMIZED",
87
94
  FULLSCREEN: "FULLSCREEN",
88
- };
95
+ } as const;
89
96
 
90
97
  export type PlayNextData = {
91
98
  state: PlayNextState;
@@ -97,6 +104,11 @@ const focusableBottomContainerId = "player-container-bottom";
97
104
 
98
105
  const isAndroidTV = isAndroidTVPlatform();
99
106
 
107
+ const isTvOS = isTvOSPlatform();
108
+
109
+ // height for container with additional content below player
110
+ const INLINE_CONTAINER_CONTENT_HEIGHT = 400;
111
+
100
112
  const withBorderHack = () => {
101
113
  if (isAndroidTV) {
102
114
  /* @HACK: see GH#7269 */
@@ -119,14 +131,14 @@ const webStyles = {
119
131
  playerScreen: {
120
132
  flex: 1,
121
133
  height: "100vh",
122
- background: "black",
134
+ backgroundColor: "black",
123
135
  },
124
136
  playerWrapper: {
125
137
  height: "100%",
126
138
  flex: "initial",
127
139
  },
128
140
  inlineRiver: {
129
- height: 400,
141
+ height: INLINE_CONTAINER_CONTENT_HEIGHT,
130
142
  },
131
143
  };
132
144
 
@@ -137,14 +149,13 @@ const nativeStyles = {
137
149
  },
138
150
  playerScreen: {
139
151
  flex: 1,
140
- backgroundColor: "black",
141
152
  overflow: "hidden",
142
153
  },
143
154
  playerWrapper: {
144
155
  flex: 1,
145
156
  },
146
157
  inlineRiver: {
147
- height: 400,
158
+ height: INLINE_CONTAINER_CONTENT_HEIGHT,
148
159
  },
149
160
  };
150
161
 
@@ -164,12 +175,11 @@ const renderApplePlayer = ({
164
175
  videoStyle,
165
176
  playNextData,
166
177
  }) => {
167
- const { title, summary } = item || {};
168
-
169
- if (!isAppleTV() || !player) {
178
+ if (!isTvOS || !player) {
170
179
  return null;
171
180
  }
172
181
 
182
+ // render audio_player for tvos
173
183
  if (isAudioContent) {
174
184
  return (
175
185
  <AudioPlayer
@@ -183,6 +193,8 @@ const renderApplePlayer = ({
183
193
  return null;
184
194
  }
185
195
 
196
+ const { title, summary } = item || {};
197
+
186
198
  return (
187
199
  <ProgramInfo
188
200
  title={title}
@@ -193,21 +205,6 @@ const renderApplePlayer = ({
193
205
  );
194
206
  };
195
207
 
196
- const renderRestPlayers = ({ item, showAudioPlayer, pluginConfiguration }) => {
197
- if (isApplePlatform()) {
198
- return null;
199
- }
200
-
201
- return (
202
- showAudioPlayer && (
203
- <AudioPlayer
204
- audio_item={item}
205
- plugin_configuration={pluginConfiguration}
206
- />
207
- )
208
- );
209
- };
210
-
211
208
  const PlayerContainerComponent = (props: Props) => {
212
209
  const {
213
210
  Player,
@@ -266,9 +263,15 @@ const PlayerContainerComponent = (props: Props) => {
266
263
  return;
267
264
  }
268
265
 
266
+ // send command to clear and stop player
267
+ PlayerNativeSendCommand(
268
+ PlayerNativeCommandTypes.clearPlayerData,
269
+ state.playerId
270
+ );
271
+
269
272
  showNavBar(true);
270
273
  navigator.goBack();
271
- }, [isModal, navigator.goBack, showNavBar]);
274
+ }, [isModal, navigator.goBack, state.playerId, showNavBar]);
272
275
 
273
276
  const playEntry = (entry) => navigator.replaceTop(entry, { mode });
274
277
 
@@ -337,7 +340,7 @@ const PlayerContainerComponent = (props: Props) => {
337
340
 
338
341
  setState({ error: errorObj });
339
342
 
340
- if (!isAppleTV()) {
343
+ if (!isTvOS) {
341
344
  setTimeout(() => {
342
345
  close();
343
346
  }, 800);
@@ -396,13 +399,17 @@ const PlayerContainerComponent = (props: Props) => {
396
399
  }
397
400
  };
398
401
 
399
- const playerRemoteHandler = (event, isLanguageOverlayVisible) => {
400
- const { eventType } = event;
402
+ const playerRemoteHandler = React.useCallback(
403
+ (isLanguageOverlayVisible = false) =>
404
+ (event) => {
405
+ const { eventType } = event;
401
406
 
402
- if (!isLanguageOverlayVisible && eventType === "menu") {
403
- close();
404
- }
405
- };
407
+ if (!isLanguageOverlayVisible && eventType === "menu") {
408
+ close();
409
+ }
410
+ },
411
+ [close]
412
+ );
406
413
 
407
414
  // Effects
408
415
  useEffect(() => {
@@ -515,16 +522,6 @@ const PlayerContainerComponent = (props: Props) => {
515
522
  }
516
523
  }, [isAudioContent]);
517
524
 
518
- // Needs to handle back button on Apple TV
519
- // https://github.com/facebook/react-native/issues/18930
520
- useEffect(() => {
521
- TVMenuControl?.enableTVMenuKey();
522
-
523
- return () => {
524
- TVMenuControl?.disableTVMenuKey();
525
- };
526
- }, []);
527
-
528
525
  useEffect(() => {
529
526
  playerEvent("source_changed", { item });
530
527
 
@@ -568,14 +565,12 @@ const PlayerContainerComponent = (props: Props) => {
568
565
 
569
566
  const uri = item?.content ? item.content?.src : null;
570
567
 
571
- const isInlineTV =
572
- screenData?.ui_components &&
573
- screenData?.ui_components?.length > 0 &&
574
- isTV();
568
+ const isInlineTV = isInlineTVUtil(screenData);
575
569
 
576
570
  const inline =
577
- [VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(mode) ||
578
- isInlineTV;
571
+ [VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(
572
+ mode as any
573
+ ) || isInlineTV;
579
574
 
580
575
  const value = React.useMemo(
581
576
  () => ({ playerId: state.playerId }),
@@ -596,7 +591,11 @@ const PlayerContainerComponent = (props: Props) => {
596
591
  );
597
592
  }
598
593
 
599
- if (screen_background_color && mode !== VideoModalMode.FULLSCREEN) {
594
+ if (
595
+ screen_background_color &&
596
+ mode !== VideoModalMode.FULLSCREEN &&
597
+ isTV()
598
+ ) {
600
599
  updatedStyles.playerScreen.backgroundColor = screen_background_color;
601
600
  }
602
601
 
@@ -626,11 +625,7 @@ const PlayerContainerComponent = (props: Props) => {
626
625
  playNextData,
627
626
  };
628
627
 
629
- const restPlayerProps = {
630
- item,
631
- showAudioPlayer: isAudioContent,
632
- pluginConfiguration,
633
- };
628
+ const pointerEventsProp = mode === "MINIMIZED" ? "box-none" : "auto";
634
629
 
635
630
  return (
636
631
  <PlayerStateContext.Provider value={value}>
@@ -642,9 +637,9 @@ const PlayerContainerComponent = (props: Props) => {
642
637
  <PlayerContainerContext.Consumer>
643
638
  {(context) => (
644
639
  <TVEventHandlerComponent
645
- tvEventHandler={(_component, event) =>
646
- playerRemoteHandler(event, context.isLanguageOverlayVisible)
647
- }
640
+ tvEventHandler={playerRemoteHandler(
641
+ context.isLanguageOverlayVisible
642
+ )}
648
643
  >
649
644
  <FocusableGroup
650
645
  id={FocusableGroupMainContainerId}
@@ -652,14 +647,17 @@ const PlayerContainerComponent = (props: Props) => {
652
647
  preferredFocus
653
648
  shouldUsePreferredFocus
654
649
  groupId={groupId}
650
+ pointerEvents={pointerEventsProp}
655
651
  >
656
652
  {/* Video player and components */}
657
653
  <View
658
654
  style={styles.playerScreen}
659
655
  testID={"player-screen-container"}
656
+ pointerEvents={pointerEventsProp}
660
657
  >
661
658
  {/* Player container */}
662
659
  <View
660
+ pointerEvents={pointerEventsProp}
663
661
  style={[
664
662
  styles.playerWrapper,
665
663
  // eslint-disable-next-line react-native/no-inline-styles, react-native/no-color-literals
@@ -707,8 +705,6 @@ const PlayerContainerComponent = (props: Props) => {
707
705
  </Player>
708
706
  </PlayerFocusableWrapperView>
709
707
 
710
- {renderRestPlayers(restPlayerProps)}
711
-
712
708
  {state.error ? <ErrorDisplay error={state.error} /> : null}
713
709
  </View>
714
710
  {/* Components container */}
@@ -732,12 +728,12 @@ const PlayerContainerComponent = (props: Props) => {
732
728
  screenId={screenData.id}
733
729
  key={item.id}
734
730
  groupId={FocusableGroupMainContainerId}
735
- cellTapAction={(event) => onCellTap(event)}
736
- extraAnchorPointYOffset={-600}
731
+ cellTapAction={onCellTap}
732
+ extraAnchorPointYOffset={0}
737
733
  isScreenWrappedInContainer={true}
738
734
  containerHeight={styles.inlineRiver.height}
739
735
  componentsMapExtraProps={{
740
- isNestedComponentsMap: R.T,
736
+ isNestedComponentsMap: true,
741
737
  }}
742
738
  />
743
739
  )}
@@ -28,7 +28,7 @@ const PlayerImageBackgroundComponent = ({
28
28
  defaultImageDimensions,
29
29
  }: Props) => {
30
30
  const source = React.useMemo(
31
- () => ({ uri: imageSrcFromMediaItem(entry, imageKey) }),
31
+ () => ({ uri: imageSrcFromMediaItem(entry, [imageKey]) }),
32
32
  [imageKey, entry]
33
33
  );
34
34
 
@@ -281,12 +281,11 @@ function ComponentsMapComponent(props: Props) {
281
281
  scrollIndicatorInsets={scrollIndicatorInsets}
282
282
  extraData={feed}
283
283
  stickyHeaderIndices={stickyHeaderIndices}
284
- onLayout={handleOnLayout}
285
284
  removeClippedSubviews={isAndroid}
285
+ onLayout={handleOnLayout}
286
286
  initialNumToRender={3}
287
287
  maxToRenderPerBatch={10}
288
288
  windowSize={12}
289
- listKey={riverId}
290
289
  keyExtractor={keyExtractor}
291
290
  renderItem={renderRiverItem}
292
291
  data={riverComponents}
@@ -303,10 +302,6 @@ function ComponentsMapComponent(props: Props) {
303
302
  onMomentumScrollEnd={_onMomentumScrollEnd}
304
303
  onScrollEndDrag={_onScrollEndDrag}
305
304
  scrollEventThrottle={16}
306
- maintainVisibleContentPosition={{
307
- minIndexForVisible: 0,
308
- autoscrollToTopThreshold: 10,
309
- }}
310
305
  {...scrollViewExtraProps}
311
306
  />
312
307
  </ViewportTracker>
@@ -39,6 +39,10 @@ function getFeedUrl(feed: ZappFeed, index: number) {
39
39
  }
40
40
  }
41
41
 
42
+ const isNextIndex = (index, readyIndex) => {
43
+ return readyIndex + 1 >= index;
44
+ };
45
+
42
46
  /**
43
47
  * useLoadingState for RiverItemComponent
44
48
  * takes currentIndex and loadingState as arguments
@@ -48,24 +52,20 @@ const useLoadingState = (
48
52
  loadingState: RiverItemType["loadingState"]
49
53
  ) => {
50
54
  const [readyToBeDisplayed, setReadyToBeDisplayed] = React.useState(
51
- loadingState.getValue().index + 1 >= currentIndex
55
+ isNextIndex(currentIndex, loadingState.getValue().index)
52
56
  );
53
57
 
54
58
  useEffect(() => {
55
59
  const subscription = loadingState.subscribe(({ index }) => {
56
- if (index + 1 >= currentIndex) {
60
+ if (isNextIndex(currentIndex, index)) {
57
61
  setReadyToBeDisplayed(true);
58
62
  }
59
63
  });
60
64
 
61
- if (loadingState.getValue().index + 1 >= currentIndex) {
62
- setReadyToBeDisplayed(true);
63
- }
64
-
65
65
  return () => {
66
66
  subscription.unsubscribe();
67
67
  };
68
- }, [loadingState, currentIndex]);
68
+ }, [currentIndex]);
69
69
 
70
70
  return readyToBeDisplayed;
71
71
  };
@@ -151,7 +151,7 @@ function RiverItemComponent(props: RiverItemType) {
151
151
  component={item}
152
152
  componentIndex={index}
153
153
  onLoadFailed={onLoadFailed}
154
- onLoadFinished={() => onLoadFinished(index)} // Keeping it here to don't break the plugins.
154
+ onLoadFinished={() => onLoadFinished(index)}
155
155
  groupId={groupId}
156
156
  feedUrl={feedUrl}
157
157
  isLast={isLast}
@@ -26,7 +26,6 @@ type Props = {
26
26
  componentsMapExtraProps?: any;
27
27
  isInsideContainer?: boolean;
28
28
  extraAnchorPointYOffset: number;
29
- extraOffset: number;
30
29
  river?: ZappRiver | ZappEntry;
31
30
  };
32
31
 
@@ -39,7 +38,6 @@ export const River = (props: Props) => {
39
38
  componentsMapExtraProps,
40
39
  isInsideContainer,
41
40
  extraAnchorPointYOffset,
42
- extraOffset,
43
41
  } = props;
44
42
 
45
43
  const { title: screenTitle, summary: screenSummary } = useNavbarState();
@@ -120,7 +118,6 @@ export const River = (props: Props) => {
120
118
  <GeneralContentScreen
121
119
  feed={feedData}
122
120
  screenId={screenId}
123
- extraOffset={extraOffset}
124
121
  isScreenWrappedInContainer={isInsideContainer}
125
122
  extraAnchorPointYOffset={extraAnchorPointYOffset}
126
123
  componentsMapExtraProps={componentsMapExtraProps}
@@ -8,7 +8,7 @@ export const withTvEventHandler = (Component) => {
8
8
  return function WithTVEventHandler(props) {
9
9
  const navigator = useNavigation();
10
10
 
11
- const remoteHandler = (_, event) => {
11
+ const remoteHandler = (event) => {
12
12
  const { eventType } = event;
13
13
 
14
14
  const canGoBack = navigator.canGoBack();
@@ -135,12 +135,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
135
135
  getItemCount={[Function]}
136
136
  initialNumToRender={3}
137
137
  keyExtractor={[Function]}
138
- maintainVisibleContentPosition={
139
- {
140
- "autoscrollToTopThreshold": 10,
141
- "minIndexForVisible": 0,
142
- }
143
- }
144
138
  maxToRenderPerBatch={10}
145
139
  onContentSizeChange={[Function]}
146
140
  onLayout={[Function]}
@@ -165,6 +159,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
165
159
  >
166
160
  <View>
167
161
  <View
162
+ onFocusCapture={[Function]}
168
163
  onLayout={[Function]}
169
164
  style={null}
170
165
  >
@@ -180,6 +175,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
180
175
  </View>
181
176
  </View>
182
177
  <View
178
+ onFocusCapture={[Function]}
183
179
  onLayout={[Function]}
184
180
  style={null}
185
181
  >
@@ -0,0 +1,76 @@
1
+ import { makeListOf } from "@applicaster/zapp-react-native-utils/arrayUtils";
2
+ import { isFirstComponentGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
3
+ import { once } from "ramda";
4
+
5
+ const INITIAL_NUMBER_TO_LOAD = 3;
6
+
7
+ // Infer the values of COMPONENT_LOADING_STATE as a type
8
+ type ComponentLoadingState =
9
+ (typeof COMPONENT_LOADING_STATE)[keyof typeof COMPONENT_LOADING_STATE];
10
+
11
+ export const COMPONENT_LOADING_STATE = {
12
+ UNKNOWN: "UNKNOWN",
13
+ LOADED_WITH_SUCCESS: "LOADED_WITH_SUCCESS",
14
+ LOADED_WITH_FAILURE: "LOADED_WITH_FAILURE",
15
+ } as const;
16
+
17
+ // Function to get the number of loaded components
18
+ const getNumberOfLoaded = (states: ComponentLoadingState[]): number => {
19
+ return states.filter((value) => value !== COMPONENT_LOADING_STATE.UNKNOWN)
20
+ .length;
21
+ };
22
+
23
+ const getNumberOfComponentsWaitToLoadBeforePresent = (
24
+ componentsToRender: ZappUIComponent[]
25
+ ): number => {
26
+ // when Gallery is the first component, no need to wait the others
27
+ if (isFirstComponentGallery(componentsToRender)) {
28
+ return 1;
29
+ }
30
+
31
+ return Math.min(INITIAL_NUMBER_TO_LOAD, componentsToRender.length);
32
+ };
33
+
34
+ export class ScreenRevealManager {
35
+ public numberOfComponentsWaitToLoadBeforePresent: number;
36
+ private renderingState: Array<ComponentLoadingState>;
37
+ private callback: Callback;
38
+
39
+ constructor(componentsToRender: ZappUIComponent[], callback: Callback) {
40
+ this.numberOfComponentsWaitToLoadBeforePresent =
41
+ getNumberOfComponentsWaitToLoadBeforePresent(componentsToRender);
42
+
43
+ this.renderingState = makeListOf<ComponentLoadingState>(
44
+ COMPONENT_LOADING_STATE.UNKNOWN,
45
+ this.numberOfComponentsWaitToLoadBeforePresent
46
+ );
47
+
48
+ this.callback = once(callback);
49
+ }
50
+
51
+ onLoadFinished = (index: number): void => {
52
+ this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_SUCCESS;
53
+
54
+ if (
55
+ getNumberOfLoaded(this.renderingState) >=
56
+ this.numberOfComponentsWaitToLoadBeforePresent
57
+ ) {
58
+ this.setIsReadyToShow();
59
+ }
60
+ };
61
+
62
+ onLoadFailed = (index: number): void => {
63
+ this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_FAILURE;
64
+
65
+ if (
66
+ getNumberOfLoaded(this.renderingState) >=
67
+ this.numberOfComponentsWaitToLoadBeforePresent
68
+ ) {
69
+ this.setIsReadyToShow();
70
+ }
71
+ };
72
+
73
+ setIsReadyToShow = (): void => {
74
+ this.callback();
75
+ };
76
+ }
@@ -0,0 +1,107 @@
1
+ import {
2
+ ScreenRevealManager,
3
+ COMPONENT_LOADING_STATE,
4
+ } from "../ScreenRevealManager";
5
+
6
+ describe("ScreenRevealManager", () => {
7
+ const mockCallback = jest.fn();
8
+
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ it("should initialize with the correct number of components to wait for", () => {
14
+ const componentsToRender: ZappUIComponent[] = [
15
+ { component_type: "component1" },
16
+ { component_type: "component2" },
17
+ { component_type: "component3" },
18
+ ];
19
+
20
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
21
+
22
+ expect(manager["numberOfComponentsWaitToLoadBeforePresent"]).toBe(3);
23
+
24
+ expect(manager["renderingState"]).toEqual([
25
+ COMPONENT_LOADING_STATE.UNKNOWN,
26
+ COMPONENT_LOADING_STATE.UNKNOWN,
27
+ COMPONENT_LOADING_STATE.UNKNOWN,
28
+ ]);
29
+ });
30
+
31
+ it("should call the callback when the required number of components are loaded successfully", () => {
32
+ const componentsToRender: ZappUIComponent[] = [
33
+ { component_type: "component1" },
34
+ { component_type: "component2" },
35
+ { component_type: "component3" },
36
+ ];
37
+
38
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
39
+
40
+ manager.onLoadFinished(0);
41
+ manager.onLoadFinished(1);
42
+ manager.onLoadFinished(2);
43
+
44
+ expect(mockCallback).toHaveBeenCalledTimes(1);
45
+ });
46
+
47
+ it("should call the callback when the required number of components fail to load", () => {
48
+ const componentsToRender: ZappUIComponent[] = [
49
+ { component_type: "component1" },
50
+ { component_type: "component2" },
51
+ { component_type: "component3" },
52
+ ];
53
+
54
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
55
+
56
+ manager.onLoadFailed(0);
57
+ manager.onLoadFailed(1);
58
+ manager.onLoadFailed(2);
59
+
60
+ expect(mockCallback).toHaveBeenCalledTimes(1);
61
+ });
62
+
63
+ it("should call the callback when a mix of successful and failed loads meet the required number", () => {
64
+ const componentsToRender: ZappUIComponent[] = [
65
+ { component_type: "component1" },
66
+ { component_type: "component2" },
67
+ { component_type: "component3" },
68
+ ];
69
+
70
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
71
+
72
+ manager.onLoadFinished(0);
73
+ manager.onLoadFailed(1);
74
+ manager.onLoadFinished(2);
75
+
76
+ expect(mockCallback).toHaveBeenCalledTimes(1);
77
+ });
78
+
79
+ it("should not call the callback if the required number of components are not loaded", () => {
80
+ const componentsToRender: ZappUIComponent[] = [
81
+ { component_type: "component1" },
82
+ { component_type: "component2" },
83
+ { component_type: "component3" },
84
+ ];
85
+
86
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
87
+
88
+ manager.onLoadFinished(0);
89
+ manager.onLoadFailed(1);
90
+
91
+ expect(mockCallback).not.toHaveBeenCalled();
92
+ });
93
+
94
+ it("should call the callback when the when first component is gallery and it was loaded successfully", () => {
95
+ const componentsToRender: ZappUIComponent[] = [
96
+ { component_type: "gallery-qb" },
97
+ { component_type: "component2" },
98
+ { component_type: "component3" },
99
+ ];
100
+
101
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
102
+
103
+ manager.onLoadFinished(0);
104
+
105
+ expect(mockCallback).toHaveBeenCalledTimes(1);
106
+ });
107
+ });