@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4201443043 → 12.0.0-alpha.4324313105
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.
- package/Components/BaseFocusable/index.tsx +1 -1
- package/Components/Cell/Cell.tsx +5 -5
- package/Components/Cell/CellWithFocusable.tsx +5 -17
- package/Components/FocusableGroup/index.tsx +0 -1
- package/Components/HandlePlayable/HandlePlayable.tsx +0 -3
- package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
- package/Components/Layout/TV/TransitionOverlay.tsx +64 -0
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +50 -10
- package/Components/Layout/TV/__tests__/index.test.tsx +74 -18
- package/Components/Layout/TV/index.tsx +108 -9
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +80 -109
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +1 -4
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +1 -4
- package/Components/PlayerContainer/PlayerContainer.tsx +1 -3
- package/Components/River/TV/River.tsx +23 -0
- package/Components/ScreenResolver/index.tsx +1 -3
- package/Components/VideoLive/LiveImageManager.ts +1 -15
- package/Components/VideoModal/PlayerDetails.tsx +3 -3
- package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
- package/package.json +5 -5
- package/Components/Navigator/StackNavigator.tsx +0 -48
- package/Components/Navigator/index.ts +0 -1
- package/Components/Screen/TV/hooks/index.ts +0 -3
- package/Components/Screen/TV/hooks/useInitialFocus.ts +0 -70
- package/Components/Screen/TV/hooks/useRestoreFocus.ts +0 -22
- package/Components/Screen/TV/index.web.tsx +0 -179
package/Components/Cell/Cell.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
|
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,
|
|
@@ -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
|
|
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={
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
9
|
+
import { ThemeContext } from "@applicaster/zapp-react-native-utils/theme";
|
|
10
10
|
|
|
11
|
-
jest.mock("
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
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={
|
|
58
|
+
<Provider store={store}>
|
|
34
59
|
<NavigationContext.Provider value={navigator}>
|
|
35
|
-
{
|
|
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" {...
|
|
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" {...
|
|
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(
|
|
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,32 +1,131 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
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 {
|
|
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";
|
|
5
18
|
|
|
6
19
|
import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
|
|
7
|
-
import {
|
|
20
|
+
import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
21
|
+
import { PathnameContext } from "../../../Contexts/PathnameContext";
|
|
22
|
+
import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
23
|
+
import { ScreenDataContext } from "../../../Contexts/ScreenDataContext";
|
|
8
24
|
|
|
9
25
|
type Components = {
|
|
10
26
|
NavBar: React.ComponentType<any>;
|
|
11
27
|
Background: React.ComponentType<any>;
|
|
12
28
|
};
|
|
13
29
|
|
|
30
|
+
type ComponentsExtraProps = {
|
|
31
|
+
Background?: Record<string, any>;
|
|
32
|
+
NavBar?: Record<string, any>;
|
|
33
|
+
};
|
|
34
|
+
|
|
14
35
|
type Props = {
|
|
15
36
|
Components: Components;
|
|
37
|
+
ComponentsExtraProps?: ComponentsExtraProps;
|
|
38
|
+
children: React.ReactNode;
|
|
16
39
|
};
|
|
17
40
|
|
|
18
|
-
const Layout = ({ Components }: Props) => {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
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
|
+
);
|
|
94
|
+
|
|
95
|
+
const isTVOS = isApplePlatform();
|
|
21
96
|
|
|
22
97
|
if (!appReady) {
|
|
23
98
|
return null;
|
|
24
99
|
}
|
|
25
100
|
|
|
26
101
|
return (
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
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>
|
|
30
129
|
);
|
|
31
130
|
};
|
|
32
131
|
|
|
@@ -7,50 +7,24 @@ type BorderPosition = "inside" | "outside" | "center";
|
|
|
7
7
|
interface Props {
|
|
8
8
|
style: ImageStyle | ViewStyle;
|
|
9
9
|
borderPosition: BorderPosition;
|
|
10
|
+
borderPaddingTop: number;
|
|
11
|
+
borderPaddingRight: number;
|
|
12
|
+
borderPaddingBottom: number;
|
|
13
|
+
borderPaddingLeft: number;
|
|
10
14
|
children: React.ReactNode;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
const
|
|
14
|
-
"marginTop",
|
|
15
|
-
"marginBottom",
|
|
16
|
-
"marginLeft",
|
|
17
|
-
"marginRight",
|
|
18
|
-
] as const;
|
|
17
|
+
const COLOR_TRANSPARENT = "transparent";
|
|
19
18
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
container: {
|
|
21
|
+
overflow: "visible",
|
|
22
|
+
borderWidth: 0,
|
|
23
|
+
borderColor: COLOR_TRANSPARENT,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
if (typeof value === "string" && value.endsWith("%")) {
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return false;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const calculateContainerSpacing = ({
|
|
36
|
-
spacingKey,
|
|
37
|
-
style,
|
|
38
|
-
additionalSpacing,
|
|
39
|
-
}: {
|
|
40
|
-
spacingKey: typeof MARGINS[number] | typeof PADDINGS[number];
|
|
41
|
-
style: ImageStyle | ViewStyle;
|
|
42
|
-
additionalSpacing: number;
|
|
43
|
-
}) => {
|
|
44
|
-
return {
|
|
45
|
-
[spacingKey]:
|
|
46
|
-
toNumberWithDefaultZero(style[spacingKey]) + additionalSpacing,
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const getContainerBorderWidth = (
|
|
51
|
-
borderPosition: BorderPosition,
|
|
52
|
-
borderWidth
|
|
53
|
-
) => {
|
|
27
|
+
const getBorderPadding = (borderPosition: BorderPosition, borderWidth) => {
|
|
54
28
|
switch (borderPosition) {
|
|
55
29
|
case "inside":
|
|
56
30
|
return 0;
|
|
@@ -63,104 +37,101 @@ const getContainerBorderWidth = (
|
|
|
63
37
|
}
|
|
64
38
|
};
|
|
65
39
|
|
|
66
|
-
const
|
|
67
|
-
switch (borderPosition) {
|
|
68
|
-
case "inside":
|
|
69
|
-
return borderWidth;
|
|
70
|
-
case "center":
|
|
71
|
-
return borderWidth / 2;
|
|
72
|
-
case "outside":
|
|
73
|
-
return 0;
|
|
74
|
-
default:
|
|
75
|
-
return 0;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const InsideBorder = ({
|
|
40
|
+
const Border = ({
|
|
80
41
|
style,
|
|
42
|
+
padding,
|
|
81
43
|
borderPosition,
|
|
82
44
|
}: {
|
|
83
45
|
style: ViewStyle;
|
|
46
|
+
padding: ViewStyle;
|
|
84
47
|
borderPosition: BorderPosition;
|
|
85
48
|
}) => {
|
|
86
|
-
if (borderPosition
|
|
49
|
+
if (!borderPosition) {
|
|
87
50
|
return null;
|
|
88
51
|
}
|
|
89
52
|
|
|
90
|
-
const
|
|
91
|
-
|
|
53
|
+
const borderWidth = toNumberWithDefaultZero(style.borderWidth);
|
|
54
|
+
const containerPadding = getBorderPadding(borderPosition, borderWidth);
|
|
55
|
+
|
|
56
|
+
const initialBorderRadius = toNumberWithDefaultZero(style.borderRadius);
|
|
57
|
+
const paddingTop = toNumberWithDefaultZero(padding.paddingTop);
|
|
58
|
+
const paddingRight = toNumberWithDefaultZero(padding.paddingRight);
|
|
59
|
+
const paddingBottom = toNumberWithDefaultZero(padding.paddingBottom);
|
|
60
|
+
const paddingLeft = toNumberWithDefaultZero(padding.paddingLeft);
|
|
61
|
+
|
|
62
|
+
const borderRadiusStyle = initialBorderRadius
|
|
63
|
+
? {
|
|
64
|
+
borderTopLeftRadius:
|
|
65
|
+
initialBorderRadius +
|
|
66
|
+
containerPadding +
|
|
67
|
+
Math.min(paddingTop, paddingLeft),
|
|
68
|
+
borderTopRightRadius:
|
|
69
|
+
initialBorderRadius +
|
|
70
|
+
containerPadding +
|
|
71
|
+
Math.min(paddingTop, paddingRight),
|
|
72
|
+
borderBottomLeftRadius:
|
|
73
|
+
initialBorderRadius +
|
|
74
|
+
containerPadding +
|
|
75
|
+
Math.min(paddingBottom, paddingLeft),
|
|
76
|
+
borderBottomRightRadius:
|
|
77
|
+
initialBorderRadius +
|
|
78
|
+
containerPadding +
|
|
79
|
+
Math.min(paddingBottom, paddingRight),
|
|
80
|
+
}
|
|
81
|
+
: {
|
|
82
|
+
borderTopLeftRadius: 0,
|
|
83
|
+
borderTopRightRadius: 0,
|
|
84
|
+
borderBottomLeftRadius: 0,
|
|
85
|
+
borderBottomRightRadius: 0,
|
|
86
|
+
};
|
|
92
87
|
|
|
93
88
|
return (
|
|
94
89
|
<View
|
|
95
90
|
style={{
|
|
96
91
|
...StyleSheet.absoluteFillObject,
|
|
97
|
-
|
|
92
|
+
right: -containerPadding - paddingRight,
|
|
93
|
+
bottom: -containerPadding - paddingBottom,
|
|
94
|
+
left: -containerPadding - paddingLeft,
|
|
95
|
+
top: -containerPadding - paddingTop,
|
|
96
|
+
borderWidth,
|
|
98
97
|
borderColor: style.borderColor,
|
|
99
|
-
|
|
98
|
+
...borderRadiusStyle,
|
|
100
99
|
}}
|
|
101
100
|
/>
|
|
102
101
|
);
|
|
103
102
|
};
|
|
104
103
|
|
|
105
104
|
export const BorderContainerView = (props: Props) => {
|
|
106
|
-
const {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
const {
|
|
106
|
+
borderPosition,
|
|
107
|
+
borderPaddingTop,
|
|
108
|
+
borderPaddingRight,
|
|
109
|
+
borderPaddingBottom,
|
|
110
|
+
borderPaddingLeft,
|
|
111
|
+
style,
|
|
112
|
+
children,
|
|
113
|
+
} = props;
|
|
112
114
|
|
|
113
115
|
const borderStyleForMissingBorder =
|
|
114
116
|
style?.borderWidth === 0 || !style?.borderColor ? { borderRadius: 0 } : {};
|
|
115
117
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const updatedPaddings = PADDINGS.reduce((acc, padding) => {
|
|
127
|
-
const paddingValue = calculateContainerSpacing({
|
|
128
|
-
spacingKey: padding,
|
|
129
|
-
style,
|
|
130
|
-
additionalSpacing: spacing,
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
return { ...acc, ...paddingValue };
|
|
134
|
-
}, {});
|
|
135
|
-
|
|
136
|
-
const updatedWidth = style.width
|
|
137
|
-
? isPercentageValue(style.width)
|
|
138
|
-
? style.width
|
|
139
|
-
: toNumberWithDefaultZero(style.width) - spacing * 2
|
|
140
|
-
: undefined;
|
|
141
|
-
|
|
142
|
-
const updatedHeight = style.height
|
|
143
|
-
? isPercentageValue(style.height)
|
|
144
|
-
? style.height
|
|
145
|
-
: toNumberWithDefaultZero(style.height) - spacing * 2
|
|
146
|
-
: undefined;
|
|
147
|
-
|
|
148
|
-
const containerStyle = {
|
|
149
|
-
borderWidth: getContainerBorderWidth(borderPosition, borderWidth),
|
|
150
|
-
padding: spacing,
|
|
151
|
-
...updatedMargins,
|
|
152
|
-
...updatedPaddings,
|
|
153
|
-
width: updatedWidth,
|
|
154
|
-
height: updatedHeight,
|
|
155
|
-
};
|
|
118
|
+
const padding =
|
|
119
|
+
borderPosition === "outside"
|
|
120
|
+
? {
|
|
121
|
+
paddingTop: borderPaddingTop,
|
|
122
|
+
paddingRight: borderPaddingRight,
|
|
123
|
+
paddingBottom: borderPaddingBottom,
|
|
124
|
+
paddingLeft: borderPaddingLeft,
|
|
125
|
+
}
|
|
126
|
+
: {};
|
|
156
127
|
|
|
157
128
|
return (
|
|
158
|
-
<View
|
|
129
|
+
<View
|
|
130
|
+
{...props}
|
|
131
|
+
style={[style, styles.container, borderStyleForMissingBorder]}
|
|
132
|
+
>
|
|
159
133
|
{children}
|
|
160
|
-
<
|
|
161
|
-
style={{ ...style, ...borderStyleForMissingBorder }}
|
|
162
|
-
borderPosition={borderPosition}
|
|
163
|
-
/>
|
|
134
|
+
<Border style={style} padding={padding} borderPosition={borderPosition} />
|
|
164
135
|
</View>
|
|
165
136
|
);
|
|
166
137
|
};
|
|
@@ -61,10 +61,7 @@ export function ButtonContainerView({
|
|
|
61
61
|
<View style={applyRTLStylesIfNeeded(style, isRTL)}>
|
|
62
62
|
{insertBetween(
|
|
63
63
|
(index) => (
|
|
64
|
-
<HorizontalSeparator
|
|
65
|
-
key={`separator_${index}`}
|
|
66
|
-
width={horizontalGutter}
|
|
67
|
-
/>
|
|
64
|
+
<HorizontalSeparator key={index} width={horizontalGutter} />
|
|
68
65
|
),
|
|
69
66
|
cloneElementsWithIds(buttonIds, filteredChildren)
|
|
70
67
|
)}
|
|
@@ -29,10 +29,7 @@ export function ButtonContainerView({
|
|
|
29
29
|
<View style={style}>
|
|
30
30
|
{insertBetween(
|
|
31
31
|
(index) => (
|
|
32
|
-
<HorizontalSeparator
|
|
33
|
-
key={`separator_${index}`}
|
|
34
|
-
width={horizontalGutter}
|
|
35
|
-
/>
|
|
32
|
+
<HorizontalSeparator key={index} width={horizontalGutter} />
|
|
36
33
|
),
|
|
37
34
|
filteredChildren
|
|
38
35
|
)}
|
|
@@ -68,7 +68,6 @@ type Props = {
|
|
|
68
68
|
mode?: VideoModalMode;
|
|
69
69
|
loadPipesData: (url: string, options: {}) => void;
|
|
70
70
|
disableCastAction?: boolean;
|
|
71
|
-
groupId?: string;
|
|
72
71
|
};
|
|
73
72
|
|
|
74
73
|
type ScreenStyles = ZappRiver["styles"] & {
|
|
@@ -201,7 +200,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
201
200
|
style: videoStyle,
|
|
202
201
|
mode = VideoModalMode.MAXIMIZED,
|
|
203
202
|
disableCastAction = false,
|
|
204
|
-
groupId,
|
|
205
203
|
} = props;
|
|
206
204
|
|
|
207
205
|
// Hooks
|
|
@@ -251,6 +249,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
251
249
|
return;
|
|
252
250
|
}
|
|
253
251
|
|
|
252
|
+
navigator.goBack();
|
|
254
253
|
showNavBar(true);
|
|
255
254
|
}, [isModal, navigator.goBack, showNavBar]);
|
|
256
255
|
|
|
@@ -630,7 +629,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
630
629
|
style={styles.focusableGroup}
|
|
631
630
|
preferredFocus
|
|
632
631
|
shouldUsePreferredFocus
|
|
633
|
-
groupId={groupId}
|
|
634
632
|
>
|
|
635
633
|
{/* Video player and components */}
|
|
636
634
|
<View
|
|
@@ -7,10 +7,14 @@ import * as R from "ramda";
|
|
|
7
7
|
import {
|
|
8
8
|
useFeedLoader,
|
|
9
9
|
useLayoutVersion,
|
|
10
|
+
useNavigationPluginData,
|
|
10
11
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
11
12
|
import { GeneralContentScreen } from "../../GeneralContentScreen";
|
|
12
13
|
import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
|
|
13
14
|
import { utilsLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
15
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
16
|
+
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
17
|
+
import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
14
18
|
import {
|
|
15
19
|
useNavbarState,
|
|
16
20
|
useSetNavbarState,
|
|
@@ -47,6 +51,8 @@ export const River = (props: Props) => {
|
|
|
47
51
|
const { setTitle: setScreenTitle, setSummary: setScreenSummary } =
|
|
48
52
|
useSetNavbarState();
|
|
49
53
|
|
|
54
|
+
const navigationPluginData = useNavigationPluginData();
|
|
55
|
+
|
|
50
56
|
const rivers = useRivers();
|
|
51
57
|
const isV2 = useLayoutVersion({ isV2: true });
|
|
52
58
|
|
|
@@ -93,6 +99,23 @@ export const River = (props: Props) => {
|
|
|
93
99
|
}
|
|
94
100
|
}, [feedData, screenData, screenTitle, screenSummary]);
|
|
95
101
|
|
|
102
|
+
React.useEffect(() => {
|
|
103
|
+
if (
|
|
104
|
+
isWeb() &&
|
|
105
|
+
toBooleanWithDefaultFalse(
|
|
106
|
+
navigationPluginData?.rules?.focus_on_content
|
|
107
|
+
) &&
|
|
108
|
+
river &&
|
|
109
|
+
river.type !== "general_content"
|
|
110
|
+
) {
|
|
111
|
+
const groupId = R.pathOr("river", ["groupId"], componentsMapExtraProps);
|
|
112
|
+
|
|
113
|
+
focusManager.setupInitialFocus(groupId, true);
|
|
114
|
+
focusManager.setInitialFocus();
|
|
115
|
+
focusManager.enableAutomaticFocus();
|
|
116
|
+
}
|
|
117
|
+
}, [river, componentsMapExtraProps]);
|
|
118
|
+
|
|
96
119
|
if (!river) {
|
|
97
120
|
utilsLogger.error({
|
|
98
121
|
message: `River ${screenData.id} not found!`,
|
|
@@ -32,13 +32,12 @@ type Props = {
|
|
|
32
32
|
nextFocusLeft?: React.Ref<any>;
|
|
33
33
|
nextFocusUp?: React.Ref<any>;
|
|
34
34
|
};
|
|
35
|
-
groupId?: string;
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
export function ScreenResolverComponent(props: Props) {
|
|
39
38
|
useScreenAnalytics(props);
|
|
40
39
|
|
|
41
|
-
const { screenType, screenId, screenData
|
|
40
|
+
const { screenType, screenId, screenData } = props;
|
|
42
41
|
|
|
43
42
|
const { hookPlugin } = screenData || {};
|
|
44
43
|
|
|
@@ -82,7 +81,6 @@ export function ScreenResolverComponent(props: Props) {
|
|
|
82
81
|
item={screenData}
|
|
83
82
|
mode={mode === "PIP" ? "PIP" : "FULLSCREEN"}
|
|
84
83
|
isModal={false}
|
|
85
|
-
groupId={groupId}
|
|
86
84
|
/>
|
|
87
85
|
);
|
|
88
86
|
}
|
|
@@ -10,11 +10,7 @@ import { Component } from "react";
|
|
|
10
10
|
|
|
11
11
|
const { log_debug, log_info, log_error } = loggerLiveImageManager;
|
|
12
12
|
|
|
13
|
-
export type LiveImageManagerEvent =
|
|
14
|
-
| "onEnd"
|
|
15
|
-
| "onStart"
|
|
16
|
-
| "onError"
|
|
17
|
-
| "onDestroy";
|
|
13
|
+
export type LiveImageManagerEvent = "onEnd" | "onStart" | "onError";
|
|
18
14
|
|
|
19
15
|
export type LiveImageManagerListenerData = {
|
|
20
16
|
id: string;
|
|
@@ -106,16 +102,6 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
106
102
|
}
|
|
107
103
|
|
|
108
104
|
this.items = this.items.filter((i) => i !== item);
|
|
109
|
-
|
|
110
|
-
this.invokeListenersUpdate({
|
|
111
|
-
callbackName: "onDestroy",
|
|
112
|
-
event: {
|
|
113
|
-
item,
|
|
114
|
-
playerId: this.currentlyPlaying?.playerId,
|
|
115
|
-
primaryPlayerId: this.primaryPlayer?.playerId,
|
|
116
|
-
entry: item.getPlayer().getEntry(),
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
105
|
};
|
|
120
106
|
|
|
121
107
|
public onViewportEnter = (item: LiveImage) => {
|
|
@@ -42,12 +42,12 @@ export const PlayerDetails = ({
|
|
|
42
42
|
: {};
|
|
43
43
|
|
|
44
44
|
// Animation setup
|
|
45
|
-
const translateY = useRef(new Animated.Value(
|
|
45
|
+
const translateY = useRef(new Animated.Value(50)).current;
|
|
46
46
|
const opacity = useRef(new Animated.Value(0)).current;
|
|
47
47
|
|
|
48
48
|
const runAnimation = () => {
|
|
49
49
|
Animated.timing(translateY, {
|
|
50
|
-
toValue:
|
|
50
|
+
toValue: -30,
|
|
51
51
|
duration: 500,
|
|
52
52
|
easing: Easing.inOut(Easing.ease),
|
|
53
53
|
useNativeDriver: true,
|
|
@@ -63,7 +63,7 @@ export const PlayerDetails = ({
|
|
|
63
63
|
|
|
64
64
|
useEffect(() => {
|
|
65
65
|
if (isAudioPlayer) {
|
|
66
|
-
setTimeout(() => runAnimation(),
|
|
66
|
+
setTimeout(() => runAnimation(), 1500);
|
|
67
67
|
}
|
|
68
68
|
}, [isAudioPlayer]);
|
|
69
69
|
|
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.
|
|
3
|
+
"version": "12.0.0-alpha.4324313105",
|
|
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.
|
|
38
|
-
"@applicaster/zapp-react-native-bridge": "12.0.0-alpha.
|
|
39
|
-
"@applicaster/zapp-react-native-redux": "12.0.0-alpha.
|
|
40
|
-
"@applicaster/zapp-react-native-utils": "12.0.0-alpha.
|
|
37
|
+
"@applicaster/applicaster-types": "12.0.0-alpha.4324313105",
|
|
38
|
+
"@applicaster/zapp-react-native-bridge": "12.0.0-alpha.4324313105",
|
|
39
|
+
"@applicaster/zapp-react-native-redux": "12.0.0-alpha.4324313105",
|
|
40
|
+
"@applicaster/zapp-react-native-utils": "12.0.0-alpha.4324313105",
|
|
41
41
|
"promise": "^8.3.0",
|
|
42
42
|
"react-router-native": "^5.1.2",
|
|
43
43
|
"url": "^0.11.0",
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { isEmpty } from "ramda";
|
|
3
|
-
|
|
4
|
-
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
5
|
-
import { getPathAttributes } from "@applicaster/zapp-react-native-utils/navigationUtils";
|
|
6
|
-
import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
|
|
7
|
-
import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
|
|
8
|
-
import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
|
|
9
|
-
|
|
10
|
-
import { Screen } from "../Screen/TV/index.web";
|
|
11
|
-
|
|
12
|
-
type Components = {
|
|
13
|
-
NavBar: React.ComponentType<any>;
|
|
14
|
-
Background: React.ComponentType<any>;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type Props = {
|
|
18
|
-
Components: Components;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const StackNavigator = ({ Components }: Props) => {
|
|
22
|
-
const { mainStack, currentRoute } = useNavigation();
|
|
23
|
-
|
|
24
|
-
const renderScreen = ({ route, state }) => {
|
|
25
|
-
const pathAttributes = getPathAttributes({ pathname: route });
|
|
26
|
-
|
|
27
|
-
if (isEmpty(pathAttributes)) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<FocusableGroup
|
|
33
|
-
id={route}
|
|
34
|
-
preferredFocus={route === currentRoute}
|
|
35
|
-
excludeFromFocusSearching
|
|
36
|
-
key={route}
|
|
37
|
-
>
|
|
38
|
-
<ScreenDataContext.Provider value={state}>
|
|
39
|
-
<PathnameContext.Provider value={route}>
|
|
40
|
-
<Screen route={route} Components={Components} />
|
|
41
|
-
</PathnameContext.Provider>
|
|
42
|
-
</ScreenDataContext.Provider>
|
|
43
|
-
</FocusableGroup>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return <>{mainStack.map(renderScreen)}</>;
|
|
48
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { StackNavigator } from "./StackNavigator";
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {
|
|
3
|
-
useNavigationPluginData,
|
|
4
|
-
useIsNavBarVisible,
|
|
5
|
-
useRoute,
|
|
6
|
-
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
7
|
-
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
8
|
-
import {
|
|
9
|
-
setFocusOnContent,
|
|
10
|
-
setFocusOnMenu,
|
|
11
|
-
} from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux";
|
|
12
|
-
|
|
13
|
-
type Return =
|
|
14
|
-
| {
|
|
15
|
-
onContent: true;
|
|
16
|
-
onMenu: false;
|
|
17
|
-
}
|
|
18
|
-
| {
|
|
19
|
-
onContent: false;
|
|
20
|
-
onMenu: true;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const getInitialFocus = (focusOnContent, isNavBarVisible): Return => {
|
|
24
|
-
if (focusOnContent) {
|
|
25
|
-
return {
|
|
26
|
-
onContent: true,
|
|
27
|
-
onMenu: false,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!focusOnContent && isNavBarVisible) {
|
|
32
|
-
// put focus on navbar
|
|
33
|
-
return {
|
|
34
|
-
onContent: false,
|
|
35
|
-
onMenu: true,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// we don't have navbar, put focus on content anyway
|
|
40
|
-
return {
|
|
41
|
-
onContent: true,
|
|
42
|
-
onMenu: false,
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const useInitialFocus = (): void => {
|
|
47
|
-
const navigationPluginData = useNavigationPluginData();
|
|
48
|
-
|
|
49
|
-
const focusOnContent = toBooleanWithDefaultFalse(
|
|
50
|
-
navigationPluginData?.rules?.focus_on_content
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const isNavBarVisible = useIsNavBarVisible();
|
|
54
|
-
|
|
55
|
-
const { pathname: currentRoute } = useRoute();
|
|
56
|
-
|
|
57
|
-
React.useEffect(() => {
|
|
58
|
-
const initialFocus = getInitialFocus(focusOnContent, isNavBarVisible);
|
|
59
|
-
|
|
60
|
-
if (initialFocus.onContent) {
|
|
61
|
-
setFocusOnContent(currentRoute);
|
|
62
|
-
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (initialFocus.onMenu) {
|
|
67
|
-
setFocusOnMenu(currentRoute);
|
|
68
|
-
}
|
|
69
|
-
}, []);
|
|
70
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { isNil } from "ramda";
|
|
3
|
-
import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
4
|
-
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
|
|
5
|
-
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
6
|
-
|
|
7
|
-
export const useRestoreFocus = (): void => {
|
|
8
|
-
const isScreenActive = useIsScreenActive();
|
|
9
|
-
const previousIsScreenActive = usePrevious(isScreenActive);
|
|
10
|
-
|
|
11
|
-
React.useEffect(() => {
|
|
12
|
-
if (isNil(previousIsScreenActive)) {
|
|
13
|
-
// it's initial mounting of the screen, do nothing here
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (!previousIsScreenActive && isScreenActive) {
|
|
18
|
-
// restore focus when we go back from next screen
|
|
19
|
-
focusManager.setInitialFocus();
|
|
20
|
-
}
|
|
21
|
-
}, [previousIsScreenActive, isScreenActive]);
|
|
22
|
-
};
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { last, pathOr } from "ramda";
|
|
3
|
-
import { View, StyleSheet } from "react-native";
|
|
4
|
-
import {
|
|
5
|
-
getNavigationProps,
|
|
6
|
-
getPathAttributes,
|
|
7
|
-
resolveNavigationPlugin,
|
|
8
|
-
} from "@applicaster/zapp-react-native-utils/navigationUtils";
|
|
9
|
-
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
10
|
-
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
11
|
-
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
12
|
-
import { useScreenBackgroundColor } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
13
|
-
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
14
|
-
|
|
15
|
-
import { NavBarContainer } from "../../Layout/TV/NavBarContainer";
|
|
16
|
-
import { ScreenResolver } from "../../ScreenResolver";
|
|
17
|
-
import { useInitialFocus, useRestoreFocus } from "./hooks";
|
|
18
|
-
|
|
19
|
-
const styles = StyleSheet.create({
|
|
20
|
-
container: {
|
|
21
|
-
position: "absolute",
|
|
22
|
-
top: 0,
|
|
23
|
-
left: 0,
|
|
24
|
-
right: 0,
|
|
25
|
-
bottom: 0,
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
type Components = {
|
|
30
|
-
NavBar: React.ComponentType<any>;
|
|
31
|
-
Background: React.ComponentType<any>;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type Props = {
|
|
35
|
-
route: string;
|
|
36
|
-
Components: Components;
|
|
37
|
-
};
|
|
38
|
-
|
|
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
|
-
const getActiveRiver = ({
|
|
52
|
-
screenId,
|
|
53
|
-
screenType,
|
|
54
|
-
routeState,
|
|
55
|
-
rivers,
|
|
56
|
-
fallbackActiveRiver,
|
|
57
|
-
}) => {
|
|
58
|
-
if (rivers[screenId]) {
|
|
59
|
-
if (screenType === "hooks") {
|
|
60
|
-
const navigations = routeState?.state?.entry?.payload?.navigations;
|
|
61
|
-
|
|
62
|
-
if (navigations) {
|
|
63
|
-
return routeState.state.entry.payload;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return routeState?.state?.screen;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// screenId is not presented into rivers, use fallback instead
|
|
71
|
-
return fallbackActiveRiver;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const getNavigations = ({
|
|
75
|
-
screenId,
|
|
76
|
-
screenType,
|
|
77
|
-
routeState,
|
|
78
|
-
rivers,
|
|
79
|
-
fallbackNavigations,
|
|
80
|
-
}) => {
|
|
81
|
-
if (rivers[screenId]) {
|
|
82
|
-
// river presents
|
|
83
|
-
if (screenType === "hooks") {
|
|
84
|
-
const navigations = routeState?.state?.entry?.payload?.navigations;
|
|
85
|
-
|
|
86
|
-
if (navigations) {
|
|
87
|
-
return navigations;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return routeState?.state?.screen?.navigations;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// screenId is not presented into rivers, use fallback instead
|
|
95
|
-
return fallbackNavigations;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export const Screen = ({ route, Components }: Props) => {
|
|
99
|
-
if (isNilOrEmpty(route)) {
|
|
100
|
-
throw Error("Required props: route is missing");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const navigator = useNavigation();
|
|
104
|
-
const { plugins = [], rivers = [] } = usePickFromState(["plugins", "rivers"]);
|
|
105
|
-
|
|
106
|
-
const pathAttributes = getPathAttributes({ pathname: route });
|
|
107
|
-
const routeState = navigator.getStackForPathname(route);
|
|
108
|
-
|
|
109
|
-
const { screenType, screenId } = last(pathAttributes);
|
|
110
|
-
|
|
111
|
-
const backgroundColor = useScreenBackgroundColor(screenId);
|
|
112
|
-
|
|
113
|
-
useInitialFocus();
|
|
114
|
-
useRestoreFocus();
|
|
115
|
-
|
|
116
|
-
const activeRiver = getActiveRiver({
|
|
117
|
-
screenId,
|
|
118
|
-
screenType,
|
|
119
|
-
routeState,
|
|
120
|
-
rivers,
|
|
121
|
-
fallbackActiveRiver: navigator.activeRiver,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const navigationProps = getNavigationProps({
|
|
125
|
-
navigator: {
|
|
126
|
-
// @ts-ignore
|
|
127
|
-
activeRiver,
|
|
128
|
-
// @ts-ignore
|
|
129
|
-
screenTitle: routeState.state.entry?.title || routeState.screen?.name,
|
|
130
|
-
},
|
|
131
|
-
category: "menu",
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const navigations = getNavigations({
|
|
135
|
-
screenId,
|
|
136
|
-
screenType,
|
|
137
|
-
routeState,
|
|
138
|
-
rivers,
|
|
139
|
-
fallbackNavigations: navigator.activeRiver.navigations,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const menuPlugin = resolveNavigationPlugin({
|
|
143
|
-
category: "menu",
|
|
144
|
-
navigations,
|
|
145
|
-
plugins,
|
|
146
|
-
}) as QuickBrickPlugin;
|
|
147
|
-
|
|
148
|
-
const NavBar = (Components.NavBar ||
|
|
149
|
-
menuPlugin.module) as React.ComponentType<any>;
|
|
150
|
-
|
|
151
|
-
const isPlayer = isPlayerPlugin(routeState);
|
|
152
|
-
const isLogin = isLoginPlugin(routeState);
|
|
153
|
-
|
|
154
|
-
const isNavBarVisible = !isPlayer && !isLogin;
|
|
155
|
-
|
|
156
|
-
const screenData =
|
|
157
|
-
screenType === "hooks" || isPlayer
|
|
158
|
-
? routeState?.state?.entry
|
|
159
|
-
: routeState?.state?.screen;
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<View style={[styles.container, { backgroundColor }]}>
|
|
163
|
-
<NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
|
|
164
|
-
<NavBar
|
|
165
|
-
groupId={route}
|
|
166
|
-
isVisible={isNavBarVisible}
|
|
167
|
-
menuPlugin={menuPlugin}
|
|
168
|
-
navigationProps={navigationProps}
|
|
169
|
-
/>
|
|
170
|
-
</NavBarContainer>
|
|
171
|
-
<ScreenResolver
|
|
172
|
-
screenType={screenType}
|
|
173
|
-
screenId={screenId}
|
|
174
|
-
screenData={screenData}
|
|
175
|
-
groupId={route}
|
|
176
|
-
/>
|
|
177
|
-
</View>
|
|
178
|
-
);
|
|
179
|
-
};
|