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

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 (36) hide show
  1. package/Components/BaseFocusable/index.tsx +1 -1
  2. package/Components/Cell/Cell.tsx +5 -5
  3. package/Components/Cell/CellWithFocusable.tsx +5 -17
  4. package/Components/FocusableGroup/index.tsx +0 -1
  5. package/Components/HandlePlayable/HandlePlayable.tsx +0 -3
  6. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  7. package/Components/Layout/TV/TransitionOverlay.tsx +64 -0
  8. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +50 -10
  9. package/Components/Layout/TV/__tests__/index.test.tsx +74 -18
  10. package/Components/Layout/TV/index.tsx +105 -43
  11. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +80 -109
  12. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  13. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +1 -4
  14. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +1 -4
  15. package/Components/PlayerContainer/PlayerContainer.tsx +1 -3
  16. package/Components/River/ComponentsMap/ComponentsMap.tsx +6 -0
  17. package/Components/River/RefreshControl.tsx +2 -2
  18. package/Components/River/TV/River.tsx +23 -0
  19. package/Components/RouteManager/index.tsx +2 -2
  20. package/Components/ScreenResolver/index.tsx +1 -3
  21. package/Components/Transitioner/Scene.tsx +24 -19
  22. package/Components/Transitioner/Transitioner.tsx +9 -2
  23. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +6 -2
  24. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  25. package/Components/VideoLive/LiveImageManager.ts +3 -0
  26. package/Components/VideoModal/PlayerDetails.tsx +3 -3
  27. package/Components/VideoModal/PlayerWrapper.tsx +69 -62
  28. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +7 -0
  29. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  30. package/Contexts/ScreenDataContext/index.ts +3 -0
  31. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  32. package/package.json +5 -5
  33. package/Components/Screen/TV/hooks/index.ts +0 -3
  34. package/Components/Screen/TV/hooks/useInitialFocus.ts +0 -67
  35. package/Components/Screen/TV/hooks/useRestoreFocus.ts +0 -22
  36. package/Components/Screen/TV/index.web.tsx +0 -174
@@ -66,7 +66,7 @@ export class BaseFocusable<
66
66
  const { initialFocus } = this.props;
67
67
 
68
68
  if (initialFocus) {
69
- focusManager.setFocus(this.getId());
69
+ focusManager.setupInitialFocus(this.getId());
70
70
  }
71
71
  }
72
72
 
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { join, path, merge } from "ramda";
2
+ import * as R from "ramda";
3
3
  import { View } from "react-native";
4
4
 
5
5
  import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
@@ -105,8 +105,8 @@ export class CellComponent extends React.Component<Props> {
105
105
  onPress(item, index);
106
106
  } else {
107
107
  const { item, navigator, component, appData } = this.props;
108
- const itemTargetScreen = path(["screen_type"], item);
109
- const componentTargetScreen = path(["data", "target"], component);
108
+ const itemTargetScreen = R.path(["screen_type"], item);
109
+ const componentTargetScreen = R.path(["data", "target"], component);
110
110
  const itemType = getItemType(item, appData?.layoutVersion);
111
111
 
112
112
  sendSelectCellEvent(item, component, item.title, index);
@@ -116,7 +116,7 @@ export class CellComponent extends React.Component<Props> {
116
116
  componentTargetScreen &&
117
117
  !itemTargetScreen
118
118
  ) {
119
- navigator.push(merge(item, { screen_type: componentTargetScreen }));
119
+ navigator.push(R.merge(item, { screen_type: componentTargetScreen }));
120
120
 
121
121
  return;
122
122
  }
@@ -207,7 +207,7 @@ export class CellComponent extends React.Component<Props> {
207
207
 
208
208
  const { id } = item;
209
209
 
210
- const focusableId = join("-", [component?.id, id, index]);
210
+ const focusableId = R.join("-", [component?.id, id, index]);
211
211
 
212
212
  const handleFocus = (focusable, mouse) => {
213
213
  const focusFn = onFocus || noop;
@@ -6,20 +6,12 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
6
  import { isAppleTV } from "../../Helpers/Platform";
7
7
  import { useCellState } from "../MasterCell/utils";
8
8
 
9
- const useCellFocusedState = (
10
- skipFocusManagerRegistration: boolean,
11
- groupId: string,
12
- id: string
13
- ) => {
9
+ const useCellFocusedState = (groupId: string, id: string) => {
14
10
  const [currentCellFocused, setCurrentCellFocused] = React.useState(false);
15
11
 
16
12
  React.useEffect(() => {
17
- const isGroupItemFocused = () => {
18
- if (!skipFocusManagerRegistration) {
19
- const isFocused = focusManager.isGroupItemFocused(groupId, id);
20
- setCurrentCellFocused(isFocused);
21
- }
22
- };
13
+ const isGroupItemFocused = () =>
14
+ setCurrentCellFocused(focusManager.isGroupItemFocused(groupId, id));
23
15
 
24
16
  const handler = () => {
25
17
  // tvOS hack for properly checking focus
@@ -37,7 +29,7 @@ const useCellFocusedState = (
37
29
  return () => {
38
30
  focusManager.removeHandler(FOCUS_EVENTS.FOCUS, handler);
39
31
  };
40
- }, [groupId, skipFocusManagerRegistration]);
32
+ }, [groupId]);
41
33
 
42
34
  return currentCellFocused;
43
35
  };
@@ -73,11 +65,7 @@ export function CellWithFocusable(props: Props) {
73
65
  selected,
74
66
  } = props;
75
67
 
76
- const isFocused = useCellFocusedState(
77
- skipFocusManagerRegistration,
78
- groupId,
79
- id
80
- );
68
+ const isFocused = useCellFocusedState(groupId, id);
81
69
 
82
70
  const state = useCellState({
83
71
  id: item.id,
@@ -26,7 +26,6 @@ type Props = {
26
26
  nextFocusLeft?: any;
27
27
  nextFocusRight?: any;
28
28
  onLayout?: any;
29
- excludeFromFocusSearching?: boolean;
30
29
  };
31
30
 
32
31
  export class FocusableGroup extends BaseFocusable<Props> {
@@ -19,7 +19,6 @@ type Props = {
19
19
  modalHeight?: number;
20
20
  modalWidth?: number;
21
21
  mode: VideoModalMode;
22
- groupId?: string;
23
22
  };
24
23
 
25
24
  const YOUTUBE_PLUGIN_ID = "youtube-player-qb";
@@ -94,7 +93,6 @@ export function HandlePlayable({
94
93
  modalHeight,
95
94
  modalWidth,
96
95
  mode,
97
- groupId,
98
96
  }: Props): React.ReactElement | null {
99
97
  const state = usePickFromState();
100
98
 
@@ -169,7 +167,6 @@ export function HandlePlayable({
169
167
  isModal={isModal}
170
168
  mode={mode}
171
169
  disableCastAction={playerModuleProperties?.disableCastAction}
172
- groupId={groupId}
173
170
  />
174
171
  );
175
172
  }
@@ -60,7 +60,7 @@ export function ScreenLayoutContextProvider(props: {
60
60
  initialState
61
61
  );
62
62
 
63
- const screenLayoutValue = React.useMemo(
63
+ const screeenLayoutValue = React.useMemo(
64
64
  () => ({
65
65
  ...screenLayout,
66
66
  setScreenLayout,
@@ -69,7 +69,7 @@ export function ScreenLayoutContextProvider(props: {
69
69
  );
70
70
 
71
71
  return (
72
- <ScreenLayoutContext.Provider value={screenLayoutValue}>
72
+ <ScreenLayoutContext.Provider value={screeenLayoutValue}>
73
73
  {props.children}
74
74
  </ScreenLayoutContext.Provider>
75
75
  );
@@ -0,0 +1,64 @@
1
+ import * as React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
4
+ import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
5
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
6
+ import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
7
+ import { Spinner } from "@applicaster/zapp-react-native-ui-components/Components/Spinner";
8
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
9
+
10
+ import { useScreenConfiguration } from "../../River/useScreenConfiguration";
11
+
12
+ const styles = StyleSheet.create({
13
+ container: {
14
+ position: "absolute",
15
+ top: 0,
16
+ left: 0,
17
+ right: 0,
18
+ bottom: 0,
19
+
20
+ alignItems: "center",
21
+ justifyContent: "center",
22
+ },
23
+ });
24
+
25
+ export const TransitionOverlay = () => {
26
+ const screen = useCurrentScreenData();
27
+
28
+ const { backgroundColor: screenBackgroundColor } = useScreenConfiguration(
29
+ screen?.id
30
+ );
31
+
32
+ const theme = useTheme();
33
+
34
+ const themeBackgroundColor = React.useMemo(
35
+ () => theme.app_background_color,
36
+ [theme.app_background_color]
37
+ );
38
+
39
+ const backgroundColor = ifEmptyUseFallback(
40
+ screenBackgroundColor,
41
+ themeBackgroundColor
42
+ );
43
+
44
+ const {
45
+ appState: { isTransitioning },
46
+ } = usePickFromState(["appState"]);
47
+
48
+ if (!isWeb() || !isTransitioning) {
49
+ return null;
50
+ }
51
+
52
+ return (
53
+ <View
54
+ style={[
55
+ styles.container,
56
+ {
57
+ backgroundColor,
58
+ },
59
+ ]}
60
+ >
61
+ <Spinner size={"large"} />
62
+ </View>
63
+ );
64
+ };
@@ -2,18 +2,58 @@
2
2
 
3
3
  exports[`Layout TV renders 1`] = `
4
4
  <View
5
- excludeFromFocusSearching={true}
6
- id="/river/A1234"
7
- preferredFocus={true}
5
+ style={
6
+ Object {
7
+ "flex": 1,
8
+ "height": 1080,
9
+ "width": 1920,
10
+ }
11
+ }
12
+ testID="layout-container"
8
13
  >
9
14
  <View
10
- Components={
11
- Object {
12
- "Background": [Function],
13
- "NavBar": [Function],
15
+ backgroundImageUrl="http://applicaster.com"
16
+ testID="background-component"
17
+ >
18
+ <View
19
+ style={
20
+ Array [
21
+ Object {
22
+ "position": "absolute",
23
+ "top": 0,
24
+ "width": 1920,
25
+ "zIndex": 10,
26
+ },
27
+ Object {
28
+ "maxHeight": 10,
29
+ },
30
+ ]
14
31
  }
15
- }
16
- route="/river/A1234"
17
- />
32
+ testID="nav-bar-container"
33
+ >
34
+ <View
35
+ isVisible={true}
36
+ menuPlugin={
37
+ Object {
38
+ "module": [Function],
39
+ "name": "resolved-menu-plug",
40
+ "type": "menu",
41
+ }
42
+ }
43
+ navigationProps={
44
+ Object {
45
+ "assets": undefined,
46
+ "home": undefined,
47
+ "nav_items": undefined,
48
+ "rules": undefined,
49
+ "selected": undefined,
50
+ "styles": undefined,
51
+ "title": "",
52
+ }
53
+ }
54
+ testID="nav-bar-component"
55
+ />
56
+ </View>
57
+ </View>
18
58
  </View>
19
59
  `;
@@ -1,48 +1,79 @@
1
1
  import * as React from "react";
2
- import { omit } from "ramda";
2
+ import * as R from "ramda";
3
3
  import { View } from "react-native";
4
4
  import { render } from "@testing-library/react-native";
5
5
  import { Provider } from "react-redux";
6
+ import { createStore } from "redux";
6
7
  import { NavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NavigationContext";
7
- import configureStore from "redux-mock-store";
8
8
 
9
- const withoutChildren = omit(["children"]);
9
+ import { ThemeContext } from "@applicaster/zapp-react-native-utils/theme";
10
10
 
11
- jest.mock("../../../Screen/TV/index.web", () => ({
12
- Screen: View,
11
+ jest.mock("@applicaster/quick-brick-core/App/ActionSetters", () => ({
12
+ useRiverInitialState: () => ({}),
13
13
  }));
14
14
 
15
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen");
16
+
15
17
  const Layout = require("..").default;
16
18
 
17
- const mockStore = configureStore()({
19
+ const ResolvedMenuPlug = () => <View testID="resolved-menu-plug" />;
20
+
21
+ const plugins = [
22
+ {
23
+ module: ResolvedMenuPlug,
24
+ type: "menu",
25
+ name: "resolved-menu-plug",
26
+ },
27
+ ];
28
+
29
+ const bgImage = "http://applicaster.com";
30
+ const isNavBarVisible = true;
31
+
32
+ const store = createStore(() => ({
18
33
  appState: { appReady: true },
19
- });
34
+ remoteConfigurations: { sdkAssets: { bgImage } },
35
+ plugins,
36
+ }));
20
37
 
21
38
  describe("Layout TV", () => {
39
+ const themes = {
40
+ myTheme: { screen_margin_top: 10, screen_margin_bottom: 10 },
41
+ };
42
+
43
+ const selectedThemeId = "myTheme";
44
+
22
45
  const navigator = {
46
+ activeRiver: {
47
+ navigations: [{ category: "menu", navigation_type: "menu" }],
48
+ },
49
+ canGoBack: () => false,
23
50
  currentRoute: "/river/A1234",
24
- mainStack: [
25
- {
26
- route: "/river/A1234",
27
- state: "state",
28
- },
29
- ],
51
+ screenData: {
52
+ navigations: [{ category: "menu", navigation_type: "menu" }],
53
+ },
54
+ isNavBarVisible: isNavBarVisible,
30
55
  };
31
56
 
32
57
  const wrapper = ({ children }) => (
33
- <Provider store={mockStore}>
58
+ <Provider store={store}>
34
59
  <NavigationContext.Provider value={navigator}>
35
- {children}
60
+ <ThemeContext.Provider value={{ themes, selectedThemeId }}>
61
+ {children}
62
+ </ThemeContext.Provider>
36
63
  </NavigationContext.Provider>
37
64
  </Provider>
38
65
  );
39
66
 
67
+ const Screen = (props) => (
68
+ <View testID="screen-component" {...R.omit(["children"])(props)} />
69
+ );
70
+
40
71
  const NavBar = (props) => (
41
- <View testID="nav-bar-component" {...withoutChildren(props)} />
72
+ <View testID="nav-bar-component" {...R.omit(["children"])(props)} />
42
73
  );
43
74
 
44
75
  const Background = (props: Record<any, any>) => (
45
- <View testID="background-component" {...withoutChildren(props)}>
76
+ <View testID="background-component" {...R.omit(["children"])(props)}>
46
77
  {props.children}
47
78
  </View>
48
79
  );
@@ -53,8 +84,33 @@ describe("Layout TV", () => {
53
84
  };
54
85
 
55
86
  it("renders", () => {
56
- const { toJSON } = render(<Layout Components={Components} />, { wrapper });
87
+ const { toJSON } = render(
88
+ <Layout Components={Components}>
89
+ <Screen />
90
+ </Layout>,
91
+ { wrapper }
92
+ );
57
93
 
58
94
  expect(toJSON()).toMatchSnapshot();
59
95
  });
96
+
97
+ describe("props passed to background component", () => {
98
+ it("passes correct props", () => {
99
+ const { getByTestId } = render(
100
+ <Layout
101
+ Components={Components}
102
+ ComponentsExtraProps={{
103
+ Background: { testProp: true },
104
+ }}
105
+ >
106
+ <Screen />
107
+ </Layout>,
108
+ { wrapper }
109
+ );
110
+
111
+ const bgComponent = getByTestId("background-component");
112
+ expect(bgComponent.props.backgroundImageUrl).toBe(bgImage);
113
+ expect(bgComponent.props.testProp).toBe(true);
114
+ });
115
+ });
60
116
  });
@@ -1,69 +1,131 @@
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 {
8
+ getNavigationProps,
9
+ resolveNavigationPlugin,
10
+ routeIsPlayerScreen,
11
+ } from "@applicaster/zapp-react-native-utils/navigationUtils";
12
+
13
+ import { getBackgroundImageUrl } from "../utils";
14
+ import { LayoutContainer } from "./LayoutContainer";
15
+ import { NavBarContainer } from "./NavBarContainer";
16
+ import { ScreenContainer } from "./ScreenContainer";
17
+ import { TransitionOverlay } from "./TransitionOverlay";
6
18
 
7
19
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
20
+ import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
8
21
  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);
22
+ import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
23
+ import { ScreenDataContext } from "../../../Contexts/ScreenDataContext";
13
24
 
14
25
  type Components = {
15
26
  NavBar: React.ComponentType<any>;
16
27
  Background: React.ComponentType<any>;
17
28
  };
18
29
 
30
+ type ComponentsExtraProps = {
31
+ Background?: Record<string, any>;
32
+ NavBar?: Record<string, any>;
33
+ };
34
+
19
35
  type Props = {
20
36
  Components: Components;
37
+ ComponentsExtraProps?: ComponentsExtraProps;
38
+ children: React.ReactNode;
21
39
  };
22
40
 
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 });
29
-
30
- if (isEmpty(pathAttributes)) {
31
- return null;
32
- }
33
-
34
- const id = `${route}`;
35
-
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
- };
41
+ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
42
+ const navigator = useNavigation();
43
+ const theme = useTheme();
44
+ const { title, visible } = useNavbarState();
45
+ const { activeRiver, currentRoute } = navigator;
46
+
47
+ const {
48
+ appState: { appReady = false } = {},
49
+ remoteConfigurations,
50
+ plugins = [],
51
+ } = usePickFromState(["appState", "remoteConfigurations", "plugins"]);
52
+
53
+ const navigationProps = getNavigationProps({
54
+ navigator,
55
+ title,
56
+ category: "menu",
57
+ });
58
+
59
+ const backgroundColor = React.useMemo(() => theme.app_background_color, []);
60
+
61
+ const backgroundImageUrl = React.useMemo(
62
+ () => getBackgroundImageUrl(remoteConfigurations),
63
+ [remoteConfigurations]
64
+ );
65
+
66
+ const menuPlugin = React.useMemo(
67
+ () =>
68
+ resolveNavigationPlugin({
69
+ category: "menu",
70
+ navigations: activeRiver?.navigations,
71
+ plugins,
72
+ }) as QuickBrickPlugin,
73
+ []
74
+ );
75
+
76
+ const NavBar = React.useMemo(
77
+ () => Components.NavBar || (menuPlugin.module as React.ComponentType<any>),
78
+ []
79
+ );
80
+
81
+ const [navBarReady, setNavBarReady] = React.useState(false);
82
+
83
+ const navBarContainer = (
84
+ <NavBarContainer isVisible={visible} onReady={setNavBarReady}>
85
+ <NavBar
86
+ ref={ComponentsExtraProps?.NavBar?.ref}
87
+ isVisible={visible && !routeIsPlayerScreen(currentRoute)}
88
+ menuPlugin={menuPlugin}
89
+ navigationProps={navigationProps}
90
+ {...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
91
+ />
92
+ </NavBarContainer>
93
+ );
54
94
 
55
- const Layout = ({ Components }: Props) => {
56
- const { appState } = usePickFromState(["appState"]);
57
- const appReady = pathOr(false, ["appReady"], appState);
95
+ const isTVOS = isApplePlatform();
58
96
 
59
97
  if (!appReady) {
60
98
  return null;
61
99
  }
62
100
 
63
101
  return (
64
- <ScreenLayoutContextProvider>
65
- <StackNavigator Components={Components} />
66
- </ScreenLayoutContextProvider>
102
+ <LayoutContainer>
103
+ <ScreenLayoutContextProvider>
104
+ <Components.Background
105
+ backgroundColor={backgroundColor}
106
+ backgroundImageUrl={backgroundImageUrl}
107
+ {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
108
+ >
109
+ <ScreenDataContext.Provider value={navigator.data}>
110
+ <PathnameContext.Provider value={navigator.currentRoute}>
111
+ {!isTVOS ? navBarContainer : null}
112
+ {navBarReady ? (
113
+ /**
114
+ Need to re-mount this component on each navigation event
115
+ It's a hack to work around the fact that we don't support
116
+ correct screen rendering in our navigator (shared currentRoute state)
117
+ **/
118
+ <ScreenContainer>{children}</ScreenContainer>
119
+ ) : null}
120
+ {/** Render NavBarContainer over ScreenContainer to let tvOS native
121
+ focus manager works correctly to focus nav bar items first **/}
122
+ {isTVOS ? navBarContainer : null}
123
+ </PathnameContext.Provider>
124
+ </ScreenDataContext.Provider>
125
+ <TransitionOverlay />
126
+ </Components.Background>
127
+ </ScreenLayoutContextProvider>
128
+ </LayoutContainer>
67
129
  );
68
130
  };
69
131