@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4337072244 → 12.0.0-alpha.4354608902

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 (63) hide show
  1. package/Components/BaseFocusable/index.tsx +2 -2
  2. package/Components/Cell/Cell.tsx +9 -8
  3. package/Components/Cell/CellWithFocusable.tsx +7 -5
  4. package/Components/Cell/TvOSCellComponent.tsx +6 -11
  5. package/Components/Focusable/Focusable.tsx +6 -1
  6. package/Components/FocusableGroup/index.tsx +10 -2
  7. package/Components/Layout/TV/ScreenContainer.tsx +79 -5
  8. package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +38 -28
  9. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +25 -13
  10. package/Components/Layout/TV/__tests__/index.test.tsx +3 -2
  11. package/Components/Layout/TV/index.tsx +47 -41
  12. package/Components/Layout/TV/index.web.tsx +33 -0
  13. package/Components/Layout/index.tsx +15 -11
  14. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +88 -112
  15. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  16. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
  17. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +20 -0
  18. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +6 -1
  19. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  20. package/Components/MasterCell/utils/index.ts +17 -30
  21. package/Components/NativeFocusables/{index.js → index.ts} +4 -2
  22. package/Components/Navigator/StackNavigator.tsx +57 -0
  23. package/Components/Navigator/index.ts +1 -0
  24. package/Components/PlayerContainer/PlayerContainer.tsx +2 -0
  25. package/Components/River/ComponentsMap/ComponentsMap.tsx +6 -0
  26. package/Components/River/RefreshControl.tsx +2 -2
  27. package/Components/RouteManager/index.tsx +2 -2
  28. package/Components/Screen/TV/hooks/useInitialFocus.ts +6 -3
  29. package/Components/Screen/TV/index.web.tsx +12 -21
  30. package/Components/Screen/__tests__/Screen.test.tsx +3 -0
  31. package/Components/Screen/index.tsx +55 -58
  32. package/Components/Screen/navigationHandler.ts +5 -0
  33. package/Components/Transitioner/Scene.tsx +32 -26
  34. package/Components/Transitioner/Transitioner.tsx +21 -28
  35. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +12 -4
  36. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  37. package/Components/VideoLive/LiveImageManager.ts +28 -1
  38. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +2 -14
  39. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +56 -43
  40. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +42 -32
  41. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +104 -86
  42. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +32 -4
  43. package/Components/VideoModal/ModalAnimation/utils.ts +7 -4
  44. package/Components/VideoModal/PlayerDetails.tsx +3 -3
  45. package/Components/VideoModal/PlayerWrapper.tsx +69 -62
  46. package/Components/VideoModal/VideoModal.tsx +4 -4
  47. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +7 -0
  48. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +32 -12
  49. package/Components/VideoModal/hooks/useModalSize.ts +10 -12
  50. package/Components/VideoModal/utils.ts +5 -2
  51. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  52. package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
  53. package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
  54. package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
  55. package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
  56. package/Contexts/FocusableGroupContext/index.ts +7 -0
  57. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
  58. package/Contexts/ScreenDataContext/index.tsx +20 -0
  59. package/Decorators/Analytics/{index.js → index.tsx} +11 -11
  60. package/README.md +1 -13
  61. package/package.json +5 -5
  62. /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
  63. /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
@@ -71,9 +71,9 @@ export class BaseFocusable<
71
71
  }
72
72
 
73
73
  getId(props?: { id: string; groupId: string }) {
74
- const { id, groupId = null } = props || this.props;
74
+ const { id } = props || this.props;
75
75
 
76
- return groupId ? `${groupId}-${id}` : id;
76
+ return id;
77
77
  }
78
78
 
79
79
  getRect() {
@@ -11,12 +11,10 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
11
11
 
12
12
  import { CellWithFocusable } from "./CellWithFocusable";
13
13
  import { BaseFocusable } from "../BaseFocusable";
14
+ import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
14
15
 
15
16
  type Props = {
16
- item: {
17
- id: string;
18
- title: string;
19
- };
17
+ item: ZappEntry;
20
18
  index: number;
21
19
  shouldScrollHorizontally: (arg1: [any]) => boolean | null | undefined;
22
20
  shouldScrollVertically: (...args: any) => boolean | null | undefined;
@@ -32,10 +30,9 @@ type Props = {
32
30
  id: string;
33
31
  };
34
32
  selected?: boolean;
35
- CellRenderer: React.ComponentType<{
36
- item: any;
37
- state: string;
38
- }> & { hasFocusableInside: (item: Record<string, string>) => boolean };
33
+ CellRenderer: React.FunctionComponent<any> & {
34
+ hasFocusableInside: (item: ZappEntry) => boolean;
35
+ };
39
36
  preferredFocus: boolean;
40
37
  navigator: {
41
38
  push: (arg1: Record<any, any>) => void;
@@ -139,6 +136,10 @@ export class CellComponent extends React.Component<Props> {
139
136
  shouldScrollVertically?.(mouse, focusable, id, title)
140
137
  ) {
141
138
  this.scrollVertically(focusable);
139
+
140
+ const accessibilityManager = AccessibilityManager.getInstance();
141
+ // Text from DSP should be localized already
142
+ accessibilityManager.readText({ text: title.toString() });
142
143
  }
143
144
  }
144
145
  }
@@ -3,6 +3,8 @@ import * as React from "react";
3
3
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
4
4
  import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
5
5
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
7
+
6
8
  import { isAppleTV } from "../../Helpers/Platform";
7
9
  import { useCellState } from "../MasterCell/utils";
8
10
 
@@ -44,7 +46,7 @@ const useCellFocusedState = (
44
46
 
45
47
  type Props = {
46
48
  item: ZappEntry;
47
- CellRenderer: React.ComponentType<{ item: Object; state: string }>;
49
+ CellRenderer: React.FunctionComponent<any>;
48
50
  id: string;
49
51
  groupId: string;
50
52
  onFocus: Function;
@@ -54,7 +56,7 @@ type Props = {
54
56
  skipFocusManagerRegistration?: boolean;
55
57
  isFocusable?: boolean;
56
58
  behavior: Behavior;
57
- selected?: boolean;
59
+ focused?: boolean;
58
60
  };
59
61
 
60
62
  export function CellWithFocusable(props: Props) {
@@ -70,7 +72,7 @@ export function CellWithFocusable(props: Props) {
70
72
  skipFocusManagerRegistration,
71
73
  isFocusable,
72
74
  behavior,
73
- selected,
75
+ focused,
74
76
  } = props;
75
77
 
76
78
  const isFocused = useCellFocusedState(
@@ -82,8 +84,7 @@ export function CellWithFocusable(props: Props) {
82
84
  const state = useCellState({
83
85
  id: item.id,
84
86
  behavior,
85
- focused: isFocused,
86
- selected,
87
+ focused: isFocused || toBooleanWithDefaultFalse(focused),
87
88
  });
88
89
 
89
90
  const [focusedButtonId, setFocusedButtonId] = React.useState(undefined);
@@ -114,6 +115,7 @@ export function CellWithFocusable(props: Props) {
114
115
  preferredFocus={preferredFocus}
115
116
  skipFocusManagerRegistration={skipFocusManagerRegistration}
116
117
  isFocusable={isFocusable}
118
+ focused={focused}
117
119
  />
118
120
  );
119
121
  }
@@ -36,10 +36,9 @@ type Props = {
36
36
  component_type: string;
37
37
  };
38
38
  selected: boolean;
39
- CellRenderer: React.ComponentType<{
40
- item: any;
41
- state: string;
42
- }> & { hasFocusableInside: (item: any) => boolean };
39
+ CellRenderer: React.FunctionComponent<any> & {
40
+ hasFocusableInside: (item: any) => boolean;
41
+ };
43
42
  preferredFocus: boolean;
44
43
  navigator: {
45
44
  push: (arg1: Record<any, any>) => void;
@@ -226,13 +225,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
226
225
 
227
226
  hasReceivedFocus() {}
228
227
 
229
- scrollTo(index) {
230
- // Workaround for HL autoscroll/autoloop feature, we scroll manually on tvOS.
231
- // Should be removed after refactoring autoscroll/autoloop feature
232
- if (this.props.component?.component_type !== "horizontal_list_qb") {
233
- this.props?.scrollToIndex?.(index);
234
- }
235
- }
228
+ scrollTo() {}
236
229
 
237
230
  render() {
238
231
  const {
@@ -279,7 +272,9 @@ export class TvOSCellComponent extends React.Component<Props, State> {
279
272
  index={index}
280
273
  scrollTo={this.scrollTo}
281
274
  preferredFocus={preferredFocus}
275
+ focused={this.props.focused}
282
276
  behavior={behavior}
277
+ isFocusable={isFocusable}
283
278
  />
284
279
  </View>
285
280
  );
@@ -4,6 +4,7 @@ import * as R from "ramda";
4
4
  import { BaseFocusable } from "../BaseFocusable";
5
5
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
6
6
  import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
7
+ import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
7
8
 
8
9
  type Props = {
9
10
  initialFocus?: boolean;
@@ -23,7 +24,7 @@ type Props = {
23
24
  style?: React.CSSProperties;
24
25
  };
25
26
 
26
- export class Focusable extends BaseFocusable<Props> {
27
+ class Focusable extends BaseFocusable<Props> {
27
28
  isGroup: boolean;
28
29
  mouse: boolean;
29
30
  longPressTimeout = null;
@@ -198,3 +199,7 @@ export class Focusable extends BaseFocusable<Props> {
198
199
  );
199
200
  }
200
201
  }
202
+
203
+ const FocusableWithFocusableContext = withFocusableContext(Focusable);
204
+
205
+ export { FocusableWithFocusableContext as Focusable };
@@ -1,6 +1,8 @@
1
1
  import * as React from "react";
2
2
 
3
3
  import { BaseFocusable } from "../BaseFocusable";
4
+ import { FocusableGroupContextProvider } from "../../Contexts/FocusableGroupContext";
5
+ import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
4
6
 
5
7
  type Props = {
6
8
  testID?: string;
@@ -29,7 +31,7 @@ type Props = {
29
31
  excludeFromFocusSearching?: boolean;
30
32
  };
31
33
 
32
- export class FocusableGroup extends BaseFocusable<Props> {
34
+ class FocusableGroup extends BaseFocusable<Props> {
33
35
  constructor(props) {
34
36
  super(props);
35
37
  this.isGroup = true;
@@ -82,8 +84,14 @@ export class FocusableGroup extends BaseFocusable<Props> {
82
84
  style={style}
83
85
  ref={this.ref}
84
86
  >
85
- {children}
87
+ <FocusableGroupContextProvider id={id}>
88
+ {children}
89
+ </FocusableGroupContextProvider>
86
90
  </div>
87
91
  );
88
92
  }
89
93
  }
94
+
95
+ const FocusableGroupWithFocusableContext = withFocusableContext(FocusableGroup);
96
+
97
+ export { FocusableGroupWithFocusableContext as FocusableGroup };
@@ -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,69 +1,75 @@
1
1
  import * as React from "react";
2
- import { isEmpty, pathOr } from "ramda";
2
+ import * as R from "ramda";
3
3
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
4
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
- import { getPathAttributes } from "@applicaster/zapp-react-native-utils/navigationUtils";
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";
6
10
 
7
11
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
8
12
  import { PathnameContext } from "../../../Contexts/PathnameContext";
9
- import { FocusableGroup } from "../../FocusableGroup";
10
- import { Screen } from "../../Screen/TV/index.web";
11
-
12
- export const ScreenDataContext = React.createContext(null);
13
+ import { ScreenDataContext } from "../../../Contexts/ScreenDataContext";
14
+ import { ScreenContextProvider } from "../../../Contexts/ScreenContext";
13
15
 
14
16
  type Components = {
15
17
  NavBar: React.ComponentType<any>;
16
18
  Background: React.ComponentType<any>;
17
19
  };
18
20
 
21
+ type ComponentsExtraProps = {
22
+ Background?: Record<string, any>;
23
+ NavBar?: Record<string, any>;
24
+ };
25
+
19
26
  type Props = {
20
27
  Components: Components;
28
+ ComponentsExtraProps?: ComponentsExtraProps;
29
+ children: React.ReactNode;
21
30
  };
22
31
 
23
- const StackNavigator = ({ Components }: Props) => {
24
- // @ts-ignore
25
- const { mainStack, currentRoute } = useNavigation();
26
-
27
- const renderScreen = ({ route, state }) => {
28
- const pathAttributes = getPathAttributes({ pathname: route });
32
+ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
33
+ const navigator = useNavigation();
34
+ const theme = useTheme();
29
35
 
30
- if (isEmpty(pathAttributes)) {
31
- return null;
32
- }
36
+ const { appState: { appReady = false } = {}, remoteConfigurations } =
37
+ usePickFromState(["appState", "remoteConfigurations", "plugins"]);
33
38
 
34
- const id = `${route}`;
39
+ const backgroundColor = React.useMemo(() => theme.app_background_color, []);
35
40
 
36
- return (
37
- <FocusableGroup
38
- id={id}
39
- preferredFocus={route === currentRoute}
40
- excludeFromFocusSearching
41
- key={id}
42
- >
43
- <ScreenDataContext.Provider value={state}>
44
- <PathnameContext.Provider value={route}>
45
- <Screen route={route} Components={Components} />
46
- </PathnameContext.Provider>
47
- </ScreenDataContext.Provider>
48
- </FocusableGroup>
49
- );
50
- };
51
-
52
- return mainStack.map(renderScreen);
53
- };
54
-
55
- const Layout = ({ Components }: Props) => {
56
- const { appState } = usePickFromState(["appState"]);
57
- const appReady = pathOr(false, ["appReady"], appState);
41
+ const backgroundImageUrl = React.useMemo(
42
+ () => getBackgroundImageUrl(remoteConfigurations),
43
+ [remoteConfigurations]
44
+ );
58
45
 
59
46
  if (!appReady) {
60
47
  return null;
61
48
  }
62
49
 
63
50
  return (
64
- <ScreenLayoutContextProvider>
65
- <StackNavigator Components={Components} />
66
- </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>
67
73
  );
68
74
  };
69
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;