@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4201443043 → 12.0.0-alpha.4223432180

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 (93) hide show
  1. package/Components/BaseFocusable/index.tsx +2 -2
  2. package/Components/Cell/Cell.tsx +34 -6
  3. package/Components/Cell/CellWithFocusable.tsx +6 -4
  4. package/Components/Cell/TvOSCellComponent.tsx +3 -7
  5. package/Components/Focusable/Focusable.tsx +6 -1
  6. package/Components/Focusable/__tests__/index.android.test.tsx +72 -0
  7. package/Components/Focusable/index.android.tsx +15 -9
  8. package/Components/FocusableGroup/index.tsx +10 -2
  9. package/Components/FocusableList/FocusableChild.ts +2 -4
  10. package/Components/FocusableList/index.tsx +11 -12
  11. package/Components/FocusableScrollView/index.tsx +1 -1
  12. package/Components/FreezeWithCallback/Freeze.tsx +31 -0
  13. package/Components/FreezeWithCallback/LICENSE +21 -0
  14. package/Components/FreezeWithCallback/Suspender.tsx +17 -0
  15. package/Components/FreezeWithCallback/__tests__/index.test.tsx +170 -0
  16. package/Components/FreezeWithCallback/index.tsx +3 -0
  17. package/Components/Layout/TV/ScreenContainer.tsx +79 -5
  18. package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +38 -28
  19. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +25 -13
  20. package/Components/Layout/TV/__tests__/index.test.tsx +3 -2
  21. package/Components/Layout/TV/index.tsx +52 -9
  22. package/Components/Layout/TV/index.web.tsx +33 -0
  23. package/Components/Layout/index.tsx +15 -11
  24. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -1
  25. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +86 -116
  26. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +16 -1
  27. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +6 -5
  28. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +2 -2
  29. package/Components/MasterCell/DefaultComponents/ImageBorderContainer/index.ts +71 -0
  30. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +20 -0
  31. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  32. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  33. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +6 -1
  34. package/Components/MasterCell/SharedUI/ProgressBar/hooks/useFillInPercent.ts +7 -1
  35. package/Components/MasterCell/dataAdapter.ts +21 -7
  36. package/Components/MasterCell/index.tsx +5 -3
  37. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  38. package/Components/MasterCell/utils/index.ts +17 -30
  39. package/Components/Navigator/StackNavigator.tsx +14 -5
  40. package/Components/PlayerContainer/PlayerContainer.tsx +16 -8
  41. package/Components/Screen/TV/hooks/index.ts +0 -2
  42. package/Components/Screen/TV/index.web.tsx +40 -41
  43. package/Components/Screen/__tests__/Screen.test.tsx +3 -49
  44. package/Components/Screen/index.tsx +55 -76
  45. package/Components/Screen/navigationHandler.ts +6 -3
  46. package/Components/Screen/orientationHandler.ts +18 -7
  47. package/Components/Screen/utils.ts +16 -0
  48. package/Components/ScreenResolver/index.tsx +6 -11
  49. package/Components/Transitioner/Scene.tsx +20 -12
  50. package/Components/Transitioner/Transitioner.tsx +52 -51
  51. package/Components/Transitioner/__tests__/Scene.test.js +7 -0
  52. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +8 -4
  53. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  54. package/Components/VideoLive/LiveImageManager.ts +10 -0
  55. package/Components/VideoLive/PlayerLiveImageComponent.tsx +52 -24
  56. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +31 -24
  57. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +2 -14
  58. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +81 -46
  59. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +47 -33
  60. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +129 -86
  61. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +32 -4
  62. package/Components/VideoModal/ModalAnimation/utils.ts +7 -4
  63. package/Components/VideoModal/PlayerDetails.tsx +19 -5
  64. package/Components/VideoModal/PlayerWrapper.tsx +49 -19
  65. package/Components/VideoModal/VideoModal.tsx +31 -25
  66. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +7 -0
  67. package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +2 -0
  68. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +134 -22
  69. package/Components/VideoModal/hooks/useModalSize.ts +10 -12
  70. package/Components/VideoModal/utils.ts +5 -2
  71. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +7 -3
  72. package/Components/ZappFrameworkComponents/BarView/Buttons/BackButton.tsx +7 -2
  73. package/Components/ZappFrameworkComponents/BarView/Buttons/CloseButton.tsx +7 -2
  74. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +87 -0
  75. package/Components/ZappFrameworkComponents/Buttons/ActionButton.tsx +9 -4
  76. package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +4 -4
  77. package/Contexts/CachedDimensionsContext/index.ts +13 -9
  78. package/Contexts/ConfirmDialogState/index.ts +1 -1
  79. package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
  80. package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
  81. package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
  82. package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
  83. package/Contexts/FocusableGroupContext/index.ts +7 -0
  84. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
  85. package/Contexts/ScreenContext/index.tsx +6 -1
  86. package/Contexts/ScreenDataContext/index.tsx +20 -0
  87. package/const.ts +6 -0
  88. package/index.js +3 -0
  89. package/package.json +5 -5
  90. package/Components/Screen/TV/hooks/useRestoreFocus.ts +0 -22
  91. package/Contexts/ScreenDataContext/index.ts +0 -3
  92. /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
  93. /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
@@ -0,0 +1,170 @@
1
+ import React, { Dispatch, useEffect, useState } from "react";
2
+ import {
3
+ act,
4
+ create,
5
+ ReactTestRenderer,
6
+ ReactTestRendererJSON,
7
+ } from "react-test-renderer";
8
+ import { FreezeWithCallback } from "../index";
9
+
10
+ const SimpleContent = () => <div />;
11
+
12
+ interface InnerProps {
13
+ value: number;
14
+ }
15
+ // eslint-disable-next-line unused-imports/no-unused-vars
16
+ const Inner = ({ value }: InnerProps) => <div />;
17
+
18
+ interface TestSubscriber {
19
+ renderCount: number;
20
+ subscription: Dispatch<number>;
21
+ }
22
+
23
+ function createSubscriberComponent(): [React.FC, TestSubscriber] {
24
+ const testState: TestSubscriber = {
25
+ renderCount: 0,
26
+ subscription: () => {},
27
+ };
28
+
29
+ const Subscriber: React.FC = () => {
30
+ const [value, setValue] = useState(0);
31
+
32
+ useEffect(() => {
33
+ testState.subscription = setValue;
34
+ }, []);
35
+
36
+ testState.renderCount += 1;
37
+
38
+ return <Inner value={value} />;
39
+ };
40
+
41
+ return [Subscriber, testState];
42
+ }
43
+
44
+ interface ContainerProps {
45
+ freeze: boolean;
46
+ children: React.ReactNode;
47
+ }
48
+
49
+ const Container = ({ freeze, children }: ContainerProps) => (
50
+ <FreezeWithCallback freeze={freeze}>{children}</FreezeWithCallback>
51
+ );
52
+
53
+ function setupTest(initialFreeze: boolean = false) {
54
+ let testRenderer: ReactTestRenderer | undefined;
55
+ const [Subscriber, subscriberState] = createSubscriberComponent();
56
+
57
+ act(() => {
58
+ testRenderer = create(
59
+ <Container freeze={initialFreeze}>
60
+ <Subscriber />
61
+ </Container>
62
+ );
63
+ });
64
+
65
+ return {
66
+ testRenderer,
67
+ testInstance: testRenderer?.root,
68
+ subscriberState,
69
+ updateFreeze: (freeze: boolean) => {
70
+ act(() =>
71
+ testRenderer?.update(
72
+ <Container freeze={freeze}>
73
+ <Subscriber />
74
+ </Container>
75
+ )
76
+ );
77
+ },
78
+ };
79
+ }
80
+
81
+ describe("FreezeWithCallback", () => {
82
+ test("Renders stuff not frozen", () => {
83
+ const testRenderer = create(
84
+ <Container freeze={false}>
85
+ <SimpleContent />
86
+ </Container>
87
+ );
88
+
89
+ expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
90
+ });
91
+
92
+ test("Does not render stuff when frozen", () => {
93
+ const testRenderer = create(
94
+ <Container freeze>
95
+ <SimpleContent />
96
+ </Container>
97
+ );
98
+
99
+ expect(testRenderer.root.findAllByType(SimpleContent)).toHaveLength(0);
100
+ });
101
+
102
+ test("Stuff is gone after freeze", () => {
103
+ const testRenderer = create(
104
+ <Container freeze={false}>
105
+ <SimpleContent />
106
+ </Container>
107
+ );
108
+
109
+ expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
110
+
111
+ act(() =>
112
+ testRenderer.update(
113
+ <Container freeze>
114
+ <SimpleContent />
115
+ </Container>
116
+ )
117
+ );
118
+
119
+ expect(testRenderer.toJSON()).toBe(null);
120
+ });
121
+
122
+ test("Updates work when not frozen", () => {
123
+ const { testInstance, subscriberState } = setupTest();
124
+
125
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
126
+ act(() => subscriberState.subscription(1));
127
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
128
+ expect(subscriberState.renderCount).toBe(2);
129
+ });
130
+
131
+ test("Updates does not propagate when frozen", () => {
132
+ const { testInstance, subscriberState, updateFreeze } = setupTest();
133
+
134
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
135
+ updateFreeze(true);
136
+ act(() => subscriberState.subscription(1));
137
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
138
+ expect(subscriberState.renderCount).toBe(1);
139
+ });
140
+
141
+ test("State persists after defrost", () => {
142
+ const { testInstance, subscriberState, updateFreeze, testRenderer } =
143
+ setupTest();
144
+
145
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
146
+ act(() => subscriberState.subscription(1));
147
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
148
+
149
+ updateFreeze(true);
150
+ expect(testRenderer?.toJSON()).toBe(null);
151
+
152
+ updateFreeze(false);
153
+ expect((testRenderer?.toJSON() as ReactTestRendererJSON).type).toBe("div");
154
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
155
+ });
156
+
157
+ test("Update propagate after defrost", () => {
158
+ const { testInstance, subscriberState, updateFreeze } = setupTest();
159
+
160
+ updateFreeze(true);
161
+ act(() => subscriberState.subscription(1));
162
+ act(() => subscriberState.subscription(2));
163
+ act(() => subscriberState.subscription(3));
164
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
165
+
166
+ updateFreeze(false);
167
+ expect(testInstance?.findByType(Inner).props.value).toEqual(3);
168
+ expect(subscriberState.renderCount).toBe(2);
169
+ });
170
+ });
@@ -0,0 +1,3 @@
1
+ import { Freeze } from "./Freeze";
2
+
3
+ export const FreezeWithCallback = Freeze;
@@ -5,13 +5,30 @@ import * as R from "ramda";
5
5
  import { ScreenLayoutContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenLayoutContext";
6
6
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
7
7
  import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
8
- import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
8
+ import {
9
+ useCurrentScreenData,
10
+ useNavbarState,
11
+ } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
9
12
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
10
13
 
11
14
  import { useScreenConfiguration } from "../../River/useScreenConfiguration";
12
- import { routeIsPlayerScreen } from "@applicaster/zapp-react-native-utils/navigationUtils";
15
+ import {
16
+ findMenuPlugin,
17
+ getNavigationProps,
18
+ routeIsPlayerScreen,
19
+ } from "@applicaster/zapp-react-native-utils/navigationUtils";
20
+ import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
21
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
22
+ import { NavBarContainer } from "./NavBarContainer";
23
+
24
+ type ComponentsExtraProps = {
25
+ Background?: Record<string, any>;
26
+ NavBar?: Record<string, any>;
27
+ };
13
28
 
14
29
  type Props = {
30
+ ComponentsExtraProps?: ComponentsExtraProps;
31
+ NavBar: React.ComponentType<any>;
15
32
  children: React.ReactNode;
16
33
  };
17
34
 
@@ -30,9 +47,13 @@ const styles = StyleSheet.create({
30
47
  container: { flex: 1, height: "100%", width: "100%" },
31
48
  });
32
49
 
50
+ const isTVOS = isApplePlatform();
51
+
33
52
  // eslint-disable-next-line react/display-name
34
53
  export const ScreenContainer = React.memo(function ScreenContainer({
35
54
  children,
55
+ ComponentsExtraProps,
56
+ NavBar,
36
57
  }: Props) {
37
58
  const { screenMarginBottom } = React.useContext(ScreenLayoutContext);
38
59
  const { currentRoute, screenData } = useNavigation();
@@ -80,9 +101,62 @@ export const ScreenContainer = React.memo(function ScreenContainer({
80
101
  [themeStyles]
81
102
  );
82
103
 
104
+ const navigator = useNavigation();
105
+ const { activeRiver } = navigator;
106
+ const { title, visible } = useNavbarState();
107
+
108
+ const { plugins = [] } = usePickFromState([
109
+ "appState",
110
+ "remoteConfigurations",
111
+ "plugins",
112
+ ]);
113
+
114
+ const navigationProps = getNavigationProps({
115
+ navigator,
116
+ title,
117
+ category: "menu",
118
+ });
119
+
120
+ const menuPlugin = React.useMemo(
121
+ () => findMenuPlugin(activeRiver?.navigations, plugins),
122
+ [activeRiver?.navigations]
123
+ );
124
+
125
+ const NavBarComponent = React.useMemo(
126
+ () => NavBar || (menuPlugin.module as React.ComponentType<any>),
127
+ []
128
+ );
129
+
130
+ const [navBarReady, setNavBarReady] = React.useState(false);
131
+
132
+ const navBarContainer = (
133
+ <NavBarContainer isVisible={visible} onReady={setNavBarReady}>
134
+ <NavBarComponent
135
+ ref={ComponentsExtraProps?.NavBar?.ref}
136
+ isVisible={visible}
137
+ menuPlugin={menuPlugin}
138
+ navigationProps={navigationProps}
139
+ {...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
140
+ />
141
+ </NavBarContainer>
142
+ );
143
+
83
144
  return (
84
- <View testID="screen-container" style={viewStyles}>
85
- {children}
86
- </View>
145
+ <>
146
+ {!isTVOS ? navBarContainer : null}
147
+ {navBarReady ? (
148
+ /**
149
+ Need to re-mount this component on each navigation event
150
+ It's a hack to work around the fact that we don't support
151
+ correct screen rendering in our navigator (shared currentRoute state)
152
+ **/
153
+ <View testID="screen-container" style={viewStyles}>
154
+ {children}
155
+ </View>
156
+ ) : null}
157
+ {/** Render NavBarContainer over ScreenContainer to let tvOS native
158
+ focus manager works correctly to focus nav bar items first **/}
159
+ {isTVOS ? navBarContainer : null}
160
+ </>
87
161
  );
88
162
  });
@@ -1,14 +1,27 @@
1
1
  import * as React from "react";
2
2
  import { View } from "react-native";
3
- import { render } from "@testing-library/react-native";
3
+ import { render, waitFor } from "@testing-library/react-native";
4
+ import { WrappedWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
4
5
 
5
6
  import { ScreenLayoutContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenLayoutContext";
6
7
 
7
8
  const navigator_mock = {
8
9
  currentRoute: "/river/A1234",
9
10
  screenData: {},
11
+ activeRiver: {
12
+ navigations: {},
13
+ id: "A1234",
14
+ home: false,
15
+ },
10
16
  };
11
17
 
18
+ jest.mock("@applicaster/zapp-react-native-utils/navigationUtils", () => ({
19
+ ...jest.requireActual<any>(
20
+ "@applicaster/zapp-react-native-utils/navigationUtils"
21
+ ),
22
+ findMenuPlugin: () => ({}),
23
+ }));
24
+
12
25
  jest.mock("@applicaster/quick-brick-core/App/ActionSetters", () => ({
13
26
  useRiverInitialState: () => ({}),
14
27
  }));
@@ -17,13 +30,13 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
17
30
  useNavigation: () => navigator_mock,
18
31
  }));
19
32
 
20
- jest.mock("../../../River/useScreenConfiguration.ts", () => ({
33
+ jest.mock("../../../River/useScreenConfiguration", () => ({
21
34
  useScreenConfiguration: () => ({}),
22
35
  }));
23
36
 
24
- jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
25
- useCurrentScreenData: jest.fn(() => ({})),
26
- }));
37
+ jest.mock(
38
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useCurrentScreenData"
39
+ );
27
40
 
28
41
  jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
29
42
  useTheme: () => ({}),
@@ -44,42 +57,39 @@ describe("ScreenContainer", () => {
44
57
  const setScreenLayout = () => {};
45
58
 
46
59
  const wrapper = ({ children }) => (
47
- <ScreenLayoutContext.Provider value={{ ...screenLayout, setScreenLayout }}>
48
- {children}
49
- </ScreenLayoutContext.Provider>
60
+ <WrappedWithProviders>
61
+ <ScreenLayoutContext.Provider
62
+ value={{ ...screenLayout, setScreenLayout }}
63
+ >
64
+ <ScreenContainer NavBar={View}>{children}</ScreenContainer>
65
+ </ScreenLayoutContext.Provider>
66
+ </WrappedWithProviders>
50
67
  );
51
68
 
52
69
  const TestComp = () => <View testID="test-component" />;
53
70
 
54
71
  it("renders", () => {
55
- const { toJSON } = render(
56
- <ScreenContainer>
57
- <TestComp />
58
- </ScreenContainer>,
59
- { wrapper }
60
- );
72
+ const { toJSON } = render(<TestComp />, { wrapper });
61
73
 
62
74
  expect(toJSON()).toMatchSnapshot();
63
75
  });
64
76
 
65
- it("renders passed component", () => {
66
- const { getByTestId } = render(
67
- <ScreenContainer>
68
- <TestComp />
69
- </ScreenContainer>,
70
- { wrapper }
71
- );
77
+ it("renders passed component", async () => {
78
+ const { getByTestId } = render(<TestComp />, { wrapper });
79
+
80
+ await waitFor(() => {
81
+ return expect(getByTestId("screen-container")).toBeDefined();
82
+ });
72
83
 
73
84
  expect(getByTestId("test-component")).toBeDefined();
74
85
  });
75
86
 
76
- it("sets margins provided by the theme", () => {
77
- const { getByTestId } = render(
78
- <ScreenContainer>
79
- <TestComp />
80
- </ScreenContainer>,
81
- { wrapper }
82
- );
87
+ it("sets margins provided by the theme", async () => {
88
+ const { getByTestId } = render(<TestComp />, { wrapper });
89
+
90
+ await waitFor(() => {
91
+ return expect(getByTestId("screen-container")).toBeDefined();
92
+ });
83
93
 
84
94
  const screen = getByTestId("screen-container");
85
95
  expect(screen.props.style).toMatchSnapshot();
@@ -1,28 +1,40 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`ScreenContainer renders 1`] = `
4
- <View
4
+ <RNCSafeAreaProvider
5
+ onInsetsChange={[Function]}
5
6
  style={
6
7
  Array [
7
8
  Object {
8
9
  "flex": 1,
9
- "height": "100%",
10
- "width": "100%",
11
- },
12
- Object {
13
- "backgroundColor": "transparent",
14
- "marginBottom": 10,
15
- "paddingBottom": 0,
16
- "paddingTop": 0,
17
10
  },
11
+ undefined,
18
12
  ]
19
13
  }
20
- testID="screen-container"
21
14
  >
22
15
  <View
23
- testID="test-component"
24
- />
25
- </View>
16
+ style={
17
+ Array [
18
+ Object {
19
+ "position": "absolute",
20
+ "top": 0,
21
+ "width": 1920,
22
+ "zIndex": 10,
23
+ },
24
+ Object {
25
+ "maxHeight": 10,
26
+ },
27
+ ]
28
+ }
29
+ testID="nav-bar-container"
30
+ >
31
+ <View
32
+ isVisible={true}
33
+ menuPlugin={Object {}}
34
+ navigationProps={null}
35
+ />
36
+ </View>
37
+ </RNCSafeAreaProvider>
26
38
  `;
27
39
 
28
40
  exports[`ScreenContainer sets margins provided by the theme 1`] = `
@@ -12,7 +12,7 @@ jest.mock("../../../Screen/TV/index.web", () => ({
12
12
  Screen: View,
13
13
  }));
14
14
 
15
- const Layout = require("..").default;
15
+ const Layout = require("../index.web").default;
16
16
 
17
17
  const mockStore = configureStore()({
18
18
  appState: { appReady: true },
@@ -21,10 +21,11 @@ const mockStore = configureStore()({
21
21
  describe("Layout TV", () => {
22
22
  const navigator = {
23
23
  currentRoute: "/river/A1234",
24
+ canGoBack: jest.fn(),
24
25
  mainStack: [
25
26
  {
26
27
  route: "/river/A1234",
27
- state: "state",
28
+ state: { screen: {} },
28
29
  },
29
30
  ],
30
31
  };
@@ -1,32 +1,75 @@
1
1
  import * as React from "react";
2
- import { pathOr } from "ramda";
3
-
2
+ import * as R from "ramda";
4
3
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
6
+
7
+ import { getBackgroundImageUrl } from "../utils";
8
+ import { LayoutContainer } from "./LayoutContainer";
9
+ import { ScreenContainer } from "./ScreenContainer";
5
10
 
6
11
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
7
- import { StackNavigator } from "../../Navigator";
12
+ import { PathnameContext } from "../../../Contexts/PathnameContext";
13
+ import { ScreenDataContext } from "../../../Contexts/ScreenDataContext";
14
+ import { ScreenContextProvider } from "../../../Contexts/ScreenContext";
8
15
 
9
16
  type Components = {
10
17
  NavBar: React.ComponentType<any>;
11
18
  Background: React.ComponentType<any>;
12
19
  };
13
20
 
21
+ type ComponentsExtraProps = {
22
+ Background?: Record<string, any>;
23
+ NavBar?: Record<string, any>;
24
+ };
25
+
14
26
  type Props = {
15
27
  Components: Components;
28
+ ComponentsExtraProps?: ComponentsExtraProps;
29
+ children: React.ReactNode;
16
30
  };
17
31
 
18
- const Layout = ({ Components }: Props) => {
19
- const { appState } = usePickFromState(["appState"]);
20
- const appReady = pathOr(false, ["appReady"], appState);
32
+ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
33
+ const navigator = useNavigation();
34
+ const theme = useTheme();
35
+
36
+ const { appState: { appReady = false } = {}, remoteConfigurations } =
37
+ usePickFromState(["appState", "remoteConfigurations", "plugins"]);
38
+
39
+ const backgroundColor = React.useMemo(() => theme.app_background_color, []);
40
+
41
+ const backgroundImageUrl = React.useMemo(
42
+ () => getBackgroundImageUrl(remoteConfigurations),
43
+ [remoteConfigurations]
44
+ );
21
45
 
22
46
  if (!appReady) {
23
47
  return null;
24
48
  }
25
49
 
26
50
  return (
27
- <ScreenLayoutContextProvider>
28
- <StackNavigator Components={Components} />
29
- </ScreenLayoutContextProvider>
51
+ <LayoutContainer>
52
+ <ScreenLayoutContextProvider>
53
+ <Components.Background
54
+ backgroundColor={backgroundColor}
55
+ backgroundImageUrl={backgroundImageUrl}
56
+ {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
57
+ >
58
+ <ScreenDataContext.Provider value={navigator.data}>
59
+ <PathnameContext.Provider value={navigator.currentRoute}>
60
+ <ScreenContextProvider pathname={navigator.currentRoute}>
61
+ <ScreenContainer
62
+ ComponentsExtraProps={ComponentsExtraProps}
63
+ NavBar={Components.NavBar}
64
+ >
65
+ {children}
66
+ </ScreenContainer>
67
+ </ScreenContextProvider>
68
+ </PathnameContext.Provider>
69
+ </ScreenDataContext.Provider>
70
+ </Components.Background>
71
+ </ScreenLayoutContextProvider>
72
+ </LayoutContainer>
30
73
  );
31
74
  };
32
75
 
@@ -0,0 +1,33 @@
1
+ import * as React from "react";
2
+ import { pathOr } from "ramda";
3
+
4
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
+
6
+ import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
7
+ import { StackNavigator } from "../../Navigator";
8
+
9
+ type Components = {
10
+ NavBar: React.ComponentType<any>;
11
+ Background: React.ComponentType<any>;
12
+ };
13
+
14
+ type Props = {
15
+ Components: Components;
16
+ };
17
+
18
+ const Layout = ({ Components }: Props) => {
19
+ const { appState } = usePickFromState(["appState"]);
20
+ const appReady = pathOr(false, ["appReady"], appState);
21
+
22
+ if (!appReady) {
23
+ return null;
24
+ }
25
+
26
+ return (
27
+ <ScreenLayoutContextProvider>
28
+ <StackNavigator Components={Components} />
29
+ </ScreenLayoutContextProvider>
30
+ );
31
+ };
32
+
33
+ export default Layout;
@@ -1,20 +1,24 @@
1
1
  import * as React from "react";
2
- import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
3
- import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
4
- import { ScreenContextProvider } from "../../Contexts/ScreenContext";
2
+ import {
3
+ isTV,
4
+ platformSelect,
5
+ } from "@applicaster/zapp-react-native-utils/reactUtils";
5
6
 
6
7
  import Mobile from "./Mobile";
7
- import TV from "./TV";
8
8
 
9
- export default function Layout(props) {
10
- const { currentRoute: pathname } = useNavigation();
9
+ import TVWeb from "./TV/index.web";
10
+ import TVDefault from "./TV/index";
11
+
12
+ const TV = platformSelect({
13
+ samsung_tv: TVWeb,
14
+ lg_tv: TVWeb,
15
+ web: TVWeb,
16
+ default: TVDefault,
17
+ });
11
18
 
19
+ export default function Layout(props) {
12
20
  if (isTV()) {
13
- return (
14
- <ScreenContextProvider pathname={pathname}>
15
- <TV {...props} />
16
- </ScreenContextProvider>
17
- );
21
+ return <TV {...props} />;
18
22
  } else {
19
23
  return <Mobile {...props} />;
20
24
  }
@@ -53,6 +53,7 @@ export function ActionButton(props: Props) {
53
53
  const { item, action, asset, flavour = "flavour_1", cellUUID } = props;
54
54
  const actionContext = useActions(action?.identifier);
55
55
 
56
+ // TODO: add subscription API for action availability
56
57
  const actionDisabled =
57
58
  typeof actionContext?.isActionAvailable === "function" &&
58
59
  !actionContext.isActionAvailable(item);
@@ -68,7 +69,7 @@ export function ActionButton(props: Props) {
68
69
  if (actionDisabled || !actionContext) return;
69
70
 
70
71
  setActionState(actionContext.initialEntryState(item));
71
- }, [actionDisabled, item?.id]);
72
+ }, [actionDisabled, item?.id, actionContext]);
72
73
 
73
74
  const onPress = useCallback(() => {
74
75
  actionContext.invokeAction(item, {