@applicaster/zapp-react-native-ui-components 13.0.0-alpha.1724095599 → 13.0.0-alpha.1731130113

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 (92) hide show
  1. package/Components/AudioPlayer/AudioPlayer.tsx +5 -9
  2. package/Components/AudioPlayer/AudioPlayerLayout.tsx +8 -9
  3. package/Components/AudioPlayer/helpers.tsx +1 -2
  4. package/Components/Cell/Cell.tsx +3 -13
  5. package/Components/Cell/CellWithFocusable.tsx +21 -15
  6. package/Components/Cell/CellWrapper.tsx +18 -0
  7. package/Components/Cell/styles.ts +17 -0
  8. package/Components/CellRendererResolver/index.ts +2 -1
  9. package/Components/ErrorScreen/index.tsx +8 -0
  10. package/Components/FocusableCell/index.tsx +1 -1
  11. package/Components/FocusableGroup/FocusableTvOS.tsx +34 -86
  12. package/Components/FocusableGroup/index.tsx +0 -3
  13. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +3 -0
  14. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +4 -2
  15. package/Components/GeneralContentScreen/utils/useEventAlerts.ts +30 -0
  16. package/Components/HandlePlayable/HandlePlayable.tsx +21 -27
  17. package/Components/Layout/TV/LayoutBackground.tsx +28 -0
  18. package/Components/Layout/TV/NavBarContainer.tsx +2 -1
  19. package/Components/Layout/TV/ScreenContainer.tsx +8 -2
  20. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +5 -0
  21. package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +2 -1
  22. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +1 -0
  23. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +1 -0
  24. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +15 -10
  25. package/Components/Layout/TV/__tests__/index.test.tsx +8 -2
  26. package/Components/Layout/TV/index.tsx +6 -20
  27. package/Components/Layout/TV/index.web.tsx +4 -1
  28. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -3
  29. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +4 -3
  30. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +11 -7
  31. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/const.ts +3 -0
  32. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +6 -6
  33. package/Components/MasterCell/SharedUI/ProgressBar/hooks/useFillInPercent.ts +2 -8
  34. package/Components/MasterCell/utils/behaviorProvider.ts +136 -0
  35. package/Components/MasterCell/utils/index.ts +8 -134
  36. package/Components/ModalComponent/BottomSheetModalContent.tsx +66 -54
  37. package/Components/ModalComponent/Button/Item.tsx +6 -5
  38. package/Components/ModalComponent/Button/assets.ts +1 -1
  39. package/Components/ModalComponent/Button/index.tsx +45 -62
  40. package/Components/ModalComponent/Header/index.tsx +9 -8
  41. package/Components/ModalComponent/utils.ts +55 -7
  42. package/Components/OfflineHandler/utils/index.ts +1 -1
  43. package/Components/PlayerContainer/PlayerContainer.tsx +55 -46
  44. package/Components/PlayerImageBackground/index.tsx +1 -1
  45. package/Components/River/ComponentsMap/ComponentsMap.tsx +14 -3
  46. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +3 -3
  47. package/Components/River/RiverItem.tsx +8 -3
  48. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +7 -1
  49. package/Components/RouteManager/TestId.tsx +1 -5
  50. package/Components/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +0 -1
  51. package/Components/RouteManager/__tests__/testId.test.js +0 -4
  52. package/Components/Screen/TV/__tests__/index.web.test.tsx +26 -0
  53. package/Components/Screen/TV/index.web.tsx +5 -6
  54. package/Components/Screen/__tests__/Screen.test.tsx +22 -14
  55. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -2
  56. package/Components/Screen/hooks.ts +3 -6
  57. package/Components/TopMarginApplicator/TopMarginApplicator.tsx +2 -3
  58. package/Components/TrackedView/index.tsx +1 -0
  59. package/Components/Transitioner/AnimationManager.js +15 -15
  60. package/Components/VideoLive/PlayerLiveImageComponent.tsx +4 -0
  61. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  62. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +1 -1
  63. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +35 -19
  64. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +3 -3
  65. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +38 -14
  66. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +11 -3
  67. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +108 -0
  68. package/Components/VideoModal/ModalAnimation/const.ts +5 -0
  69. package/Components/VideoModal/ModalAnimation/index.ts +2 -0
  70. package/Components/VideoModal/ModalAnimation/utils.ts +30 -0
  71. package/Components/VideoModal/PlayerDetails.tsx +13 -5
  72. package/Components/VideoModal/PlayerWrapper.tsx +35 -22
  73. package/Components/VideoModal/VideoModal.tsx +19 -4
  74. package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +2 -40
  75. package/Components/VideoModal/utils.ts +3 -4
  76. package/Components/ZappUIComponent/index.tsx +4 -4
  77. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +4 -10
  78. package/Contexts/HeaderOffsetContext/index.tsx +4 -6
  79. package/Contexts/ScreenContext/index.tsx +3 -10
  80. package/Contexts/ScreenLayoutContext/index.tsx +5 -3
  81. package/Decorators/RiverResolver/index.tsx +5 -7
  82. package/Decorators/ZappPipesDataConnector/index.tsx +4 -30
  83. package/package.json +5 -9
  84. package/tsconfig.json +2 -3
  85. package/.babelrc +0 -8
  86. package/Components/Cell/CellStyles/FallbackCellStyle/index.js +0 -157
  87. package/Components/Cell/CellStyles/Hero/index.js +0 -111
  88. package/Components/Cell/CellStyles/ScreenSelector/index.js +0 -68
  89. package/Components/Cell/CellStyles/cellStylesResolver.ts +0 -19
  90. package/Components/Cell/CellStyles/colors.js +0 -40
  91. package/Components/Cell/CellStyles/index.js +0 -15
  92. package/Components/Cell/CellWithFocusable.ios.tsx +0 -126
@@ -1,15 +1,18 @@
1
1
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
2
- import { PluginConfiguration } from "./index";
2
+ import { PluginConfiguration } from "./";
3
+ import { ItemIconProps } from "./Button/ItemIcon";
4
+ import { ItemLabelProps } from "./Button/ItemLabel";
5
+ import { ItemProps } from "./Button/Item";
3
6
 
4
7
  export function defaultItemProps(
5
8
  config: PluginConfiguration,
6
9
  maxWidth: number
7
- ) {
10
+ ): ItemProps {
8
11
  const {
9
12
  modal_bottom_sheet_item_background_color,
10
13
  modal_bottom_sheet_item_focus_background_color,
11
14
  modal_bottom_sheet_item_selected_background_color,
12
- modal_bottom_sheet_item_focusected_background_color,
15
+ modal_bottom_sheet_item_focused_selected_background_color,
13
16
  modal_bottom_sheet_item_corner_radius,
14
17
  modal_bottom_sheet_item_margin_bottom,
15
18
  modal_bottom_sheet_item_margin_left,
@@ -25,8 +28,8 @@ export function defaultItemProps(
25
28
  backgroundColor: modal_bottom_sheet_item_background_color,
26
29
  focusedBackgroundColor: modal_bottom_sheet_item_focus_background_color,
27
30
  selectedBackgroundColor: modal_bottom_sheet_item_selected_background_color,
28
- focusectedBackgroundColor:
29
- modal_bottom_sheet_item_focusected_background_color,
31
+ focusedSelectedBackgroundColor:
32
+ modal_bottom_sheet_item_focused_selected_background_color,
30
33
  borderRadius: modal_bottom_sheet_item_corner_radius,
31
34
  marginBottom: modal_bottom_sheet_item_margin_bottom,
32
35
  marginTop: modal_bottom_sheet_item_margin_top,
@@ -43,7 +46,7 @@ export function defaultItemProps(
43
46
  export function defaultItemLabelProps(
44
47
  config: PluginConfiguration,
45
48
  sheetWidth: number
46
- ) {
49
+ ): ItemLabelProps {
47
50
  const {
48
51
  modal_bottom_sheet_item_label_android_letter_spacing,
49
52
  modal_bottom_sheet_item_label_ios_letter_spacing,
@@ -103,7 +106,9 @@ export function defaultItemLabelProps(
103
106
  };
104
107
  }
105
108
 
106
- export function defaultItemIconProps(config: PluginConfiguration) {
109
+ export function defaultItemIconProps(
110
+ config: PluginConfiguration
111
+ ): ItemIconProps {
107
112
  const {
108
113
  modal_bottom_sheet_item_icon_height,
109
114
  modal_bottom_sheet_item_icon_width,
@@ -124,3 +129,46 @@ export function defaultItemIconProps(config: PluginConfiguration) {
124
129
  marginRight: modal_bottom_sheet_item_icon_margin_right,
125
130
  };
126
131
  }
132
+
133
+ export function getItemIconProps(
134
+ theme: PluginConfiguration,
135
+ iconProps?: ((theme: PluginConfiguration) => ItemIconProps) | ItemIconProps
136
+ ) {
137
+ if (iconProps) {
138
+ return typeof iconProps === "function" ? iconProps(theme) : iconProps;
139
+ }
140
+
141
+ return defaultItemIconProps(theme);
142
+ }
143
+
144
+ export function getItemLabelProps(
145
+ theme: PluginConfiguration,
146
+ width: number,
147
+ labelProps?:
148
+ | ((theme: PluginConfiguration, width: number) => ItemLabelProps)
149
+ | ItemLabelProps
150
+ ) {
151
+ if (labelProps) {
152
+ return typeof labelProps === "function"
153
+ ? labelProps(theme, width)
154
+ : labelProps;
155
+ }
156
+
157
+ return defaultItemLabelProps(theme, width);
158
+ }
159
+
160
+ export function getItemProps(
161
+ theme: PluginConfiguration,
162
+ width: number,
163
+ itemProps?:
164
+ | ((theme: PluginConfiguration, width: number) => ItemProps)
165
+ | ItemProps
166
+ ) {
167
+ if (itemProps) {
168
+ return typeof itemProps === "function"
169
+ ? itemProps(theme, width)
170
+ : itemProps;
171
+ }
172
+
173
+ return defaultItemProps(theme, width);
174
+ }
@@ -57,7 +57,7 @@ export const useNotificationHeight = () => {
57
57
 
58
58
  const navBarHeight = platformSelect({ ios: 44, android: 56 });
59
59
 
60
- const statusHeight = Platform.OS === "android" ? 0 : insets.top;
60
+ const statusHeight = insets.top;
61
61
  const notificationHeight = statusHeight + navBarHeight;
62
62
 
63
63
  return { statusHeight, notificationHeight };
@@ -6,10 +6,15 @@ import * as R from "ramda";
6
6
  import uuid from "uuid/v4";
7
7
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
8
8
  import {
9
- isApplePlatform,
10
9
  isTV,
11
10
  platformSelect,
11
+ isAndroidTVPlatform,
12
+ isTvOSPlatform,
12
13
  } from "@applicaster/zapp-react-native-utils/reactUtils";
14
+ import {
15
+ isAudioItem,
16
+ isInlineTV as isInlineTVUtil,
17
+ } from "@applicaster/zapp-react-native-utils/playerUtils";
13
18
 
14
19
  import { TVEventHandlerComponent } from "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent";
15
20
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
@@ -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,
@@ -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,8 +692,6 @@ const PlayerContainerComponent = (props: Props) => {
681
692
  </Player>
682
693
  </PlayerFocusableWrapperView>
683
694
 
684
- {renderRestPlayers(restPlayerProps)}
685
-
686
695
  {state.error ? <ErrorDisplay error={state.error} /> : null}
687
696
  </View>
688
697
  {/* Components container */}
@@ -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}
@@ -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);
@@ -271,11 +277,12 @@ function ComponentsMapComponent(props: Props) {
271
277
  }}
272
278
  // Fix for WebView rerender crashes on Android API 28+
273
279
  // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
274
- overScrollMode={Platform.OS === "android" ? "never" : "auto"}
280
+ overScrollMode={isAndroid ? "never" : "auto"}
275
281
  scrollIndicatorInsets={scrollIndicatorInsets}
276
282
  extraData={feed}
283
+ stickyHeaderIndices={stickyHeaderIndices}
277
284
  onLayout={handleOnLayout}
278
- removeClippedSubviews
285
+ removeClippedSubviews={isAndroid}
279
286
  initialNumToRender={3}
280
287
  maxToRenderPerBatch={10}
281
288
  windowSize={12}
@@ -296,6 +303,10 @@ function ComponentsMapComponent(props: Props) {
296
303
  onMomentumScrollEnd={_onMomentumScrollEnd}
297
304
  onScrollEndDrag={_onScrollEndDrag}
298
305
  scrollEventThrottle={16}
306
+ maintainVisibleContentPosition={{
307
+ minIndexForVisible: 0,
308
+ autoscrollToTopThreshold: 10,
309
+ }}
299
310
  {...scrollViewExtraProps}
300
311
  />
301
312
  </ViewportTracker>
@@ -28,10 +28,10 @@ type Return = {
28
28
  // TODO: Take this value from Zapp configuration, when feature is added to GeneralScreen
29
29
  const SHOULD_FAIL_ON_COMPONENT_LOADING = false;
30
30
 
31
- const createLoadingStateObservable = () =>
31
+ const createLoadingStateObservable = (count: number) =>
32
32
  new BehaviorSubject<LoadingState>({
33
33
  index: -1,
34
- done: false,
34
+ done: count === 0,
35
35
  waitForAllComponents: SHOULD_FAIL_ON_COMPONENT_LOADING,
36
36
  });
37
37
 
@@ -43,7 +43,7 @@ export const useLoadingState = (
43
43
  const [loadingError, setLoadingError] = React.useState(null);
44
44
 
45
45
  const loadingState = useRefWithInitialValue<BehaviorSubject<LoadingState>>(
46
- createLoadingStateObservable
46
+ () => createLoadingStateObservable(count)
47
47
  );
48
48
 
49
49
  const arePreviousComponentsLoaded = React.useCallback((index) => {
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect } from "react";
2
2
  import * as R from "ramda";
3
+ import { isGroup } from "@applicaster/zapp-react-native-utils/componentsUtils";
3
4
 
4
5
  import { applyDecorators } from "../../Decorators";
5
6
 
@@ -47,16 +48,20 @@ const useLoadingState = (
47
48
  loadingState: RiverItemType["loadingState"]
48
49
  ) => {
49
50
  const [readyToBeDisplayed, setReadyToBeDisplayed] = React.useState(
50
- loadingState.getValue().index + 1 === currentIndex
51
+ loadingState.getValue().index + 1 >= currentIndex
51
52
  );
52
53
 
53
54
  useEffect(() => {
54
55
  const subscription = loadingState.subscribe(({ index }) => {
55
- if (index + 1 === currentIndex) {
56
+ if (index + 1 >= currentIndex) {
56
57
  setReadyToBeDisplayed(true);
57
58
  }
58
59
  });
59
60
 
61
+ if (loadingState.getValue().index + 1 >= currentIndex) {
62
+ setReadyToBeDisplayed(true);
63
+ }
64
+
60
65
  return () => {
61
66
  subscription.unsubscribe();
62
67
  };
@@ -112,7 +117,7 @@ function RiverItemComponent(props: RiverItemType) {
112
117
  jsOnly: true,
113
118
  });
114
119
 
115
- if (!CellRenderer && item.component_type !== "group-qb") {
120
+ if (!CellRenderer && !isGroup(item)) {
116
121
  riverLogger.warning({
117
122
  message: "Cell Renderer is null - will fallback to default cell",
118
123
  data: { item, CellRenderer },
@@ -135,6 +135,12 @@ 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
+ }
138
144
  maxToRenderPerBatch={10}
139
145
  onContentSizeChange={[Function]}
140
146
  onLayout={[Function]}
@@ -145,7 +151,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
145
151
  onScrollEndDrag={[Function]}
146
152
  overScrollMode="auto"
147
153
  refreshControl={null}
148
- removeClippedSubviews={true}
154
+ removeClippedSubviews={false}
149
155
  renderItem={[Function]}
150
156
  scrollEventThrottle={16}
151
157
  scrollIndicatorInsets={
@@ -12,11 +12,7 @@ const styles = StyleSheet.create({
12
12
 
13
13
  export function TestId({ screenId, children }: Props) {
14
14
  return (
15
- <View
16
- testID={screenId}
17
- accessibilityLabel={screenId}
18
- style={styles.container}
19
- >
15
+ <View testID={screenId} style={styles.container}>
20
16
  {children}
21
17
  </View>
22
18
  );
@@ -2,7 +2,6 @@
2
2
 
3
3
  exports[`<RouteManager /> renders correctly 1`] = `
4
4
  <View
5
- accessibilityLabel="A1234"
6
5
  style={
7
6
  {
8
7
  "flex": 1,
@@ -24,9 +24,5 @@ describe("TestId", () => {
24
24
  );
25
25
 
26
26
  expect(wrapper.getByTestId(screenId)).toBeTruthy();
27
-
28
- expect(wrapper.getByTestId(screenId).props.accessibilityLabel).toEqual(
29
- screenId
30
- );
31
27
  });
32
28
  });
@@ -0,0 +1,26 @@
1
+ const {
2
+ useScreenBackgroundColor,
3
+ } = require("@applicaster/zapp-react-native-utils/reactHooks/screen");
4
+
5
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
6
+ useScreenBackgroundColor: jest.fn(),
7
+ useNavbarState: jest.fn().mockReturnValue({ visible: true }),
8
+ }));
9
+
10
+ describe("TV Screen Component", () => {
11
+ it("uses the useScreenBackgroundColor hook with the correct screen ID", () => {
12
+ useScreenBackgroundColor.mockReturnValue("#FF0000");
13
+
14
+ expect(useScreenBackgroundColor("test-screen-id")).toBe("#FF0000");
15
+
16
+ // Verify the hook was called with the correct screen ID
17
+ expect(useScreenBackgroundColor).toHaveBeenCalledWith("test-screen-id");
18
+ });
19
+
20
+ it("returns 'transparent' when the screen background color is not defined", () => {
21
+ useScreenBackgroundColor.mockReturnValue("transparent");
22
+
23
+ // Verify the hook returns 'transparent'
24
+ expect(useScreenBackgroundColor("test-screen-id")).toBe("transparent");
25
+ });
26
+ });
@@ -92,6 +92,10 @@ const getNavigations = ({
92
92
  return fallbackNavigations;
93
93
  };
94
94
 
95
+ const onRelease = () => {
96
+ focusManager.setInitialFocus();
97
+ };
98
+
95
99
  export const Screen = ({ route, Components }: Props) => {
96
100
  if (isNilOrEmpty(route)) {
97
101
  throw Error("Required props: route is missing");
@@ -152,12 +156,7 @@ export const Screen = ({ route, Components }: Props) => {
152
156
  const isScreenActive = useIsScreenActive();
153
157
 
154
158
  return (
155
- <FreezeWithCallback
156
- freeze={!isScreenActive}
157
- onRelease={() => {
158
- focusManager.setInitialFocus();
159
- }}
160
- >
159
+ <FreezeWithCallback freeze={!isScreenActive} onRelease={onRelease}>
161
160
  <View style={[styles.container, { backgroundColor }]}>
162
161
  <NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
163
162
  <NavBar
@@ -68,21 +68,21 @@ jest.mock(
68
68
  })
69
69
  );
70
70
 
71
- jest.mock(
72
- "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData",
73
- () => ({
74
- useScreenData: jest.fn(() => ({
75
- id: "testId",
76
- navigations: [{ id: "testId", category: "nav_bar" }],
77
- })),
78
- })
79
- );
80
-
81
71
  jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
82
72
  isNavBarVisible: mockIsNavBarVisible,
83
- useRoute: jest.fn(() => ({
84
- pathname: "/river/testId",
85
- screenData: { id: "testId" },
73
+ useIsScreenActive: jest.fn().mockReturnValue(true),
74
+ }));
75
+
76
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
77
+ useCurrentScreenData: jest.fn(() => ({
78
+ id: "testId",
79
+ })),
80
+ useNavbarState: jest.fn(() => ({
81
+ title: "Test Title",
82
+ })),
83
+ useScreenData: jest.fn(() => ({
84
+ id: "testId",
85
+ navigations: [{ id: "testId", category: "nav_bar" }],
86
86
  })),
87
87
  useNavigation: jest.fn(() => ({
88
88
  canGoBack: () => false,
@@ -91,7 +91,15 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
91
91
  screenData: { id: "testId" },
92
92
  data: { screen: { id: "testId" } },
93
93
  })),
94
- useIsScreenActive: jest.fn().mockReturnValue(true),
94
+ useRoute: jest.fn(() => ({
95
+ pathname: "/river/testId",
96
+ screenData: { id: "testId" },
97
+ })),
98
+ useDimensions: jest.fn(() => ({
99
+ width: 1920,
100
+ height: 1080,
101
+ })),
102
+ useIsTablet: jest.fn(() => false),
95
103
  }));
96
104
 
97
105
  jest.mock("@applicaster/zapp-react-native-redux/hooks/usePickFromState", () => {
@@ -16,7 +16,7 @@ exports[`<Screen Component /> when the navbar should be hidden renders correctly
16
16
  pathname="/river/testId"
17
17
  selected="testId"
18
18
  testID="navBar"
19
- title=""
19
+ title="Test Title"
20
20
  />
21
21
  <View>
22
22
  <View
@@ -48,7 +48,7 @@ exports[`<Screen Component /> when the navbar should show renders correctly 1`]
48
48
  pathname="/river/testId"
49
49
  selected="testId"
50
50
  testID="navBar"
51
- title=""
51
+ title="Test Title"
52
52
  />
53
53
  <View>
54
54
  <View