@applicaster/zapp-react-native-ui-components 15.0.0-rc.113 → 15.0.0-rc.115

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 (31) hide show
  1. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +35 -19
  2. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  3. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  4. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  5. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  6. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  7. package/Components/PlayerContainer/PlayerContainer.tsx +0 -9
  8. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  9. package/Components/PreloaderWrapper/index.tsx +15 -0
  10. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  11. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  12. package/Components/ScreenFeedLoader/index.ts +1 -0
  13. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  14. package/Components/ScreenResolver/hooks/index.ts +3 -0
  15. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  16. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  17. package/Components/ScreenResolver/index.tsx +14 -120
  18. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  19. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  20. package/Components/ScreenResolver/utils/index.ts +1 -0
  21. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  22. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  23. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  24. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  25. package/Components/index.js +1 -1
  26. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  27. package/Contexts/ScreenContext/index.tsx +17 -0
  28. package/package.json +5 -5
  29. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  30. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  31. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -12,12 +12,26 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
12
12
  import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
13
13
  import { ScreenTrackedViewPositionsContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenTrackedViewPositionsContext";
14
14
  import { useEventAlerts } from "./utils/useEventAlerts";
15
+ import {
16
+ selectRiverById,
17
+ useAppSelector,
18
+ } from "@applicaster/zapp-react-native-redux";
19
+ import { getScreenDataSource } from "./utils/getScreenDataSource";
20
+ import { ScreenResolverFeedProvider } from "../ScreenResolverFeedProvider/ScreenResolverFeedProvider";
15
21
 
16
22
  const { log_debug } = createLogger({
17
23
  category: "ScreenContainer",
18
24
  subsystem: "General",
19
25
  });
20
26
 
27
+ /** Provides screen-feed from general-screen configuration (if defined) */
28
+ const useFeedData = (id) => {
29
+ const river = useAppSelector((state) => selectRiverById(state, id));
30
+ const feedData = getScreenDataSource(river);
31
+
32
+ return feedData;
33
+ };
34
+
21
35
  export const GeneralContentScreen = ({
22
36
  feed,
23
37
  screenId,
@@ -103,24 +117,26 @@ export const GeneralContentScreen = ({
103
117
  if (!isReady || isNilOrEmpty(components || uiComponents)) return null;
104
118
 
105
119
  return (
106
- <ScreenTrackedViewPositionsContext.Provider>
107
- <CellTapContext.Provider value={contextValue}>
108
- <ComponentsMap
109
- feed={feed}
110
- riverId={screenId}
111
- groupId={groupId || `general-content-screen-${screenId}`}
112
- riverComponents={components || uiComponents}
113
- scrollViewExtraProps={scrollViewExtraProps}
114
- getStaticComponentFeed={getStaticComponentFeed}
115
- extraAnchorPointYOffset={extraAnchorPointYOffset}
116
- isScreenWrappedInContainer={isScreenWrappedInContainer}
117
- parentFocus={parentFocus}
118
- focused={focused}
119
- containerHeight={containerHeight}
120
- preferredFocus={preferredFocus}
121
- {...componentsMapExtraProps}
122
- />
123
- </CellTapContext.Provider>
124
- </ScreenTrackedViewPositionsContext.Provider>
120
+ <ScreenResolverFeedProvider id={screenId} useFeedData={useFeedData}>
121
+ <ScreenTrackedViewPositionsContext.Provider>
122
+ <CellTapContext.Provider value={contextValue}>
123
+ <ComponentsMap
124
+ feed={feed}
125
+ riverId={screenId}
126
+ groupId={groupId || `general-content-screen-${screenId}`}
127
+ riverComponents={components || uiComponents}
128
+ scrollViewExtraProps={scrollViewExtraProps}
129
+ getStaticComponentFeed={getStaticComponentFeed}
130
+ extraAnchorPointYOffset={extraAnchorPointYOffset}
131
+ isScreenWrappedInContainer={isScreenWrappedInContainer}
132
+ parentFocus={parentFocus}
133
+ focused={focused}
134
+ containerHeight={containerHeight}
135
+ preferredFocus={preferredFocus}
136
+ {...componentsMapExtraProps}
137
+ />
138
+ </CellTapContext.Provider>
139
+ </ScreenTrackedViewPositionsContext.Provider>
140
+ </ScreenResolverFeedProvider>
125
141
  );
126
142
  };
@@ -0,0 +1,104 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+ import { GeneralContentScreen } from "../GeneralContentScreen";
4
+
5
+ const mockUseAppSelector = jest.fn();
6
+ const mockSelectRiverById = jest.fn();
7
+ const mockProviderSpy = jest.fn();
8
+
9
+ jest.mock("../../River/ComponentsMap", () => ({
10
+ ComponentsMap: () => {
11
+ const React = require("react");
12
+ const { View } = require("react-native");
13
+
14
+ return <View testID="components-map" />;
15
+ },
16
+ }));
17
+
18
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
19
+ useActions: jest.fn(() => jest.fn()),
20
+ }));
21
+
22
+ jest.mock("../utils", () => ({
23
+ logger: { warn: jest.fn() },
24
+ whenMatchingType: jest.fn((_type, value) => value),
25
+ }));
26
+
27
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/layout", () => ({
28
+ useLayoutVersion: jest.fn(() => false),
29
+ }));
30
+
31
+ jest.mock(
32
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData",
33
+ () => ({
34
+ useScreenData: jest.fn(() => ({
35
+ ui_components: [{ id: "ui-component" }],
36
+ general: {},
37
+ })),
38
+ })
39
+ );
40
+
41
+ jest.mock("../utils/useCurationAPI", () => ({
42
+ useCurationAPI: jest.fn(() => [{ id: "curation-component" }]),
43
+ }));
44
+
45
+ jest.mock("@applicaster/quick-brick-core/App/ActionSetters", () => ({
46
+ useRiverInitialState: jest.fn(() => []),
47
+ }));
48
+
49
+ jest.mock("../utils/useEventAlerts", () => ({
50
+ useEventAlerts: jest.fn(),
51
+ }));
52
+
53
+ jest.mock("@applicaster/zapp-react-native-redux", () => ({
54
+ useAppSelector: (...args) => mockUseAppSelector(...args),
55
+ selectRiverById: (...args) => mockSelectRiverById(...args),
56
+ }));
57
+
58
+ jest.mock("../utils/getScreenDataSource", () => ({
59
+ getScreenDataSource: jest.fn(() => ({
60
+ source: "https://feed",
61
+ mapping: {},
62
+ })),
63
+ }));
64
+
65
+ jest.mock(
66
+ "../../ScreenResolverFeedProvider/ScreenResolverFeedProvider",
67
+ () => ({
68
+ ScreenResolverFeedProvider: ({ id, useFeedData, children }) => {
69
+ const React = require("react");
70
+ const { View } = require("react-native");
71
+
72
+ mockProviderSpy(id, useFeedData);
73
+ useFeedData(id);
74
+
75
+ return <View testID="screen-resolver-feed-provider">{children}</View>;
76
+ },
77
+ })
78
+ );
79
+
80
+ describe("GeneralContentScreen", () => {
81
+ beforeEach(() => {
82
+ jest.clearAllMocks();
83
+ mockUseAppSelector.mockImplementation((selector) => selector({}));
84
+ mockSelectRiverById.mockReturnValue({ id: "screen-1" });
85
+ });
86
+
87
+ it("wraps content with ScreenResolverFeedProvider and renders ComponentsMap", () => {
88
+ const { getByTestId } = render(
89
+ <GeneralContentScreen
90
+ screenId="screen-1"
91
+ feed={null}
92
+ components={[{ id: "component-1" }]}
93
+ />
94
+ );
95
+
96
+ expect(getByTestId("screen-resolver-feed-provider")).toBeDefined();
97
+ expect(getByTestId("components-map")).toBeDefined();
98
+
99
+ expect(mockProviderSpy).toHaveBeenCalledWith(
100
+ "screen-1",
101
+ expect.any(Function)
102
+ );
103
+ });
104
+ });
@@ -0,0 +1,19 @@
1
+ import { getScreenDataSource } from "../getScreenDataSource";
2
+
3
+ describe("getScreenDataSource", () => {
4
+ it("returns screen_feed data when present", () => {
5
+ const result = getScreenDataSource({
6
+ data: {
7
+ screen_feed: {
8
+ source: "https://feed",
9
+ },
10
+ },
11
+ });
12
+
13
+ expect(result).toEqual({ source: "https://feed" });
14
+ });
15
+
16
+ it("returns undefined when screen_feed is missing", () => {
17
+ expect(getScreenDataSource({ data: {} })).toBeUndefined();
18
+ });
19
+ });
@@ -0,0 +1,9 @@
1
+ import { get } from "@applicaster/zapp-react-native-utils/utils";
2
+
3
+ const lookupPath = ["data", "screen_feed"];
4
+
5
+ export const getScreenDataSource = (
6
+ screenData: any
7
+ ): Option<ZappDataSource> => {
8
+ return get(screenData, lookupPath) as ZappDataSource | undefined;
9
+ };
@@ -6,7 +6,12 @@ import {
6
6
  } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
7
7
  import { useHookAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks";
8
8
  import { useSetNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks";
9
- import { PresentationType } from "../ScreenResolver";
9
+
10
+ import { componentsLogger } from "../../Helpers/logger";
11
+
12
+ const logger = componentsLogger.addSubsystem("HookRenderer");
13
+
14
+ const HOOK_PRESENTATION_TYPE = "Hook";
10
15
 
11
16
  type Props = {
12
17
  focused?: boolean;
@@ -15,20 +20,17 @@ type Props = {
15
20
  callback: hookCallback;
16
21
  };
17
22
 
18
- export const HookRenderer = (props: Props) => {
19
- const {
20
- focused,
21
- screenData: { payload, hookPlugin },
22
- callback,
23
- } = props;
24
-
25
- const { setVisible: showNavBar } = useSetNavbarState();
23
+ const HookRenderer = (props: Props) => {
24
+ const { focused, screenData, callback } = props;
25
+ const { payload, hookPlugin } = screenData;
26
26
 
27
27
  const {
28
28
  module: { Component: HookComponent, presentFullScreen },
29
29
  configuration,
30
30
  } = hookPlugin;
31
31
 
32
+ const { setVisible: showNavBar } = useSetNavbarState();
33
+
32
34
  useHookAnalytics(props);
33
35
 
34
36
  const isNavBarVisible = useIsNavBarVisible();
@@ -63,8 +65,36 @@ export const HookRenderer = (props: Props) => {
63
65
  hookPlugin,
64
66
  focused,
65
67
  parentFocus,
66
- presentationType: PresentationType.Hook,
68
+ presentationType: HOOK_PRESENTATION_TYPE,
67
69
  }}
68
70
  />
69
71
  );
70
72
  };
73
+
74
+ /**
75
+ * Guard component to prevent rendering HookRenderer when screenData or hookPlugin is missing. This is to avoid potential crashes due to missing data.
76
+ */
77
+ const HookRendererGuard = (props: Props) => {
78
+ React.useEffect(() => {
79
+ if (!props.screenData) {
80
+ logger.error(
81
+ "HookRenderer received no screenData, screen cannot be rendered"
82
+ );
83
+ } else if (!props.screenData.hookPlugin) {
84
+ logger.error(
85
+ "HookRenderer received screenData with no hookPlugin, screen cannot be rendered",
86
+ {
87
+ screenData: props.screenData,
88
+ }
89
+ );
90
+ }
91
+ }, [props.screenData]);
92
+
93
+ if (!props.screenData || !props.screenData.hookPlugin) {
94
+ return null;
95
+ }
96
+
97
+ return <HookRenderer {...props} />;
98
+ };
99
+
100
+ export { HookRendererGuard as HookRenderer };
@@ -0,0 +1,60 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+ import { HookRenderer } from "..";
5
+
6
+ jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
7
+ isTV: jest.fn(() => false),
8
+ }));
9
+
10
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
11
+ useBackHandler: jest.fn(),
12
+ useIsNavBarVisible: jest.fn(() => true),
13
+ }));
14
+
15
+ jest.mock(
16
+ "@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks",
17
+ () => ({
18
+ useHookAnalytics: jest.fn(),
19
+ })
20
+ );
21
+
22
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
23
+ useSetNavbarState: jest.fn(() => ({
24
+ setVisible: jest.fn(),
25
+ })),
26
+ }));
27
+
28
+ describe("HookRenderer", () => {
29
+ it("returns null when hookPlugin is missing", () => {
30
+ const { toJSON } = render(
31
+ <HookRenderer callback={jest.fn()} screenData={{ payload: {} } as any} />
32
+ );
33
+
34
+ expect(toJSON()).toBeNull();
35
+ });
36
+
37
+ it("passes Hook presentationType to rendered hook component", () => {
38
+ const HookComponent = (props) => (
39
+ <View testID="hook-component" {...props} />
40
+ );
41
+
42
+ const { getByTestId } = render(
43
+ <HookRenderer
44
+ callback={jest.fn()}
45
+ screenData={{
46
+ payload: { foo: "bar" },
47
+ hookPlugin: {
48
+ module: {
49
+ Component: HookComponent,
50
+ presentFullScreen: false,
51
+ },
52
+ configuration: {},
53
+ },
54
+ }}
55
+ />
56
+ );
57
+
58
+ expect(getByTestId("hook-component").props.presentationType).toBe("Hook");
59
+ });
60
+ });
@@ -46,7 +46,6 @@ import {
46
46
  PlayerContainerContextProvider,
47
47
  } from "./PlayerContainerContext";
48
48
  import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
49
- import { ErrorDisplay } from "./ErrorDisplay";
50
49
  import { PlayerFocusableWrapperView } from "./WappersView/PlayerFocusableWrapperView";
51
50
  import { FocusableGroupMainContainerId } from "./index";
52
51
  import { isPlayable } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypeMatchers";
@@ -339,12 +338,6 @@ const PlayerContainerComponent = (props: Props) => {
339
338
  playerContainerLogger.error(errorObj);
340
339
 
341
340
  setState({ error: errorObj });
342
-
343
- if (!isTvOS) {
344
- setTimeout(() => {
345
- close();
346
- }, 800);
347
- }
348
341
  },
349
342
  [close]
350
343
  );
@@ -695,8 +688,6 @@ const PlayerContainerComponent = (props: Props) => {
695
688
  </Player>
696
689
  )}
697
690
  </PlayerFocusableWrapperView>
698
-
699
- {state.error ? <ErrorDisplay error={state.error} /> : null}
700
691
  </View>
701
692
  {/* Components container */}
702
693
  {isInlineTV && context.showComponentsContainer ? (
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+ import { PreloaderWrapper } from "..";
5
+
6
+ describe("PreloaderWrapper", () => {
7
+ it("renders children when preloader is hidden", () => {
8
+ const { getByText } = render(
9
+ <PreloaderWrapper showPreloader={false}>
10
+ <Text>content</Text>
11
+ </PreloaderWrapper>
12
+ );
13
+
14
+ expect(getByText("content")).toBeDefined();
15
+ });
16
+
17
+ it("renders null when preloader is shown", () => {
18
+ const { queryByText } = render(
19
+ <PreloaderWrapper showPreloader>
20
+ <Text>content</Text>
21
+ </PreloaderWrapper>
22
+ );
23
+
24
+ expect(queryByText("content")).toBeNull();
25
+ });
26
+ });
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+
3
+ type PreloaderWrapperProps = {
4
+ showPreloader?: boolean;
5
+ children?: React.ReactNode;
6
+ };
7
+
8
+ export const PreloaderWrapper: React.FC<PreloaderWrapperProps> = ({
9
+ showPreloader = false,
10
+ children,
11
+ }) => {
12
+ return !showPreloader ? children : null;
13
+ };
14
+
15
+ export default PreloaderWrapper;
@@ -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,17 @@
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";
4
+
22
5
  import { useScreenAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks";
23
6
 
24
- import { useCallbackActions } from "@applicaster/zapp-react-native-utils/zappFrameworkUtils/HookCallback/useCallbackActions";
25
7
  import { ScreenResultCallback } from "@applicaster/zapp-react-native-utils/zappFrameworkUtils/HookCallback/callbackNavigationAction";
26
- import { selectComponents } from "@applicaster/zapp-react-native-redux";
8
+ import { useScreenComponentResolver } from "./hooks/useScreenComponentResolver";
9
+ import { withDefaultScreenContext } from "./withDefaultScreenContext";
10
+
11
+ export enum PresentationType {
12
+ Standalone = "Standalone",
13
+ Hook = "Hook",
14
+ }
27
15
 
28
16
  const logger = componentsLogger.addSubsystem("ScreenResolver");
29
17
 
@@ -44,95 +32,14 @@ type Props = {
44
32
  groupId?: string;
45
33
  };
46
34
 
47
- export enum PresentationType {
48
- Standalone = "Standalone",
49
- Hook = "Hook",
50
- }
51
-
52
35
  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
-
61
- const components = useAppSelector(selectComponents);
62
-
63
- const {
64
- videoModalState: { mode },
65
- } = useNavigation();
36
+ const { screenType } = props;
37
+ const component = useScreenComponentResolver(screenType, props);
66
38
 
67
- const parentCallback = props.resultCallback;
68
-
69
- const screenAction = useCallbackActions(
70
- hookPlugin || screenData,
71
- screenData.callback
72
- );
73
-
74
- const callbackAction = parentCallback || screenAction;
75
-
76
- const ScreenPlugin =
77
- findPluginByType(screenType, plugins, { skipWarning: true }) ||
78
- findPluginByIdentifier(screenType, plugins) ||
79
- findPluginByIdentifier(hookPlugin && hookPlugin.identifier, plugins) ||
80
- components[toPascalCase(screenType)];
81
-
82
- if (screenType === "favorites") {
83
- return <Favorites screenData={screenData} />;
84
- }
85
-
86
- if (screenType === "link") {
87
- return <LinkHandler screenData={screenData} />;
88
- }
89
-
90
- if (screenType === "playable") {
91
- return (
92
- // @ts-ignore
93
- <HandlePlayable
94
- item={screenData}
95
- mode={mode === "PIP" ? "PIP" : "FULLSCREEN"}
96
- isModal={false}
97
- groupId={groupId}
98
- />
99
- );
100
- }
101
-
102
- if (hookPlugin || screenType === "hooks") {
103
- return (
104
- screenData && (
105
- <HookRenderer
106
- callback={callbackAction}
107
- screenData={screenData}
108
- focused={props.focused}
109
- parentFocus={props.parentFocus as ParentFocus}
110
- />
111
- )
112
- );
113
- }
114
-
115
- const ScreenComponent =
116
- path(["module", "Component"], ScreenPlugin) ||
117
- prop("module", ScreenPlugin) ||
118
- prop("Component", ScreenPlugin) ||
119
- ScreenPlugin;
120
-
121
- const configuration =
122
- prop("configuration", ScreenPlugin) ||
123
- prop("__plugin_configuration", ScreenComponent);
39
+ useScreenAnalytics(props);
124
40
 
125
- if (ScreenComponent) {
126
- return (
127
- <ScreenComponent
128
- {...props}
129
- callback={callbackAction}
130
- screenId={screenId}
131
- screenData={screenData}
132
- configuration={configuration}
133
- presentationType={PresentationType.Standalone}
134
- />
135
- );
41
+ if (component) {
42
+ return component;
136
43
  }
137
44
 
138
45
  logger.warning({
@@ -143,17 +50,4 @@ export function ScreenResolverComponent(props: Props) {
143
50
  return null;
144
51
  }
145
52
 
146
- function withDefaultScreenContext(Component: React.ComponentType<any>) {
147
- return function WithDefaultScreenContext(props: any) {
148
- const screenId = props.screenId;
149
- const rivers = useRivers();
150
-
151
- return (
152
- <ZappPipesScreenContext.Provider initialContextValue={rivers[screenId]}>
153
- <Component {...props} />
154
- </ZappPipesScreenContext.Provider>
155
- );
156
- };
157
- }
158
-
159
53
  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
+ });
@@ -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";
@@ -10,7 +10,7 @@ export { ContentScreen } from "./ContentScreen";
10
10
 
11
11
  export { TextInputTv } from "./TextInputTv";
12
12
 
13
- export { HookRenderer } from "./HookRenderer";
13
+ export { HookRenderer } from "./HookRenderer/HookRenderer";
14
14
 
15
15
  export { Touchable } from "./Touchable";
16
16
 
@@ -2,6 +2,32 @@ import { render } from "@testing-library/react-native";
2
2
  import { ScreenContext, ScreenContextProvider, withScreenContext } from "../";
3
3
  import React from "react";
4
4
 
5
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
6
+ useCurrentScreenData: jest.fn(() => ({})),
7
+ useNavigation: jest.fn(() => ({
8
+ data: {},
9
+ modalData: null,
10
+ videoModalState: {},
11
+ canGoBack: jest.fn(() => false),
12
+ })),
13
+ useRoute: jest.fn(() => ({ screenData: null })),
14
+ isNavBarVisible: jest.fn(() => true),
15
+ }));
16
+
17
+ jest.mock(
18
+ "@applicaster/zapp-react-native-ui-components/Contexts/ModalNavigationContext",
19
+ () => ({
20
+ useModalNavigationContext: jest.fn(() => false),
21
+ })
22
+ );
23
+
24
+ jest.mock(
25
+ "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext",
26
+ () => ({
27
+ useNestedNavigationContext: jest.fn(() => false),
28
+ })
29
+ );
30
+
5
31
  describe("ScreenContext", () => {
6
32
  describe("ScreneContext context", () => {
7
33
  it("should return the context", () => {
@@ -13,6 +39,37 @@ describe("ScreenContext", () => {
13
39
  it("should return the provider", () => {
14
40
  expect(ScreenContextProvider).toBeDefined();
15
41
  });
42
+
43
+ it("recreates _feedStore when pathname changes", () => {
44
+ const contexts = [];
45
+
46
+ const CaptureContext = () => {
47
+ const context = React.useContext(ScreenContext);
48
+ contexts.push(context);
49
+
50
+ return null;
51
+ };
52
+
53
+ const { rerender } = render(
54
+ <ScreenContextProvider pathname="/screen-a">
55
+ <CaptureContext />
56
+ </ScreenContextProvider>
57
+ );
58
+
59
+ const first = contexts[contexts.length - 1];
60
+ first._feedStore.setState({ screenFeed: "screen-a" });
61
+
62
+ rerender(
63
+ <ScreenContextProvider pathname="/screen-b">
64
+ <CaptureContext />
65
+ </ScreenContextProvider>
66
+ );
67
+
68
+ const second = contexts[contexts.length - 1];
69
+
70
+ expect(second._feedStore).not.toBe(first._feedStore);
71
+ expect(second._feedStore.getState()).toEqual({});
72
+ });
16
73
  });
17
74
 
18
75
  describe("withScreenContext", () => {
@@ -91,9 +91,13 @@ const createStore = () =>
91
91
  const createScreenComponentsStore = () =>
92
92
  create(subscribeWithSelector<Record<string, unknown>>((_) => ({})));
93
93
 
94
+ const createFeedStore = () =>
95
+ create(subscribeWithSelector<Record<string, unknown>>((_) => ({})));
96
+
94
97
  type ScreenContextType = {
95
98
  _navBarStore: ReturnType<typeof createStore>;
96
99
  _stateStore: ReturnType<typeof createStateStore>;
100
+ _feedStore: ReturnType<typeof createFeedStore>;
97
101
  navBar: NavBarState;
98
102
  legacyFormatScreenData: LegacyNavigationScreenData | null;
99
103
  /**
@@ -116,6 +120,7 @@ type ScreenContextType = {
116
120
  export const ScreenContext = createContext<ScreenContextType>({
117
121
  _stateStore: createStateStore(),
118
122
  _navBarStore: createStore(),
123
+ _feedStore: createFeedStore(),
119
124
  navBar: {
120
125
  visible: true,
121
126
  title: "",
@@ -157,6 +162,10 @@ export function ScreenContextProvider({
157
162
  null
158
163
  );
159
164
 
165
+ const screenFeedStoreRef = useRef<null | ReturnType<typeof createFeedStore>>(
166
+ null
167
+ );
168
+
160
169
  const getScreenState = useCallback(() => {
161
170
  if (screenStateRef.current !== null) {
162
171
  return screenStateRef.current;
@@ -179,6 +188,13 @@ export function ScreenContextProvider({
179
188
  return navBarState;
180
189
  }, []);
181
190
 
191
+ // Assign feed store to ref to persist it across re-renders, but recreate on pathname change
192
+ const screenFeedStore = useMemo(() => createFeedStore(), [pathname]);
193
+
194
+ if (screenFeedStoreRef.current !== screenFeedStore) {
195
+ screenFeedStoreRef.current = screenFeedStore;
196
+ }
197
+
182
198
  // Component state store - recreated when pathname changes (route change).
183
199
  // Unlike _navBarStore and _stateStore (cached via refs), this store
184
200
  // resets only when pathname changes to provide a clean state for the new route.
@@ -237,6 +253,7 @@ export function ScreenContextProvider({
237
253
  () => ({
238
254
  _navBarStore: getScreenNavBarState(),
239
255
  _stateStore: getScreenState(),
256
+ _feedStore: screenFeedStoreRef.current,
240
257
  navBar: navBarState,
241
258
  legacyFormatScreenData: routeScreenData,
242
259
  _componentStateStore: componentStateStore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "15.0.0-rc.113",
3
+ "version": "15.0.0-rc.115",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "15.0.0-rc.113",
32
- "@applicaster/zapp-react-native-bridge": "15.0.0-rc.113",
33
- "@applicaster/zapp-react-native-redux": "15.0.0-rc.113",
34
- "@applicaster/zapp-react-native-utils": "15.0.0-rc.113",
31
+ "@applicaster/applicaster-types": "15.0.0-rc.115",
32
+ "@applicaster/zapp-react-native-bridge": "15.0.0-rc.115",
33
+ "@applicaster/zapp-react-native-redux": "15.0.0-rc.115",
34
+ "@applicaster/zapp-react-native-utils": "15.0.0-rc.115",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "url": "^0.11.0",
@@ -1,57 +0,0 @@
1
- import * as React from "react";
2
- import * as R from "ramda";
3
- import { Text, TextStyle, View, ViewStyle } from "react-native";
4
-
5
- import { getLocalizations } from "@applicaster/zapp-react-native-utils/localizationUtils";
6
- import { getAppStylesColor } from "@applicaster/zapp-react-native-utils/stylesUtils";
7
- import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
8
- import { styleKeys } from "@applicaster/zapp-react-native-utils/styleKeysUtils";
9
-
10
- type Props = {
11
- styles: {};
12
- error: {};
13
- remoteConfigurations: { localizations: {} };
14
- };
15
-
16
- const defaultAppStyles = {
17
- loading_error_label: {
18
- color: "#aaa",
19
- },
20
- };
21
-
22
- const textStyles = (appStyles = defaultAppStyles): TextStyle => ({
23
- color: getAppStylesColor("loading_error_label", appStyles),
24
- fontSize: 36,
25
- textAlign: "center",
26
- });
27
-
28
- const errorStyles = ({ backgroundColor }): ViewStyle => ({
29
- flex: 1,
30
- width: "100%",
31
- height: "100%",
32
- justifyContent: "center",
33
- alignItems: "center",
34
- position: "absolute",
35
- zIndex: 100,
36
- backgroundColor,
37
- });
38
-
39
- export function ErrorDisplayComponent({
40
- styles,
41
- remoteConfigurations: { localizations },
42
- }: Props) {
43
- const theme = useTheme();
44
- const backgroundColor = theme?.app_background_color;
45
-
46
- const { stream_error_message = "Cannot play stream" } = getLocalizations({
47
- localizations,
48
- });
49
-
50
- const appStyles = R.prop(styleKeys.style_namespace, styles);
51
-
52
- return (
53
- <View style={errorStyles({ backgroundColor })}>
54
- <Text style={textStyles(appStyles)}>{stream_error_message}</Text>
55
- </View>
56
- );
57
- }
@@ -1,9 +0,0 @@
1
- import * as R from "ramda";
2
-
3
- import { connectToStore } from "@applicaster/zapp-react-native-redux/utils/connectToStore";
4
-
5
- import { ErrorDisplayComponent } from "./ErrorDisplay";
6
-
7
- export const ErrorDisplay = R.compose(
8
- connectToStore(R.pick(["remoteConfigurations"]))
9
- )(ErrorDisplayComponent);
File without changes