@applicaster/zapp-react-native-ui-components 12.0.0-alpha.7594390460 → 12.0.0-alpha.7604714363

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.
@@ -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,6 +11,7 @@ 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
17
  item: {
@@ -139,6 +140,10 @@ export class CellComponent extends React.Component<Props> {
139
140
  shouldScrollVertically?.(mouse, focusable, id, title)
140
141
  ) {
141
142
  this.scrollVertically(focusable);
143
+
144
+ const accessibilityManager = AccessibilityManager.getInstance();
145
+ // Text from DSP should be localized already
146
+ accessibilityManager.readText({ text: title });
142
147
  }
143
148
  }
144
149
  }
@@ -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
 
@@ -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
  }
@@ -226,13 +226,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
226
226
 
227
227
  hasReceivedFocus() {}
228
228
 
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
- }
229
+ scrollTo() {}
236
230
 
237
231
  render() {
238
232
  const {
@@ -279,7 +273,9 @@ export class TvOSCellComponent extends React.Component<Props, State> {
279
273
  index={index}
280
274
  scrollTo={this.scrollTo}
281
275
  preferredFocus={preferredFocus}
276
+ focused={this.props.focused}
282
277
  behavior={behavior}
278
+ isFocusable={isFocusable}
283
279
  />
284
280
  </View>
285
281
  );
@@ -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`] = `
@@ -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
  };
@@ -4,22 +4,14 @@ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
4
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
5
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
6
6
 
7
- import {
8
- getNavigationProps,
9
- resolveNavigationPlugin,
10
- routeIsPlayerScreen,
11
- } from "@applicaster/zapp-react-native-utils/navigationUtils";
12
-
13
7
  import { getBackgroundImageUrl } from "../utils";
14
8
  import { LayoutContainer } from "./LayoutContainer";
15
- import { NavBarContainer } from "./NavBarContainer";
16
9
  import { ScreenContainer } from "./ScreenContainer";
17
10
 
18
11
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
19
- import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
20
12
  import { PathnameContext } from "../../../Contexts/PathnameContext";
21
- import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
22
13
  import { ScreenDataContext } from "../../../Contexts/ScreenDataContext";
14
+ import { ScreenContextProvider } from "../../../Contexts/ScreenContext";
23
15
 
24
16
  type Components = {
25
17
  NavBar: React.ComponentType<any>;
@@ -40,20 +32,9 @@ type Props = {
40
32
  const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
41
33
  const navigator = useNavigation();
42
34
  const theme = useTheme();
43
- const { title, visible } = useNavbarState();
44
- const { activeRiver, currentRoute } = navigator;
45
-
46
- const {
47
- appState: { appReady = false } = {},
48
- remoteConfigurations,
49
- plugins = [],
50
- } = usePickFromState(["appState", "remoteConfigurations", "plugins"]);
51
35
 
52
- const navigationProps = getNavigationProps({
53
- navigator,
54
- title,
55
- category: "menu",
56
- });
36
+ const { appState: { appReady = false } = {}, remoteConfigurations } =
37
+ usePickFromState(["appState", "remoteConfigurations", "plugins"]);
57
38
 
58
39
  const backgroundColor = React.useMemo(() => theme.app_background_color, []);
59
40
 
@@ -62,37 +43,6 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
62
43
  [remoteConfigurations]
63
44
  );
64
45
 
65
- const menuPlugin = React.useMemo(
66
- () =>
67
- resolveNavigationPlugin({
68
- category: "menu",
69
- navigations: activeRiver?.navigations,
70
- plugins,
71
- }) as QuickBrickPlugin,
72
- []
73
- );
74
-
75
- const NavBar = React.useMemo(
76
- () => Components.NavBar || (menuPlugin.module as React.ComponentType<any>),
77
- []
78
- );
79
-
80
- const [navBarReady, setNavBarReady] = React.useState(false);
81
-
82
- const navBarContainer = (
83
- <NavBarContainer isVisible={visible} onReady={setNavBarReady}>
84
- <NavBar
85
- ref={ComponentsExtraProps?.NavBar?.ref}
86
- isVisible={visible && !routeIsPlayerScreen(currentRoute)}
87
- menuPlugin={menuPlugin}
88
- navigationProps={navigationProps}
89
- {...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
90
- />
91
- </NavBarContainer>
92
- );
93
-
94
- const isTVOS = isApplePlatform();
95
-
96
46
  if (!appReady) {
97
47
  return null;
98
48
  }
@@ -107,18 +57,14 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
107
57
  >
108
58
  <ScreenDataContext.Provider value={navigator.data}>
109
59
  <PathnameContext.Provider value={navigator.currentRoute}>
110
- {!isTVOS ? navBarContainer : null}
111
- {navBarReady ? (
112
- /**
113
- Need to re-mount this component on each navigation event
114
- It's a hack to work around the fact that we don't support
115
- correct screen rendering in our navigator (shared currentRoute state)
116
- **/
117
- <ScreenContainer>{children}</ScreenContainer>
118
- ) : null}
119
- {/** Render NavBarContainer over ScreenContainer to let tvOS native
120
- focus manager works correctly to focus nav bar items first **/}
121
- {isTVOS ? navBarContainer : null}
60
+ <ScreenContextProvider pathname={navigator.currentRoute}>
61
+ <ScreenContainer
62
+ ComponentsExtraProps={ComponentsExtraProps}
63
+ NavBar={Components.NavBar}
64
+ >
65
+ {children}
66
+ </ScreenContainer>
67
+ </ScreenContextProvider>
122
68
  </PathnameContext.Provider>
123
69
  </ScreenDataContext.Provider>
124
70
  </Components.Background>
@@ -4,6 +4,7 @@ import { Focusable } from "@applicaster/zapp-react-native-ui-components/Componen
4
4
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
5
  import * as R from "ramda";
6
6
  import { getXray } from "@applicaster/zapp-react-native-utils/logger";
7
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
7
8
 
8
9
  const { Logger } = getXray();
9
10
 
@@ -35,7 +36,9 @@ export function FocusableView({ style, children, item, ...otherProps }: Props) {
35
36
 
36
37
  const focusableId = getFocusableId(prefixId, suffixId);
37
38
 
38
- const focused = focusableId === focusedButtonId;
39
+ const focused =
40
+ focusableId === focusedButtonId ||
41
+ toBooleanWithDefaultFalse(otherProps.focused);
39
42
 
40
43
  const additionalStyles = focused ? focusedStyles : normalStyles;
41
44
 
@@ -7,6 +7,10 @@ import type {
7
7
  ContainerProps,
8
8
  ContainerChildren,
9
9
  } from "./types";
10
+ import {
11
+ useIsRTL,
12
+ applyRTLStylesIfNeeded,
13
+ } from "@applicaster/zapp-react-native-utils/localizationUtils";
10
14
 
11
15
  const HorizontalSeparator = ({ width }: HorizontalSeparatorProps) => (
12
16
  <View style={{ width }} />
@@ -17,6 +21,7 @@ export function ButtonContainerView({
17
21
  children,
18
22
  ...otherProps
19
23
  }: ContainerProps) {
24
+ const isRTL = useIsRTL();
20
25
  const horizontalGutter = R.pathOr(0, ["horizontalGutter"], otherProps);
21
26
 
22
27
  const filteredChildren = useFilterChildren<ContainerChildren>(children);
@@ -26,7 +31,7 @@ export function ButtonContainerView({
26
31
  }
27
32
 
28
33
  return (
29
- <View style={style}>
34
+ <View style={applyRTLStylesIfNeeded(style, isRTL)}>
30
35
  {insertBetween(
31
36
  (index) => (
32
37
  <HorizontalSeparator
@@ -9,4 +9,13 @@ describe("insertBetween", () => {
9
9
 
10
10
  expect(result).toEqual([1, "|", 2, "|", 3]);
11
11
  });
12
+
13
+ it("insertBetween", () => {
14
+ const separator = (index) => `separator:${index}`;
15
+ const items = [1, 2, 3];
16
+
17
+ const result = insertBetween(separator, items);
18
+
19
+ expect(result).toEqual([1, "separator:0", 2, "separator:1", 3]);
20
+ });
12
21
  });
@@ -8,6 +8,8 @@ import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/
8
8
  import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
9
9
 
10
10
  import { Screen } from "../Screen/TV/index.web";
11
+ import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
12
+ import { ScreenContextProvider } from "../../Contexts/ScreenContext";
11
13
 
12
14
  type Components = {
13
15
  NavBar: React.ComponentType<any>;
@@ -19,25 +21,32 @@ type Props = {
19
21
  };
20
22
 
21
23
  export const StackNavigator = ({ Components }: Props) => {
22
- const { mainStack, currentRoute } = useNavigation();
24
+ const { mainStack } = useNavigation();
23
25
 
24
- const renderScreen = ({ route, state }) => {
26
+ const renderScreen = ({ route, state }, index) => {
25
27
  const pathAttributes = getPathAttributes({ pathname: route });
26
28
 
27
29
  if (isEmpty(pathAttributes)) {
28
30
  return null;
29
31
  }
30
32
 
33
+ // HACK: Adding index to route to avoid conflicts in the navigator.
34
+ // Startup hooks are all using PUSH navigation action to navigate, but their routes are always like root routes.
35
+ // This means some hooks can have the same routes, e.g., ["hooks/context-setter", "/hooks/login-plug", "hooks/context-setter"].
36
+ const routeId = `${route}--${index}`;
37
+
31
38
  return (
32
39
  <FocusableGroup
33
40
  id={route}
34
- preferredFocus={route === currentRoute}
41
+ preferredFocus={isLast(index, mainStack.length)}
35
42
  excludeFromFocusSearching
36
- key={route}
43
+ key={routeId}
37
44
  >
38
45
  <ScreenDataContext.Provider value={state}>
39
46
  <PathnameContext.Provider value={route}>
40
- <Screen route={route} Components={Components} />
47
+ <ScreenContextProvider pathname={route}>
48
+ <Screen route={route} Components={Components} />
49
+ </ScreenContextProvider>
41
50
  </PathnameContext.Provider>
42
51
  </ScreenDataContext.Provider>
43
52
  </FocusableGroup>
@@ -124,6 +124,7 @@ const nativeStyles = {
124
124
  playerScreen: {
125
125
  flex: 1,
126
126
  backgroundColor: "black",
127
+ overflow: "hidden",
127
128
  },
128
129
  playerWrapper: {
129
130
  flex: 1,
@@ -252,11 +253,7 @@ const PlayerContainerComponent = (props: Props) => {
252
253
  }
253
254
 
254
255
  showNavBar(true);
255
-
256
- // Hack to close player screen on mobile only (tv handles that in InteractionManager)
257
- if (!isTV()) {
258
- navigator.goBack();
259
- }
256
+ navigator.goBack();
260
257
  }, [isModal, navigator.goBack, showNavBar]);
261
258
 
262
259
  const playEntry = (entry) => navigator.replaceTop(entry, { mode });
@@ -1,20 +1,24 @@
1
1
  import * as React from "react";
2
- import { last, pathOr } from "ramda";
2
+ import { last } from "ramda";
3
3
  import { View, StyleSheet } from "react-native";
4
4
  import {
5
+ findMenuPlugin,
5
6
  getNavigationProps,
6
7
  getPathAttributes,
7
- resolveNavigationPlugin,
8
8
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
9
9
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
10
10
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
11
11
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
12
- import { useScreenBackgroundColor } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
12
+ import {
13
+ useNavbarState,
14
+ useScreenBackgroundColor,
15
+ } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
13
16
  import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
14
17
 
15
18
  import { NavBarContainer } from "../../Layout/TV/NavBarContainer";
16
19
  import { ScreenResolver } from "../../ScreenResolver";
17
20
  import { useInitialFocus, useRestoreFocus } from "./hooks";
21
+ import { isPlayerPlugin } from "@applicaster/zapp-react-native-utils/pluginUtils";
18
22
 
19
23
  const styles = StyleSheet.create({
20
24
  container: {
@@ -36,18 +40,6 @@ type Props = {
36
40
  Components: Components;
37
41
  };
38
42
 
39
- const getPluginType = pathOr("unknown_plugin_type", [
40
- "state",
41
- "screen",
42
- "plugin_type",
43
- ]);
44
-
45
- const isPlayerPlugin = (routeState): boolean =>
46
- getPluginType(routeState) === "player";
47
-
48
- const isLoginPlugin = (routeState): boolean =>
49
- getPluginType(routeState) === "login";
50
-
51
43
  const getActiveRiver = ({
52
44
  screenId,
53
45
  screenType,
@@ -139,19 +131,14 @@ export const Screen = ({ route, Components }: Props) => {
139
131
  fallbackNavigations: navigator.activeRiver.navigations,
140
132
  });
141
133
 
142
- const menuPlugin = resolveNavigationPlugin({
143
- category: "menu",
144
- navigations,
145
- plugins,
146
- }) as QuickBrickPlugin;
134
+ const menuPlugin = findMenuPlugin(navigations, plugins);
147
135
 
148
136
  const NavBar = (Components.NavBar ||
149
137
  menuPlugin.module) as React.ComponentType<any>;
150
138
 
151
139
  const isPlayer = isPlayerPlugin(routeState);
152
- const isLogin = isLoginPlugin(routeState);
153
140
 
154
- const isNavBarVisible = !isPlayer && !isLogin;
141
+ const { visible: isNavBarVisible } = useNavbarState();
155
142
 
156
143
  const screenData =
157
144
  screenType === "hooks" || isPlayer
@@ -11,10 +11,10 @@ import {
11
11
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
12
12
  import { getComponentModule } from "@applicaster/zapp-react-native-utils/pluginUtils";
13
13
  import {
14
- getNavigationPropsV2,
15
- resolveNavigationPlugin,
14
+ shouldNavBarDisplayMenu,
15
+ getNavBarProps,
16
+ getScreenId,
16
17
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
17
- import { useSafeAreaInsets } from "react-native-safe-area-context";
18
18
  import {
19
19
  useDimensions,
20
20
  useRoute,
@@ -25,20 +25,16 @@ import {
25
25
  } from "@applicaster/zapp-react-native-utils/reactHooks";
26
26
  import { getNavigationPluginModule } from "@applicaster/zapp-react-native-app/App/Layout/layoutHelpers";
27
27
 
28
- import { ScreenContextProvider } from "../../Contexts/ScreenContext";
29
28
  import { RouteManager } from "../RouteManager";
30
29
  import { useScreenConfiguration } from "../River/useScreenConfiguration";
31
30
  import { useScreenOrientationHandler } from "./orientationHandler";
32
31
 
33
- const getScreenId = (
34
- screenData: LegacyNavigationScreenData,
35
- fallbackRiver: ZappRiver
36
- ) => {
37
- return screenData?.targetScreen
38
- ? screenData?.targetScreen?.id ?? fallbackRiver.id
39
- : "screen_id" in screenData
40
- ? screenData.screen_id
41
- : fallbackRiver.id;
32
+ const screenStyles = {
33
+ flex: 1,
34
+ paddingTop: 0,
35
+ // paddingBottom: insets.bottom, // Should be applied directly by UI plugin
36
+ // paddingLeft: insets.left, // Should be applied directly by UI plugin
37
+ // paddingRight: insets.right, // Should be applied directly by UI plugin
42
38
  };
43
39
 
44
40
  function colorIsNotTransparent(color) {
@@ -49,9 +45,7 @@ function colorIsNotTransparent(color) {
49
45
  R.replace("rgba(", "")
50
46
  )(color);
51
47
 
52
- const alpha = Number(layers?.[3]);
53
-
54
- return alpha > 0;
48
+ return Number(layers?.[3]) > 0;
55
49
  }
56
50
 
57
51
  function isValidColor(string) {
@@ -71,7 +65,6 @@ export function Screen(_props: Props) {
71
65
  // Gets the data for the current screen configuration
72
66
  const currentScreenData = useCurrentScreenData();
73
67
  const { backgroundColor } = useScreenConfiguration(currentScreenData.id);
74
- const insets = useSafeAreaInsets();
75
68
 
76
69
  const {
77
70
  width: screenWidth,
@@ -96,30 +89,12 @@ export function Screen(_props: Props) {
96
89
 
97
90
  const layoutData = { isTablet, isTabletPortrait, width: screenWidth, height };
98
91
 
99
- const hasMenu =
100
- resolveNavigationPlugin({
101
- category: "menu",
102
- navigations: currentRiver.navigations,
103
- plugins,
104
- })?.identifier === "quick_brick_side_menu";
105
-
106
- const navBarProps = React.useMemo<MobileNavBarPluginProps | null>(() => {
107
- const props = getNavigationPropsV2({
108
- currentRiver,
109
- title,
110
- category: "nav_bar",
111
- });
112
-
113
- if (props) {
114
- return {
115
- ...props,
116
- id: pathname,
117
- pathname: pathname,
118
- };
119
- }
120
-
121
- return null;
122
- }, [currentRiver, pathname]);
92
+ const hasMenu = shouldNavBarDisplayMenu(currentRiver, plugins);
93
+
94
+ const navBarProps = React.useMemo<MobileNavBarPluginProps | null>(
95
+ getNavBarProps(currentRiver, pathname, title),
96
+ [currentRiver, pathname]
97
+ );
123
98
 
124
99
  const NavBar = React.useMemo(
125
100
  () =>
@@ -141,16 +116,12 @@ export function Screen(_props: Props) {
141
116
 
142
117
  const style = React.useMemo(
143
118
  () => ({
144
- flex: 1,
119
+ ...screenStyles,
145
120
  backgroundColor: isValidColor(backgroundColor)
146
121
  ? backgroundColor
147
122
  : theme.app_background_color,
148
- paddingTop: 0,
149
- // paddingBottom: insets.bottom, // Should be applied directly by UI plugin
150
- // paddingLeft: insets.left, // Should be applied directly by UI plugin
151
- // paddingRight: insets.right, // Should be applied directly by UI plugin
152
123
  }),
153
- [insets, screenWidth, theme.app_background_color, backgroundColor]
124
+ [theme.app_background_color, backgroundColor]
154
125
  );
155
126
 
156
127
  const targetScreenData =
@@ -168,19 +139,17 @@ export function Screen(_props: Props) {
168
139
  layoutData,
169
140
  })
170
141
  ) {
171
- return null;
142
+ return <View style={style} />;
172
143
  }
173
144
 
174
145
  return (
175
- <ScreenContextProvider pathname={pathname}>
176
- <View style={style}>
177
- {navBarProps && <NavBar {...navBarProps} hasMenu={hasMenu} />}
178
-
179
- <OfflineFallbackScreen screenData={targetScreenData}>
180
- {/* @TODO RouteManager doesn't use props, can they be removed ? */}
181
- <RouteManager pathname={pathname} screenData={screenData} />
182
- </OfflineFallbackScreen>
183
- </View>
184
- </ScreenContextProvider>
146
+ <View style={style}>
147
+ {navBarProps && <NavBar {...navBarProps} hasMenu={hasMenu} />}
148
+
149
+ <OfflineFallbackScreen screenData={targetScreenData}>
150
+ {/* @TODO RouteManager doesn't use props, can they be removed ? */}
151
+ <RouteManager pathname={pathname} screenData={screenData} />
152
+ </OfflineFallbackScreen>
153
+ </View>
185
154
  );
186
155
  }
@@ -5,6 +5,11 @@
5
5
  * is similar to the navbar, except that it covers scenarios where only the navbar (and not)
6
6
  * the menu, will be hidden
7
7
  *
8
+ * - playable screens
9
+ * - qb_search_screen
10
+ * - screen hooks that specify showNavBar or presentFullScreen
11
+ * - screens or nested screens that have allow_screen_plugin_presentation set to true
12
+ *
8
13
  * @param {String} route current route of the screen
9
14
  * @param {Object} screenData payload associated with the currently presented screen
10
15
  * @returns {Boolean}
@@ -1,13 +1,9 @@
1
1
  import React from "react";
2
- import {
3
- Animated,
4
- useWindowDimensions,
5
- ViewProps,
6
- ViewStyle,
7
- } from "react-native";
2
+ import { Animated, ViewProps, ViewStyle } from "react-native";
8
3
  import { useSafeAreaFrame } from "react-native-safe-area-context";
9
4
  import { PathnameContext } from "../../Contexts/PathnameContext";
10
5
  import { ScreenDataContext } from "../../Contexts/ScreenDataContext";
6
+ import { ScreenContextProvider } from "../../Contexts/ScreenContext";
11
7
 
12
8
  type Props = {
13
9
  children: any;
@@ -53,12 +49,15 @@ export function Scene({
53
49
  }, [screenData, isActive]);
54
50
 
55
51
  const frame = useSafeAreaFrame();
56
- const { height, width } = useWindowDimensions();
57
52
 
58
- // Safe frame takes too long to update,
59
- // so we can not rely on it there, and use height and width instead.
60
53
  const isAnimating = animating && overlayStyle;
61
- const dimensions = isAnimating ? { height, width } : frame;
54
+
55
+ const dimensions = isAnimating
56
+ ? {
57
+ height: "100%",
58
+ width: "100%",
59
+ }
60
+ : frame;
62
61
 
63
62
  return (
64
63
  <Animated.View
@@ -67,7 +66,9 @@ export function Scene({
67
66
  >
68
67
  <ScreenDataContext.Provider value={initialScreenData}>
69
68
  <PathnameContext.Provider value={pathname}>
70
- {children}
69
+ <ScreenContextProvider pathname={pathname}>
70
+ {children}
71
+ </ScreenContextProvider>
71
72
  </PathnameContext.Provider>
72
73
  </ScreenDataContext.Provider>
73
74
  {isAnimating && <Animated.View style={overlayStyle} />}
@@ -22,7 +22,9 @@ exports[`<Scene /> renders correctly 1`] = `
22
22
  }
23
23
  >
24
24
  <ContextProvider>
25
- <ContextProvider />
25
+ <ContextProvider>
26
+ <ScreenContextProvider />
27
+ </ContextProvider>
26
28
  </ContextProvider>
27
29
  </ForwardRef(AnimatedComponentWrapper)>
28
30
  `;
@@ -39,14 +41,16 @@ exports[`<Scene /> renders the overlay while animating 1`] = `
39
41
  "paddingBottom": 49,
40
42
  },
41
43
  Object {
42
- "height": 1334,
43
- "width": 750,
44
+ "height": "100%",
45
+ "width": "100%",
44
46
  },
45
47
  ]
46
48
  }
47
49
  >
48
50
  <ContextProvider>
49
- <ContextProvider />
51
+ <ContextProvider>
52
+ <ScreenContextProvider />
53
+ </ContextProvider>
50
54
  </ContextProvider>
51
55
  <ForwardRef(AnimatedComponentWrapper)
52
56
  style={
@@ -525,6 +525,16 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
525
525
  LiveImageManager.instance.onLiveImageError(this, error);
526
526
  };
527
527
 
528
+ onVideoError = (error: Error) => {
529
+ log_error(
530
+ `onVideoError: live-image, fail to play, switching back to image. error: ${
531
+ error?.message
532
+ }, ${playerInfo(this.player)}`
533
+ );
534
+
535
+ LiveImageManager.instance.onLiveImageError(this, error);
536
+ };
537
+
528
538
  onVideoEnd = (_event) => {
529
539
  log_info(
530
540
  `onVideoEnd: live-image, video completed, switching back to image, ${playerInfo(
@@ -58,6 +58,7 @@ export const AnimatedScrollModalComponent = ({ children }: Props) => {
58
58
  modalSnapPoints,
59
59
  lastSnap,
60
60
  setLastSnap,
61
+ setStartComponentsAnimation,
61
62
  } = useModalAnimationContext();
62
63
 
63
64
  const { isLanguageOverlayVisible } = React.useContext(PlayerContainerContext);
@@ -281,10 +282,11 @@ export const AnimatedScrollModalComponent = ({ children }: Props) => {
281
282
  if (
282
283
  preparedValue > 0 &&
283
284
  scrollPosition.current === 0 &&
284
- playerAnimationState !== PlayerAnimationStateEnum.drag_player
285
+ playerAnimationState !== PlayerAnimationStateEnum.drag_player &&
286
+ playerAnimationState !== PlayerAnimationStateEnum.drag_scroll
285
287
  ) {
286
- playerAnimationState !== PlayerAnimationStateEnum.drag_scroll &&
287
- setPlayerAnimationState(PlayerAnimationStateEnum.drag_scroll);
288
+ isMinimizedModal && setStartComponentsAnimation(true);
289
+ setPlayerAnimationState(PlayerAnimationStateEnum.drag_scroll);
288
290
  }
289
291
  });
290
292
 
@@ -292,7 +294,7 @@ export const AnimatedScrollModalComponent = ({ children }: Props) => {
292
294
  lastScrollY.removeListener(lastScrollYListenerId);
293
295
  dragScrollY.removeListener(dragListenerId);
294
296
  };
295
- }, [playerAnimationState, isAudioItem, modalSnapPoints]);
297
+ }, [playerAnimationState, isAudioItem, modalSnapPoints, isMinimizedModal]);
296
298
 
297
299
  const Wrapper = React.useMemo(
298
300
  () => (Platform.OS === "android" ? GestureHandlerRootView : View),
@@ -34,6 +34,7 @@ export const AnimatedVideoPlayer = ({ children }: Props) => {
34
34
  lastSnap,
35
35
  setLastSnap,
36
36
  resetPlayerAnimationState,
37
+ setStartComponentsAnimation,
37
38
  } = useModalAnimationContext();
38
39
 
39
40
  const { videoModalState } = useNavigation();
@@ -121,8 +122,10 @@ export const AnimatedVideoPlayer = ({ children }: Props) => {
121
122
  preparedValue > 0 &&
122
123
  playerAnimationState !== PlayerAnimationStateEnum.drag_scroll
123
124
  ) {
124
- playerAnimationState !== PlayerAnimationStateEnum.drag_player &&
125
+ if (playerAnimationState !== PlayerAnimationStateEnum.drag_player) {
126
+ isMinimizedModal && setStartComponentsAnimation(true);
125
127
  setPlayerAnimationState(PlayerAnimationStateEnum.drag_player);
128
+ }
126
129
 
127
130
  translateYOffset.setValue(
128
131
  isMaximazedModal
@@ -1,5 +1,4 @@
1
1
  import * as React from "react";
2
- import { Dimensions } from "react-native";
3
2
  import { useIsTabletLandscape } from "@applicaster/zapp-react-native-utils/reactHooks/device/useMemoizedIsTablet";
4
3
  import {
5
4
  addOrientationChangeListener,
@@ -9,11 +8,10 @@ import {
9
8
  } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
10
9
 
11
10
  import { getXray } from "@applicaster/zapp-react-native-utils/logger";
11
+ import { useSafeAreaFrame } from "react-native-safe-area-context";
12
12
 
13
13
  const { Logger } = getXray();
14
14
 
15
- const { width: WIDTH, height: HEIGHT } = Dimensions.get("window");
16
-
17
15
  const logger = new Logger(
18
16
  "hooks",
19
17
  "zapp-react-native-ui-components/Components/VideoModal"
@@ -24,20 +22,18 @@ type Size = {
24
22
  height: number | string;
25
23
  };
26
24
 
27
- const MODAL_SIZE_FOR_PORTRAIT: Size = {
28
- width: WIDTH,
29
- height: HEIGHT,
30
- };
31
-
32
25
  const MODAL_SIZE_FOR_LANDSCAPE: Size = {
33
26
  width: "100%",
34
27
  height: "100%",
35
28
  };
36
29
 
37
30
  export const useModalSize = (): Size => {
38
- const [modalSize, setModalSize] = React.useState<Size>(
39
- MODAL_SIZE_FOR_PORTRAIT
40
- );
31
+ const frame = useSafeAreaFrame();
32
+
33
+ const [modalSize, setModalSize] = React.useState<Size>({
34
+ width: frame.width,
35
+ height: frame.height,
36
+ });
41
37
 
42
38
  const setNewModalSize = React.useCallback((newSize, log) => {
43
39
  setModalSize((oldSize) => {
@@ -90,7 +86,9 @@ export const useModalSize = (): Size => {
90
86
  toOrientation === ORIENTATIONS.landscapeSensor;
91
87
 
92
88
  setNewModalSize(
93
- isLandscape ? MODAL_SIZE_FOR_LANDSCAPE : MODAL_SIZE_FOR_PORTRAIT,
89
+ isLandscape
90
+ ? MODAL_SIZE_FOR_LANDSCAPE
91
+ : { width: frame.width, height: frame.height },
94
92
  `useModalSize: set modal size ${
95
93
  isLandscape ? "landscape" : "portrait"
96
94
  } for orientation: ${to}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "12.0.0-alpha.7594390460",
3
+ "version": "12.0.0-alpha.7604714363",
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",
@@ -34,10 +34,10 @@
34
34
  "redux-mock-store": "^1.5.3"
35
35
  },
36
36
  "dependencies": {
37
- "@applicaster/applicaster-types": "12.0.0-alpha.7594390460",
38
- "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.7594390460",
39
- "@applicaster/zapp-react-native-redux": "12.0.0-alpha.7594390460",
40
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.7594390460",
37
+ "@applicaster/applicaster-types": "12.0.0-alpha.7604714363",
38
+ "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.7604714363",
39
+ "@applicaster/zapp-react-native-redux": "12.0.0-alpha.7604714363",
40
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.7604714363",
41
41
  "promise": "^8.3.0",
42
42
  "react-router-native": "^5.1.2",
43
43
  "url": "^0.11.0",