@applicaster/zapp-react-native-ui-components 15.0.0-rc.99 → 15.1.0-rc.2

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 (114) hide show
  1. package/Components/BaseFocusable/index.ios.ts +2 -12
  2. package/Components/Cell/FocusableWrapper.tsx +0 -3
  3. package/Components/Cell/TvOSCellComponent.tsx +0 -5
  4. package/Components/Focusable/Focusable.tsx +2 -4
  5. package/Components/Focusable/FocusableTvOS.tsx +1 -18
  6. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +0 -1
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +1 -30
  8. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  9. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  10. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  11. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  12. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  13. package/Components/HandlePlayable/HandlePlayable.tsx +24 -42
  14. package/Components/HandlePlayable/utils.ts +31 -0
  15. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  16. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  17. package/Components/Layout/TV/LayoutBackground.tsx +2 -5
  18. package/Components/Layout/TV/ScreenContainer.tsx +6 -2
  19. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -0
  20. package/Components/Layout/TV/index.tsx +4 -3
  21. package/Components/Layout/TV/index.web.tsx +4 -3
  22. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  23. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +10 -4
  24. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +1 -5
  25. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +3 -11
  26. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +1 -9
  27. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +14 -15
  28. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  29. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  30. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +34 -16
  31. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +6 -7
  32. package/Components/MasterCell/DefaultComponents/Text/index.tsx +2 -6
  33. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +2 -6
  34. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +11 -233
  35. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +15 -19
  36. package/Components/Navigator/StackNavigator.tsx +6 -0
  37. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  38. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +18 -17
  39. package/Components/OfflineHandler/__tests__/index.test.tsx +18 -27
  40. package/Components/PlayerContainer/PlayerContainer.tsx +14 -32
  41. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  42. package/Components/PreloaderWrapper/index.tsx +15 -0
  43. package/Components/River/ComponentsMap/ComponentsMap.tsx +3 -4
  44. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  45. package/Components/River/RefreshControl.tsx +9 -3
  46. package/Components/River/RiverItem.tsx +26 -20
  47. package/Components/River/TV/River.tsx +14 -31
  48. package/Components/River/TV/index.tsx +4 -8
  49. package/Components/River/TV/withTVEventHandler.tsx +36 -0
  50. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +0 -1
  51. package/Components/River/__tests__/componentsMap.test.js +0 -38
  52. package/Components/Screen/TV/index.web.tsx +2 -4
  53. package/Components/Screen/__tests__/Screen.test.tsx +43 -65
  54. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +44 -68
  55. package/Components/Screen/hooks.ts +76 -5
  56. package/Components/Screen/index.tsx +10 -3
  57. package/Components/Screen/orientationHandler.ts +3 -3
  58. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  59. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  60. package/Components/ScreenFeedLoader/index.ts +1 -0
  61. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  62. package/Components/ScreenResolver/hooks/index.ts +3 -0
  63. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  64. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  65. package/Components/ScreenResolver/index.tsx +9 -115
  66. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  67. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  68. package/Components/ScreenResolver/utils/index.ts +1 -0
  69. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  70. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  71. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  72. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  73. package/Components/ScreenRevealManager/ScreenRevealManager.ts +8 -40
  74. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +69 -86
  75. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +4 -1
  76. package/Components/Tabs/TabContent.tsx +4 -7
  77. package/Components/Transitioner/Scene.tsx +9 -15
  78. package/Components/Transitioner/index.js +3 -3
  79. package/Components/VideoLive/LiveImageManager.ts +199 -54
  80. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  81. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  82. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  83. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +7 -15
  84. package/Components/VideoModal/utils.ts +9 -12
  85. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  86. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +6 -4
  87. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  88. package/Components/ZappUIComponent/index.tsx +12 -6
  89. package/Components/index.js +1 -1
  90. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  91. package/Contexts/ScreenContext/index.tsx +64 -26
  92. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  93. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  94. package/Decorators/Analytics/index.tsx +5 -6
  95. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +0 -1
  96. package/Decorators/ConfigurationWrapper/const.ts +0 -1
  97. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  98. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  99. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  100. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  101. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  102. package/Helpers/DataSourceHelper/index.js +19 -0
  103. package/package.json +5 -5
  104. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  105. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  106. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  107. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  108. package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +0 -101
  109. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +0 -30
  110. package/Components/River/TV/utils/index.ts +0 -4
  111. package/Components/River/TV/withFocusableGroupForContent.tsx +0 -71
  112. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +0 -80
  113. package/Helpers/DataSourceHelper/index.ts +0 -19
  114. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -0,0 +1,43 @@
1
+ export const getScreenTypeProps = (
2
+ screenType: "favorites" | "link" | "playable" | "hooks",
3
+ props
4
+ ) => {
5
+ const {
6
+ screenData,
7
+ mode,
8
+ screenId,
9
+ groupId,
10
+ focused,
11
+ parentFocus,
12
+ screenAction,
13
+ resultCallback,
14
+ } = props;
15
+
16
+ switch (screenType) {
17
+ case "favorites":
18
+ case "link":
19
+ return {
20
+ screenData,
21
+ };
22
+ case "playable":
23
+ return {
24
+ item: screenData,
25
+ mode: mode === "PIP" ? "PIP" : "FULLSCREEN",
26
+ isModal: false,
27
+ groupId: groupId,
28
+ };
29
+ case "hooks":
30
+ return {
31
+ screenData,
32
+ callback: resultCallback || screenAction,
33
+ focused,
34
+ parentFocus: parentFocus as ParentFocus,
35
+ };
36
+ default:
37
+ return {
38
+ callback: resultCallback || screenAction,
39
+ screenId: screenId,
40
+ screenData,
41
+ };
42
+ }
43
+ };
@@ -0,0 +1 @@
1
+ export { getScreenTypeProps } from "./getScreenTypeProps";
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks";
3
+ import { ZappPipesScreenContext } from "../../Contexts";
4
+
5
+ export function withDefaultScreenContext(Component: React.ComponentType<any>) {
6
+ return function WithDefaultScreenContext(props: any) {
7
+ const screenId = props.screenId;
8
+ const rivers = useRivers();
9
+
10
+ return (
11
+ <ZappPipesScreenContext.Provider initialContextValue={rivers[screenId]}>
12
+ <Component {...props} />
13
+ </ZappPipesScreenContext.Provider>
14
+ );
15
+ };
16
+ }
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { ScreenFeedLoader } from "../ScreenFeedLoader/ScreenFeedLoader";
3
+
4
+ /** Resolves screen-feed for a given screen `id` by using the provided `useFeedData` hook */
5
+ export const ScreenResolverFeedProvider = ({
6
+ id,
7
+ children,
8
+ useFeedData,
9
+ }: {
10
+ id: string;
11
+ children: React.ReactNode;
12
+ useFeedData: (id: string) => Option<ZappDataSource>;
13
+ }) => {
14
+ const feedData = useFeedData(id);
15
+
16
+ if (feedData?.source) {
17
+ return (
18
+ <ScreenFeedLoader id={id} feedData={feedData}>
19
+ {children}
20
+ </ScreenFeedLoader>
21
+ );
22
+ } else {
23
+ return <>{children}</>;
24
+ }
25
+ };
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+ import { ScreenResolverFeedProvider } from "../ScreenResolverFeedProvider";
5
+
6
+ jest.mock("../../ScreenFeedLoader/ScreenFeedLoader", () => ({
7
+ ScreenFeedLoader: ({ children }) => {
8
+ const React = require("react");
9
+ const { View } = require("react-native");
10
+
11
+ return <View testID="feed-loader">{children}</View>;
12
+ },
13
+ }));
14
+
15
+ describe("ScreenResolverFeedProvider", () => {
16
+ it("renders ScreenFeedLoader when screen feed source exists", () => {
17
+ const useFeedData = jest.fn(() => ({
18
+ source: "https://feed",
19
+ mapping: {},
20
+ }));
21
+
22
+ const { getByTestId, getByText } = render(
23
+ <ScreenResolverFeedProvider id="screen-1" useFeedData={useFeedData}>
24
+ <Text>content</Text>
25
+ </ScreenResolverFeedProvider>
26
+ );
27
+
28
+ expect(getByTestId("feed-loader")).toBeDefined();
29
+ expect(getByText("content")).toBeDefined();
30
+ });
31
+
32
+ it("renders children directly when screen feed source is missing", () => {
33
+ const useFeedData = jest.fn(() => ({}));
34
+
35
+ const { queryByTestId, getByText } = render(
36
+ <ScreenResolverFeedProvider id="screen-1" useFeedData={useFeedData}>
37
+ <Text>content</Text>
38
+ </ScreenResolverFeedProvider>
39
+ );
40
+
41
+ expect(queryByTestId("feed-loader")).toBeNull();
42
+ expect(getByText("content")).toBeDefined();
43
+ });
44
+ });
@@ -0,0 +1 @@
1
+ export { ScreenResolverFeedProvider } from "./ScreenResolverFeedProvider";
@@ -1,11 +1,6 @@
1
1
  import { makeListOf } from "@applicaster/zapp-react-native-utils/arrayUtils";
2
2
  import { isFirstComponentGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
3
- import { race, Subject, Subscription } from "rxjs";
4
- import { first } from "rxjs/operators";
5
-
6
- import { withTimeout$ } from "@applicaster/zapp-react-native-utils/idleUtils";
7
-
8
- const MAX_TIMEOUT_TO_WAIT_COMPONENTS_TO_LOAD = 2000; // 2 seconds
3
+ import { once } from "ramda";
9
4
 
10
5
  const INITIAL_NUMBER_TO_LOAD = 3;
11
6
 
@@ -39,8 +34,7 @@ const getNumberOfComponentsWaitToLoadBeforePresent = (
39
34
  export class ScreenRevealManager {
40
35
  public numberOfComponentsWaitToLoadBeforePresent: number;
41
36
  private renderingState: Array<ComponentLoadingState>;
42
- private subject$ = new Subject<void>();
43
- private subscription: Subscription;
37
+ private callback: Callback;
44
38
 
45
39
  constructor(componentsToRender: ZappUIComponent[], callback: Callback) {
46
40
  this.numberOfComponentsWaitToLoadBeforePresent =
@@ -51,58 +45,32 @@ export class ScreenRevealManager {
51
45
  this.numberOfComponentsWaitToLoadBeforePresent
52
46
  );
53
47
 
54
- this.subscription = race(
55
- withTimeout$(MAX_TIMEOUT_TO_WAIT_COMPONENTS_TO_LOAD),
56
- this.subject$
57
- )
58
- .pipe(first())
59
- .subscribe(callback);
48
+ this.callback = once(callback);
60
49
  }
61
50
 
62
51
  onLoadFinished = (index: number): void => {
63
- const currentState = this.renderingState[index];
64
-
65
- if (
66
- COMPONENT_LOADING_STATE.LOADED_WITH_SUCCESS === currentState ||
67
- COMPONENT_LOADING_STATE.LOADED_WITH_FAILURE === currentState
68
- ) {
69
- return;
70
- }
71
-
72
52
  this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_SUCCESS;
73
53
 
74
54
  if (
75
55
  getNumberOfLoaded(this.renderingState) >=
76
56
  this.numberOfComponentsWaitToLoadBeforePresent
77
57
  ) {
78
- this.subject$.next();
79
- this.subject$.complete();
58
+ this.setIsReadyToShow();
80
59
  }
81
60
  };
82
61
 
83
62
  onLoadFailed = (index: number): void => {
84
- const currentState = this.renderingState[index];
85
-
86
- if (
87
- COMPONENT_LOADING_STATE.LOADED_WITH_SUCCESS === currentState ||
88
- COMPONENT_LOADING_STATE.LOADED_WITH_FAILURE === currentState
89
- ) {
90
- return;
91
- }
92
-
93
63
  this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_FAILURE;
94
64
 
95
65
  if (
96
66
  getNumberOfLoaded(this.renderingState) >=
97
67
  this.numberOfComponentsWaitToLoadBeforePresent
98
68
  ) {
99
- this.subject$.next();
100
- this.subject$.complete();
69
+ this.setIsReadyToShow();
101
70
  }
102
71
  };
103
72
 
104
- dispose(): void {
105
- this.subscription?.unsubscribe();
106
- this.subject$.complete();
107
- }
73
+ setIsReadyToShow = (): void => {
74
+ this.callback();
75
+ };
108
76
  }
@@ -2,123 +2,106 @@ import {
2
2
  ScreenRevealManager,
3
3
  COMPONENT_LOADING_STATE,
4
4
  } from "../ScreenRevealManager";
5
- import { Subject } from "rxjs";
6
-
7
- jest.mock("@applicaster/zapp-react-native-utils/arrayUtils", () => ({
8
- makeListOf: jest.fn((value: any, length: number) =>
9
- Array(length).fill(value)
10
- ),
11
- }));
12
-
13
- jest.mock("@applicaster/zapp-react-native-utils/componentsUtils", () => ({
14
- isFirstComponentGallery: jest.fn(),
15
- }));
16
-
17
- jest.mock("@applicaster/zapp-react-native-utils/idleUtils", () => ({
18
- withTimeout$: jest.fn(),
19
- }));
20
-
21
- import { makeListOf } from "@applicaster/zapp-react-native-utils/arrayUtils";
22
- import { isFirstComponentGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
23
- import { withTimeout$ } from "@applicaster/zapp-react-native-utils/idleUtils";
24
5
 
25
6
  describe("ScreenRevealManager", () => {
26
- let mockCallback: jest.Mock;
27
- let timeout$: Subject<void>;
7
+ const mockCallback = jest.fn();
28
8
 
29
9
  beforeEach(() => {
30
- jest.useFakeTimers();
31
- mockCallback = jest.fn();
32
- timeout$ = new Subject();
33
-
34
- (withTimeout$ as jest.Mock).mockReturnValue(timeout$);
35
- (isFirstComponentGallery as jest.Mock).mockReturnValue(false);
36
-
37
- (makeListOf as jest.Mock).mockImplementation((value, length) =>
38
- Array(length).fill(value)
39
- );
10
+ jest.clearAllMocks();
40
11
  });
41
12
 
42
- afterEach(() => {
43
- jest.clearAllTimers();
44
- jest.useRealTimers();
45
- jest.resetAllMocks();
46
- });
47
-
48
- // ────────────────────────────────────────────────
49
- it("should initialize with correct number of components and UNKNOWN state", () => {
50
- const components = new Array(5).fill({});
51
- const mgr = new ScreenRevealManager(components, mockCallback);
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
+ ];
52
19
 
53
- expect(mgr.numberOfComponentsWaitToLoadBeforePresent).toBe(3);
54
- expect(makeListOf).toHaveBeenCalledWith(COMPONENT_LOADING_STATE.UNKNOWN, 3);
55
- });
20
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
56
21
 
57
- // ────────────────────────────────────────────────
58
- it("should set numberOfComponentsWaitToLoadBeforePresent to 1 if first component is gallery", () => {
59
- (isFirstComponentGallery as jest.Mock).mockReturnValue(true);
22
+ expect(manager.numberOfComponentsWaitToLoadBeforePresent).toBe(3);
60
23
 
61
- const components = new Array(5).fill({});
62
- const mgr = new ScreenRevealManager(components, mockCallback);
63
-
64
- expect(mgr.numberOfComponentsWaitToLoadBeforePresent).toBe(1);
24
+ expect(manager.renderingState).toEqual([
25
+ COMPONENT_LOADING_STATE.UNKNOWN,
26
+ COMPONENT_LOADING_STATE.UNKNOWN,
27
+ COMPONENT_LOADING_STATE.UNKNOWN,
28
+ ]);
65
29
  });
66
30
 
67
- // ────────────────────────────────────────────────
68
- it("should trigger callback after all components load successfully", () => {
69
- const components = new Array(3).fill({});
70
- const mgr = new ScreenRevealManager(components, mockCallback);
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
+ ];
71
37
 
72
- mgr.onLoadFinished(0);
73
- expect(mockCallback).not.toHaveBeenCalled();
38
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
74
39
 
75
- mgr.onLoadFinished(1);
76
- expect(mockCallback).not.toHaveBeenCalled();
40
+ manager.onLoadFinished(0);
41
+ manager.onLoadFinished(1);
42
+ manager.onLoadFinished(2);
77
43
 
78
- mgr.onLoadFinished(2);
79
44
  expect(mockCallback).toHaveBeenCalledTimes(1);
80
45
  });
81
46
 
82
- // ────────────────────────────────────────────────
83
- it("should trigger callback after some components fail to load but all finished", () => {
84
- const components = new Array(3).fill({});
85
- const mgr = new ScreenRevealManager(components, mockCallback);
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);
86
55
 
87
- mgr.onLoadFinished(0);
88
- mgr.onLoadFailed(1);
89
- mgr.onLoadFailed(2);
56
+ manager.onLoadFailed(0);
57
+ manager.onLoadFailed(1);
58
+ manager.onLoadFailed(2);
90
59
 
91
60
  expect(mockCallback).toHaveBeenCalledTimes(1);
92
61
  });
93
62
 
94
- // ────────────────────────────────────────────────
95
- it("should not trigger callback twice when same component finishes twice", () => {
96
- const components = new Array(3).fill({});
97
- const mgr = new ScreenRevealManager(components, mockCallback);
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);
98
71
 
99
- mgr.onLoadFinished(0);
100
- mgr.onLoadFinished(0); // duplicate
101
- mgr.onLoadFinished(1);
102
- mgr.onLoadFinished(2);
72
+ manager.onLoadFinished(0);
73
+ manager.onLoadFailed(1);
74
+ manager.onLoadFinished(2);
103
75
 
104
76
  expect(mockCallback).toHaveBeenCalledTimes(1);
105
77
  });
106
78
 
107
- // ────────────────────────────────────────────────
108
- it("should trigger callback when timeout$ emits before all loaded", () => {
109
- const components = new Array(3).fill({});
110
- new ScreenRevealManager(components, mockCallback);
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
+ ];
111
85
 
112
- timeout$.next(); // simulate timeout event from withTimeout$
86
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
113
87
 
114
- expect(mockCallback).toHaveBeenCalledTimes(1);
88
+ manager.onLoadFinished(0);
89
+ manager.onLoadFailed(1);
90
+
91
+ expect(mockCallback).not.toHaveBeenCalled();
115
92
  });
116
93
 
117
- // ────────────────────────────────────────────────
118
- it("should not call callback if nothing loads and no timeout emitted", () => {
119
- const components = new Array(3).fill({});
120
- new ScreenRevealManager(components, mockCallback);
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
+ ];
121
100
 
122
- expect(mockCallback).not.toHaveBeenCalled();
101
+ const manager = new ScreenRevealManager(componentsToRender, mockCallback);
102
+
103
+ manager.onLoadFinished(0);
104
+
105
+ expect(mockCallback).toHaveBeenCalledTimes(1);
123
106
  });
124
107
  });
@@ -25,6 +25,7 @@ export const SHOWN = 1; // opacity = 1
25
25
 
26
26
  type Props = {
27
27
  componentsToRender: ZappUIComponent[];
28
+ backgroundColor?: string;
28
29
  };
29
30
 
30
31
  export const withScreenRevealManager = (Component) => {
@@ -97,7 +98,9 @@ export const withScreenRevealManager = (Component) => {
97
98
  styles.container,
98
99
  {
99
100
  opacity: opacityRef.current,
100
- backgroundColor: theme.app_background_color,
101
+ // TODO: we should support background image as well, but for now we will use background color from theme
102
+ backgroundColor:
103
+ props.backgroundColor ?? theme.app_background_color,
101
104
  },
102
105
  ]}
103
106
  testID="animated-component"
@@ -1,10 +1,11 @@
1
1
  import React from "react";
2
- import { View, StyleSheet } from "react-native";
2
+ import { View, ViewStyle } from "react-native";
3
3
 
4
4
  import { River } from "@applicaster/zapp-react-native-ui-components/Components";
5
5
  import { withNestedNavigationContextProvider } from "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext";
6
6
 
7
7
  type Props = {
8
+ styles: Record<string, ViewStyle>;
8
9
  minHeight: number;
9
10
  changingTab: boolean;
10
11
  feedUrl: string;
@@ -13,14 +14,9 @@ type Props = {
13
14
  backgroundColor: string;
14
15
  };
15
16
 
16
- const styles = StyleSheet.create({
17
- riverWrapper: {
18
- flex: 1,
19
- },
20
- });
21
-
22
17
  function TabContentComponent(props: Props) {
23
18
  const {
19
+ styles,
24
20
  minHeight,
25
21
  backgroundColor,
26
22
  changingTab,
@@ -33,6 +29,7 @@ function TabContentComponent(props: Props) {
33
29
  <View
34
30
  style={[
35
31
  styles.riverWrapper,
32
+
36
33
  {
37
34
  backgroundColor,
38
35
  minHeight,
@@ -1,9 +1,9 @@
1
- import React, { useEffect } from "react";
1
+ import React from "react";
2
2
  import { equals } from "ramda";
3
3
  import { Animated, ViewProps, ViewStyle } from "react-native";
4
- import { useSafeAreaFrame } from "react-native-safe-area-context";
5
4
 
6
5
  import { useScreenOrientationHandler } from "@applicaster/zapp-react-native-ui-components/Components/Screen/orientationHandler";
6
+ import { useMemoizedSafeAreaFrameWithActiveState } from "@applicaster/zapp-react-native-ui-components/Components/Screen/hooks";
7
7
 
8
8
  import { PathnameContext } from "../../Contexts/PathnameContext";
9
9
  import { ScreenDataContext } from "../../Contexts/ScreenDataContext";
@@ -94,19 +94,13 @@ function SceneComponent({
94
94
  isActive,
95
95
  });
96
96
 
97
- const frame = useSafeAreaFrame();
98
-
99
- const [memoFrame, setMemoFrame] = React.useState(frame);
100
-
101
- useEffect(() => {
102
- if (isActive) {
103
- setMemoFrame((oldFrame) =>
104
- oldFrame.width === frame.width && oldFrame.height === frame.height
105
- ? oldFrame
106
- : frame
107
- );
108
- }
109
- }, [isActive, frame.width, frame.height]);
97
+ // Use shared memoized frame hook - synchronized with useWaitForValidOrientation
98
+ // to prevent race conditions during orientation changes
99
+ // Pass isActive from props since Scene knows its active state from Transitioner
100
+ const memoFrame = useMemoizedSafeAreaFrameWithActiveState({
101
+ updateForInactiveScreens: false,
102
+ isActive,
103
+ });
110
104
 
111
105
  const isAnimating = animating && overlayStyle;
112
106
 
@@ -1,8 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
3
3
  import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks/layout";
4
- import { useAppData } from "@applicaster/zapp-react-native-redux";
5
-
4
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
6
5
  import { TransitionerComponent } from "./Transitioner";
7
6
 
8
7
  export const Transitioner = (props) => {
@@ -17,7 +16,8 @@ export const Transitioner = (props) => {
17
16
  deviceInfo: true,
18
17
  });
19
18
 
20
- const { isTabletPortrait } = useAppData();
19
+ const { appData } = usePickFromState(["appData"]);
20
+ const isTabletPortrait = appData?.isTabletPortrait;
21
21
 
22
22
  return (
23
23
  <TransitionerComponent