@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
@@ -1,19 +1,89 @@
1
- import { useAppData } from "@applicaster/zapp-react-native-redux/hooks";
1
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
2
2
  import {
3
3
  useGetScreenOrientation,
4
4
  isOrientationCompatible,
5
5
  } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
6
6
  import {
7
7
  useCurrentScreenData,
8
- useDimensions,
9
8
  useRoute,
10
9
  useIsTablet,
10
+ useIsScreenActive,
11
11
  } from "@applicaster/zapp-react-native-utils/reactHooks";
12
12
  import { useMemo, useEffect, useState } from "react";
13
+ import { useSafeAreaFrame } from "react-native-safe-area-context";
14
+
15
+ type MemoizedSafeAreaFrameWithActiveStateOptions = {
16
+ updateForInactiveScreens?: boolean;
17
+ isActive: boolean;
18
+ };
19
+
20
+ /**
21
+ * Base hook that wraps useSafeAreaFrame with memoization and inactive screen filtering.
22
+ * Requires isActive to be passed explicitly - use useMemoizedSafeAreaFrame for automatic detection.
23
+ *
24
+ * @param options.updateForInactiveScreens - If false, frame won't update when screen is inactive (default: true)
25
+ * @param options.isActive - Whether the screen is currently active
26
+ * @returns The memoized safe area frame { x, y, width, height }
27
+ */
28
+ export const useMemoizedSafeAreaFrameWithActiveState = (
29
+ options: MemoizedSafeAreaFrameWithActiveStateOptions
30
+ ) => {
31
+ const { updateForInactiveScreens = true, isActive } = options;
32
+ const frame = useSafeAreaFrame();
33
+
34
+ const [memoFrame, setMemoFrame] = useState(frame);
35
+
36
+ useEffect(() => {
37
+ const shouldUpdate = isActive || updateForInactiveScreens;
38
+
39
+ const dimensionsChanged =
40
+ frame.width !== memoFrame.width || frame.height !== memoFrame.height;
41
+
42
+ if (shouldUpdate && dimensionsChanged) {
43
+ setMemoFrame(frame);
44
+ }
45
+ }, [
46
+ frame.width,
47
+ frame.height,
48
+ isActive,
49
+ updateForInactiveScreens,
50
+ frame,
51
+ memoFrame.width,
52
+ memoFrame.height,
53
+ ]);
54
+
55
+ return memoFrame;
56
+ };
57
+
58
+ type MemoizedSafeAreaFrameOptions = {
59
+ updateForInactiveScreens?: boolean;
60
+ };
61
+
62
+ /**
63
+ * Hook that wraps useSafeAreaFrame with memoization and inactive screen filtering.
64
+ * Uses useIsScreenActive() internally to determine active state - use useMemoizedSafeAreaFrameWithActiveState
65
+ * if you need to pass isActive explicitly.
66
+ *
67
+ * @param options.updateForInactiveScreens - If false, frame won't update when screen is inactive (default: true)
68
+ * @returns The memoized safe area frame { x, y, width, height }
69
+ */
70
+ export const useMemoizedSafeAreaFrame = (
71
+ options: MemoizedSafeAreaFrameOptions = {}
72
+ ) => {
73
+ const { updateForInactiveScreens = true } = options;
74
+ const isActive = useIsScreenActive();
75
+
76
+ return useMemoizedSafeAreaFrameWithActiveState({
77
+ updateForInactiveScreens,
78
+ isActive,
79
+ });
80
+ };
13
81
 
14
82
  export const useWaitForValidOrientation = () => {
15
- const { width: screenWidth, height } = useDimensions("screen", {
16
- fullDimensions: true,
83
+ // Use memoized safe area frame to synchronize with Scene's dimension source
84
+ // This prevents race conditions where the orientation check passes before
85
+ // Scene's memoFrame has updated to the new dimensions
86
+ const { width: screenWidth, height } = useMemoizedSafeAreaFrame({
17
87
  updateForInactiveScreens: false,
18
88
  });
19
89
 
@@ -25,7 +95,8 @@ export const useWaitForValidOrientation = () => {
25
95
 
26
96
  const isTablet = useIsTablet();
27
97
 
28
- const { isTabletPortrait } = useAppData();
98
+ const { appData } = usePickFromState(["appData"]);
99
+ const isTabletPortrait = appData?.isTabletPortrait;
29
100
 
30
101
  const layoutData = useMemo(
31
102
  () => ({ isTablet, isTabletPortrait, width: screenWidth, height }),
@@ -1,7 +1,8 @@
1
1
  /// <reference types="@applicaster/applicaster-types" />
2
2
  import React from "react";
3
3
  import { AccessibilityInfo, findNodeHandle, View } from "react-native";
4
- import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
4
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
+
5
6
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
6
7
  import { getComponentModule } from "@applicaster/zapp-react-native-utils/pluginUtils";
7
8
  import {
@@ -40,7 +41,7 @@ type Props = {
40
41
  export function Screen(_props: Props) {
41
42
  const theme = useTheme<BaseThemePropertiesMobile>();
42
43
  const navigation = useNavigation();
43
- const plugins = usePlugins();
44
+ const { plugins } = usePickFromState(["plugins"]);
44
45
 
45
46
  // Gets the data for the current screen configuration
46
47
  const currentScreenData = useCurrentScreenData();
@@ -92,7 +93,13 @@ export function Screen(_props: Props) {
92
93
  const isActive = useIsScreenActive();
93
94
 
94
95
  const ref = React.useRef(null);
95
- const isReady = useWaitForValidOrientation();
96
+ const isOrientationReady = useWaitForValidOrientation();
97
+
98
+ // Playable screens handle their own orientation via the native player plugin,
99
+ // so we skip the orientation wait gate to avoid a deadlock where the screen
100
+ // waits for landscape but blocks rendering that would trigger the rotation.
101
+ const isPlayableRoute = pathname?.includes("/playable");
102
+ const isReady = isOrientationReady || isPlayableRoute;
96
103
 
97
104
  React.useEffect(() => {
98
105
  if (ref.current && isActive && isReady) {
@@ -7,7 +7,7 @@ import {
7
7
  useGetScreenOrientation,
8
8
  } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
9
9
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
10
- import { usePlugins, useAppData } from "@applicaster/zapp-react-native-redux";
10
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
11
11
  import { findPluginByType } from "@applicaster/zapp-react-native-utils/pluginUtils";
12
12
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
13
13
  import { zappHookModalStore } from "../../Contexts/ZappHookModalContext";
@@ -63,8 +63,8 @@ export function useNewOrientationForScreenData({
63
63
  }: OrientationHookArgs) {
64
64
  const isTablet = useIsTablet();
65
65
 
66
- const plugins = usePlugins();
67
- const { isTabletPortrait } = useAppData();
66
+ const { appData, plugins } = usePickFromState(["appData", "plugins"]);
67
+ const isTabletPortrait = appData?.isTabletPortrait;
68
68
  const isLandscapeTablet = isTablet && !isTabletPortrait;
69
69
 
70
70
  const screenOrientation = useGetScreenOrientation(screenData);
@@ -0,0 +1,46 @@
1
+ import React, { useEffect } from "react";
2
+ import { PreloaderWrapper } from "../PreloaderWrapper";
3
+ import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
4
+ import { useFeedLoader } from "@applicaster/zapp-react-native-utils/reactHooks";
5
+
6
+ import { componentsLogger } from "../../Helpers/logger";
7
+
8
+ const logger = componentsLogger.addSubsystem("ScreenFeedLoader");
9
+
10
+ /** Loads and provides `feedData` and store to */
11
+ export const ScreenFeedLoader: React.FC<
12
+ React.PropsWithChildren<{ id: string; feedData: any }>
13
+ > = ({ id, feedData, children }) => {
14
+ const { source: feedUrl, mapping } = feedData;
15
+
16
+ const { data, loading, error } = useFeedLoader({
17
+ feedUrl,
18
+ mapping,
19
+ pipesOptions: {},
20
+ });
21
+
22
+ const feedStore = useScreenContextV2()._feedStore;
23
+
24
+ useEffect(() => {
25
+ if (data && !loading) {
26
+ feedStore.setState({ screenFeed: data, screenFeedError: null });
27
+
28
+ logger.log("screenFeed set for active screen", { data, screenId: id });
29
+ }
30
+
31
+ if (error && !loading) {
32
+ feedStore.setState({ screenFeed: data, screenFeedError: error });
33
+
34
+ logger.warning("Feed data error:", {
35
+ data,
36
+ loading,
37
+ error,
38
+ screenId: id,
39
+ });
40
+ }
41
+ }, [data, loading, error, feedStore, id]);
42
+
43
+ return (
44
+ <PreloaderWrapper showPreloader={loading}>{children}</PreloaderWrapper>
45
+ );
46
+ };
@@ -0,0 +1,94 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import { render, waitFor } from "@testing-library/react-native";
4
+ import { ScreenFeedLoader } from "../ScreenFeedLoader";
5
+
6
+ const mockUseFeedLoader = jest.fn();
7
+ const mockUseScreenContextV2 = jest.fn();
8
+ const mockSetState = jest.fn();
9
+
10
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
11
+ useFeedLoader: (...args) => mockUseFeedLoader(...args),
12
+ }));
13
+
14
+ jest.mock(
15
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
16
+ () => ({
17
+ useScreenContextV2: (...args) => mockUseScreenContextV2(...args),
18
+ })
19
+ );
20
+
21
+ describe("ScreenFeedLoader", () => {
22
+ beforeEach(() => {
23
+ jest.clearAllMocks();
24
+
25
+ mockUseScreenContextV2.mockReturnValue({
26
+ _feedStore: {
27
+ setState: mockSetState,
28
+ },
29
+ });
30
+ });
31
+
32
+ it("hides children while loading", () => {
33
+ mockUseFeedLoader.mockReturnValue({
34
+ data: null,
35
+ loading: true,
36
+ error: null,
37
+ });
38
+
39
+ const { queryByText } = render(
40
+ <ScreenFeedLoader id="test" feedData={{ source: "url", mapping: {} }}>
41
+ <Text>child</Text>
42
+ </ScreenFeedLoader>
43
+ );
44
+
45
+ expect(queryByText("child")).toBeNull();
46
+ });
47
+
48
+ it("writes loaded feed data to _feedStore", async () => {
49
+ const data = { entry: { id: "1" } };
50
+
51
+ mockUseFeedLoader.mockReturnValue({
52
+ data,
53
+ loading: false,
54
+ error: null,
55
+ });
56
+
57
+ render(
58
+ <ScreenFeedLoader id="test" feedData={{ source: "url", mapping: {} }}>
59
+ <Text>child</Text>
60
+ </ScreenFeedLoader>
61
+ );
62
+
63
+ await waitFor(() => {
64
+ expect(mockSetState).toHaveBeenCalledWith({
65
+ screenFeed: data,
66
+ screenFeedError: null,
67
+ });
68
+ });
69
+ });
70
+
71
+ it("writes feed error to _feedStore", async () => {
72
+ const error = new Error("feed failed");
73
+ const data = { fallback: true };
74
+
75
+ mockUseFeedLoader.mockReturnValue({
76
+ data,
77
+ loading: false,
78
+ error,
79
+ });
80
+
81
+ render(
82
+ <ScreenFeedLoader id="test" feedData={{ source: "url", mapping: {} }}>
83
+ <Text>child</Text>
84
+ </ScreenFeedLoader>
85
+ );
86
+
87
+ await waitFor(() => {
88
+ expect(mockSetState).toHaveBeenCalledWith({
89
+ screenFeed: data,
90
+ screenFeedError: error,
91
+ });
92
+ });
93
+ });
94
+ });
@@ -0,0 +1 @@
1
+ export { ScreenFeedLoader } from "./ScreenFeedLoader";
@@ -53,6 +53,9 @@ const mockComponents = { ScreenType1, ScreenType2, PlayerController };
53
53
 
54
54
  const mockState = {
55
55
  components: mockComponents,
56
+ remoteConfigurations: {
57
+ assets: {},
58
+ },
56
59
  plugins: [
57
60
  mockScreenType3,
58
61
  mockScreenType4,
@@ -127,6 +130,21 @@ const getWrapper = (screenId, screenType, screenData) => {
127
130
  );
128
131
  };
129
132
 
133
+ const getWrappedWrapper = (screenId, screenType, screenData) => {
134
+ const ScreenResolver = require("../").ScreenResolver;
135
+
136
+ return renderWithProviders(
137
+ <ScreenResolver
138
+ {...{
139
+ screenId,
140
+ screenType,
141
+ screenData,
142
+ }}
143
+ />,
144
+ mockState
145
+ );
146
+ };
147
+
130
148
  describe("<ScreenResolver />", () => {
131
149
  it("renders correctly", () => {
132
150
  const wrapper = getWrapper("1234", "screen_type_1", {});
@@ -134,6 +152,12 @@ describe("<ScreenResolver />", () => {
134
152
  expect(wrapper.getByTestId("screen_type_1")).toBeDefined();
135
153
  });
136
154
 
155
+ it("renders correctly when wrapped with default screen context", () => {
156
+ const wrapper = getWrappedWrapper("1234", "screen_type_1", {});
157
+
158
+ expect(wrapper.getByTestId("screen_type_1")).toBeDefined();
159
+ });
160
+
137
161
  it("picks screen from plugins if it exists", () => {
138
162
  const wrapper = getWrapper("A1234", "screen_type_3", {});
139
163
 
@@ -0,0 +1,3 @@
1
+ export { useGetComponent } from "./useGetComponent";
2
+
3
+ export { useScreenComponentResolver } from "./useScreenComponentResolver";
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+
3
+ import { toPascalCase } from "@applicaster/zapp-react-native-utils/stringUtils";
4
+ import {
5
+ useAppSelector,
6
+ selectComponents,
7
+ } from "@applicaster/zapp-react-native-redux";
8
+
9
+ export const useGetComponent = (screenType) => {
10
+ const components = useAppSelector(selectComponents);
11
+
12
+ return React.useMemo(() => {
13
+ return components[toPascalCase(screenType)];
14
+ }, [components, screenType]);
15
+ };
@@ -0,0 +1,90 @@
1
+ import * as React from "react";
2
+ import { path, prop } from "ramda";
3
+ import {
4
+ findPluginByType,
5
+ findPluginByIdentifier,
6
+ } from "@applicaster/zapp-react-native-utils/pluginUtils";
7
+ import { HandlePlayable } from "../../HandlePlayable";
8
+ import { HookRenderer } from "../../HookRenderer";
9
+ import { LinkHandler } from "../../LinkHandler";
10
+ import { Favorites } from "../../Favorites";
11
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
12
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
13
+
14
+ import { useCallbackActions } from "@applicaster/zapp-react-native-utils/zappFrameworkUtils/HookCallback/useCallbackActions";
15
+ import { useGetComponent } from "./useGetComponent";
16
+ import { getScreenTypeProps } from "../utils";
17
+
18
+ export enum PresentationType {
19
+ Standalone = "Standalone",
20
+ Hook = "Hook",
21
+ }
22
+
23
+ const screenTypeComponents = {
24
+ favorites: Favorites,
25
+ link: LinkHandler,
26
+ playable: HandlePlayable,
27
+ hooks: HookRenderer,
28
+ };
29
+
30
+ export const useScreenComponentResolver = (screenType, props) => {
31
+ const plugins = usePlugins();
32
+ const { hookPlugin } = props.screenData || {};
33
+ const component = useGetComponent(screenType);
34
+
35
+ const screenAction = useCallbackActions(
36
+ hookPlugin || props.screenData,
37
+ props.screenData.callback
38
+ );
39
+
40
+ const {
41
+ videoModalState: { mode },
42
+ } = useNavigation();
43
+
44
+ const componentProps = {
45
+ ...props,
46
+ mode,
47
+ screenAction,
48
+ };
49
+
50
+ const ScreenTypeComponent = screenTypeComponents?.[screenType];
51
+
52
+ if (ScreenTypeComponent) {
53
+ return (
54
+ <ScreenTypeComponent
55
+ {...getScreenTypeProps(screenType, componentProps)}
56
+ />
57
+ );
58
+ }
59
+
60
+ const ScreenPlugin =
61
+ findPluginByType(screenType, plugins, { skipWarning: true }) ||
62
+ findPluginByIdentifier(screenType, plugins) ||
63
+ findPluginByIdentifier(hookPlugin && hookPlugin.identifier, plugins) ||
64
+ component;
65
+
66
+ const ScreenComponent =
67
+ path(["module", "Component"], ScreenPlugin) ||
68
+ prop("module", ScreenPlugin) ||
69
+ prop("Component", ScreenPlugin) ||
70
+ ScreenPlugin;
71
+
72
+ const configuration =
73
+ prop("configuration", ScreenPlugin) ||
74
+ prop("__plugin_configuration", ScreenComponent);
75
+
76
+ if (!ScreenComponent) {
77
+ return null;
78
+ }
79
+
80
+ return (
81
+ <ScreenComponent
82
+ {...props}
83
+ callback={props.resultCallback || screenAction}
84
+ screenId={props.screenId}
85
+ screenData={props.screenData}
86
+ presentationType={PresentationType.Standalone}
87
+ configuration={configuration}
88
+ />
89
+ );
90
+ };
@@ -1,29 +1,9 @@
1
1
  import * as React from "react";
2
- import { path, prop } from "ramda";
3
- import {
4
- findPluginByType,
5
- findPluginByIdentifier,
6
- } from "@applicaster/zapp-react-native-utils/pluginUtils";
7
- import { HandlePlayable } from "../HandlePlayable";
8
- import { toPascalCase } from "@applicaster/zapp-react-native-utils/stringUtils";
9
- import { HookRenderer } from "../HookRenderer";
10
- import { LinkHandler } from "../LinkHandler";
11
- import { Favorites } from "../Favorites";
12
- import { ZappPipesScreenContext } from "../../Contexts";
2
+
13
3
  import { componentsLogger } from "../../Helpers/logger";
14
- import {
15
- useAppSelector,
16
- usePlugins,
17
- } from "@applicaster/zapp-react-native-redux/hooks";
18
- import {
19
- useNavigation,
20
- useRivers,
21
- } from "@applicaster/zapp-react-native-utils/reactHooks";
22
4
  import { useScreenAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks";
23
-
24
- import { useCallbackActions } from "@applicaster/zapp-react-native-utils/zappFrameworkUtils/HookCallback/useCallbackActions";
25
- import { ScreenResultCallback } from "@applicaster/zapp-react-native-utils/zappFrameworkUtils/HookCallback/callbackNavigationAction";
26
- import { selectComponents } from "@applicaster/zapp-react-native-redux";
5
+ import { useScreenComponentResolver } from "./hooks/useScreenComponentResolver";
6
+ import { withDefaultScreenContext } from "./withDefaultScreenContext";
27
7
 
28
8
  const logger = componentsLogger.addSubsystem("ScreenResolver");
29
9
 
@@ -34,7 +14,6 @@ type Props = {
34
14
  feedId?: string;
35
15
  feedTitle?: string;
36
16
  focused?: boolean;
37
- resultCallback?: ScreenResultCallback;
38
17
  parentFocus?: {
39
18
  nextFocusDown?: React.Ref<any>;
40
19
  nextFocusRight?: React.Ref<any>;
@@ -50,96 +29,13 @@ export enum PresentationType {
50
29
  }
51
30
 
52
31
  export function ScreenResolverComponent(props: Props) {
53
- useScreenAnalytics(props);
54
-
55
- const { screenType, screenId, screenData, groupId } = props;
56
-
57
- const { hookPlugin } = screenData || {};
58
-
59
- const plugins = usePlugins();
60
- const rivers = useRivers();
61
-
62
- const components = useAppSelector(selectComponents);
63
-
64
- const {
65
- videoModalState: { mode },
66
- } = useNavigation();
67
-
68
- const [, setScreenContext] = ZappPipesScreenContext.useZappPipesContext();
69
-
70
- React.useEffect(() => {
71
- setScreenContext(rivers[screenId]);
72
- }, [rivers, screenId, setScreenContext]);
73
-
74
- const parentCallback = props.resultCallback;
75
-
76
- const screenAction = useCallbackActions(
77
- hookPlugin || screenData,
78
- screenData.callback
79
- );
32
+ const { screenType } = props;
33
+ const component = useScreenComponentResolver(screenType, props);
80
34
 
81
- const callbackAction = parentCallback || screenAction;
82
-
83
- const ScreenPlugin =
84
- findPluginByType(screenType, plugins, { skipWarning: true }) ||
85
- findPluginByIdentifier(screenType, plugins) ||
86
- findPluginByIdentifier(hookPlugin && hookPlugin.identifier, plugins) ||
87
- components[toPascalCase(screenType)];
88
-
89
- if (screenType === "favorites") {
90
- return <Favorites screenData={screenData} />;
91
- }
92
-
93
- if (screenType === "link") {
94
- return <LinkHandler screenData={screenData} />;
95
- }
96
-
97
- if (screenType === "playable") {
98
- return (
99
- // @ts-ignore
100
- <HandlePlayable
101
- item={screenData}
102
- mode={mode === "PIP" ? "PIP" : "FULLSCREEN"}
103
- isModal={false}
104
- groupId={groupId}
105
- />
106
- );
107
- }
108
-
109
- if (hookPlugin || screenType === "hooks") {
110
- return (
111
- screenData && (
112
- <HookRenderer
113
- callback={callbackAction}
114
- screenData={screenData}
115
- focused={props.focused}
116
- parentFocus={props.parentFocus as ParentFocus}
117
- />
118
- )
119
- );
120
- }
121
-
122
- const ScreenComponent =
123
- path(["module", "Component"], ScreenPlugin) ||
124
- prop("module", ScreenPlugin) ||
125
- prop("Component", ScreenPlugin) ||
126
- ScreenPlugin;
127
-
128
- const configuration =
129
- prop("configuration", ScreenPlugin) ||
130
- prop("__plugin_configuration", ScreenComponent);
35
+ useScreenAnalytics(props);
131
36
 
132
- if (ScreenComponent) {
133
- return (
134
- <ScreenComponent
135
- {...props}
136
- callback={callbackAction}
137
- screenId={screenId}
138
- screenData={screenData}
139
- configuration={configuration}
140
- presentationType={PresentationType.Standalone}
141
- />
142
- );
37
+ if (component) {
38
+ return component;
143
39
  }
144
40
 
145
41
  logger.warning({
@@ -150,6 +46,4 @@ export function ScreenResolverComponent(props: Props) {
150
46
  return null;
151
47
  }
152
48
 
153
- export const ScreenResolver = ZappPipesScreenContext.withProvider(
154
- ScreenResolverComponent
155
- );
49
+ export const ScreenResolver = withDefaultScreenContext(ScreenResolverComponent);
@@ -0,0 +1,45 @@
1
+ import { getScreenTypeProps } from "../getScreenTypeProps";
2
+
3
+ const baseProps = {
4
+ screenData: { id: "entry-1" },
5
+ mode: "PIP",
6
+ screenId: "screen-1",
7
+ groupId: "group-1",
8
+ focused: true,
9
+ parentFocus: { nextFocusDown: { current: null } },
10
+ screenAction: jest.fn(),
11
+ resultCallback: null,
12
+ };
13
+
14
+ describe("getScreenTypeProps", () => {
15
+ it("returns props for favorites/link", () => {
16
+ expect(getScreenTypeProps("favorites", baseProps)).toEqual({
17
+ screenData: baseProps.screenData,
18
+ });
19
+
20
+ expect(getScreenTypeProps("link", baseProps)).toEqual({
21
+ screenData: baseProps.screenData,
22
+ });
23
+ });
24
+
25
+ it("returns props for playable", () => {
26
+ expect(getScreenTypeProps("playable", baseProps)).toEqual({
27
+ item: baseProps.screenData,
28
+ mode: "PIP",
29
+ isModal: false,
30
+ groupId: "group-1",
31
+ });
32
+ });
33
+
34
+ it("returns props for hooks", () => {
35
+ const result = getScreenTypeProps("hooks", baseProps);
36
+
37
+ expect(result).toMatchObject({
38
+ screenData: baseProps.screenData,
39
+ focused: true,
40
+ parentFocus: baseProps.parentFocus,
41
+ });
42
+
43
+ expect(result.callback).toBe(baseProps.screenAction);
44
+ });
45
+ });