@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4840257570 → 12.0.0-alpha.4885205607
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 +3 -3
- package/Components/Cell/Cell.tsx +30 -9
- package/Components/Cell/CellWithFocusable.tsx +23 -9
- package/Components/Cell/TvOSCellComponent.tsx +3 -7
- package/Components/Focusable/Focusable.tsx +6 -1
- package/Components/FocusableGroup/index.tsx +11 -2
- package/Components/HandlePlayable/HandlePlayable.tsx +3 -0
- package/Components/Layout/TV/ScreenContainer.tsx +79 -5
- package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
- package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +38 -28
- package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +25 -13
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -50
- package/Components/Layout/TV/__tests__/index.test.tsx +20 -75
- package/Components/Layout/TV/index.tsx +14 -67
- package/Components/Layout/TV/index.web.tsx +33 -0
- package/Components/Layout/index.tsx +15 -11
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -1
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +88 -112
- package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +20 -0
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
- package/Components/MasterCell/dataAdapter.ts +21 -7
- package/Components/MasterCell/index.tsx +5 -3
- package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
- package/Components/MasterCell/utils/index.ts +32 -29
- package/Components/Navigator/StackNavigator.tsx +57 -0
- package/Components/Navigator/index.ts +1 -0
- package/Components/PlayerContainer/PlayerContainer.tsx +5 -1
- package/Components/River/ComponentsMap/ComponentsMap.tsx +6 -0
- package/Components/River/RefreshControl.tsx +2 -2
- package/Components/River/TV/River.tsx +0 -23
- package/Components/RouteManager/index.tsx +2 -2
- package/Components/Screen/TV/hooks/index.ts +3 -0
- package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
- package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
- package/Components/Screen/TV/index.web.tsx +165 -0
- package/Components/Screen/__tests__/Screen.test.tsx +3 -0
- package/Components/Screen/index.tsx +55 -58
- package/Components/Screen/navigationHandler.ts +5 -0
- package/Components/ScreenResolver/index.tsx +9 -12
- package/Components/Transitioner/Scene.tsx +32 -26
- package/Components/Transitioner/Transitioner.tsx +38 -43
- package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +12 -4
- package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
- package/Components/VideoLive/LiveImageManager.ts +28 -1
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +52 -24
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +26 -24
- package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +2 -14
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +56 -43
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +42 -32
- package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +104 -86
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +32 -4
- package/Components/VideoModal/ModalAnimation/utils.ts +7 -4
- package/Components/VideoModal/PlayerDetails.tsx +3 -3
- package/Components/VideoModal/PlayerWrapper.tsx +67 -60
- package/Components/VideoModal/VideoModal.tsx +4 -4
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +7 -0
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +32 -12
- package/Components/VideoModal/hooks/useModalSize.ts +10 -12
- package/Components/VideoModal/utils.ts +5 -2
- package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
- package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +4 -4
- package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
- package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
- package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
- package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
- package/Contexts/FocusableGroupContext/index.ts +7 -0
- package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
- package/Contexts/ScreenDataContext/index.tsx +20 -0
- package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
- package/package.json +5 -5
- package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
- /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
- /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
|
@@ -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,10 +31,13 @@ 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
|
-
<HorizontalSeparator
|
|
37
|
+
<HorizontalSeparator
|
|
38
|
+
key={`separator_${index}`}
|
|
39
|
+
width={horizontalGutter}
|
|
40
|
+
/>
|
|
33
41
|
),
|
|
34
42
|
filteredChildren
|
|
35
43
|
)}
|
|
@@ -37,14 +37,28 @@ function retrieveData(entry, func, args) {
|
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
39
|
export function configInflater(
|
|
40
|
-
entry,
|
|
41
|
-
{
|
|
40
|
+
entry: any,
|
|
41
|
+
{
|
|
42
|
+
type,
|
|
43
|
+
style,
|
|
44
|
+
additionalProps = {},
|
|
45
|
+
data = [],
|
|
46
|
+
elements,
|
|
47
|
+
}: {
|
|
48
|
+
type: any;
|
|
49
|
+
style: any;
|
|
50
|
+
additionalProps?: Record<string, any>;
|
|
51
|
+
data?: Array<{ propName: string; func: Function; args: any[] }>;
|
|
52
|
+
elements?: any[];
|
|
53
|
+
}
|
|
42
54
|
) {
|
|
43
|
-
const props =
|
|
44
|
-
(acc, curr) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
const props = data.reduce(
|
|
56
|
+
(acc, curr) => {
|
|
57
|
+
acc[curr.propName] = retrieveData(entry, curr.func, curr.args);
|
|
58
|
+
|
|
59
|
+
return acc;
|
|
60
|
+
},
|
|
61
|
+
{ ...additionalProps }
|
|
48
62
|
);
|
|
49
63
|
|
|
50
64
|
let adjustedElements;
|
|
@@ -52,6 +52,8 @@ export function masterCellBuilder({
|
|
|
52
52
|
const { View } = componentsMap;
|
|
53
53
|
const elementsBuilder = dataAdapter(cellConfiguration);
|
|
54
54
|
|
|
55
|
+
// This is very expensive operation
|
|
56
|
+
// Keep calls to minimum
|
|
55
57
|
const hasFocusableInside = hasFocusableInsideBuilder(elementsBuilder);
|
|
56
58
|
|
|
57
59
|
/**
|
|
@@ -74,8 +76,8 @@ export function masterCellBuilder({
|
|
|
74
76
|
const selectedState = screen?.selectedEntry;
|
|
75
77
|
|
|
76
78
|
const entryIsSelected = React.useMemo(
|
|
77
|
-
() => selectedState?.id === item?.id,
|
|
78
|
-
[selectedState?.id]
|
|
79
|
+
() => !!(selectedState?.id && selectedState?.id === item?.id),
|
|
80
|
+
[selectedState?.id, item?.id]
|
|
79
81
|
);
|
|
80
82
|
|
|
81
83
|
const elements = React.useMemo(
|
|
@@ -84,7 +86,7 @@ export function masterCellBuilder({
|
|
|
84
86
|
entry: item,
|
|
85
87
|
state: getEntryState(state, entryIsSelected),
|
|
86
88
|
}),
|
|
87
|
-
[state, item, entryIsSelected]
|
|
89
|
+
[state, item?.id, entryIsSelected] // Assuming that item won't mutate
|
|
88
90
|
);
|
|
89
91
|
|
|
90
92
|
const wrapperRef = React.useRef(null);
|
|
@@ -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
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo } from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
+
import validateColor from "validate-color";
|
|
3
4
|
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
4
5
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
6
|
|
|
@@ -9,6 +10,13 @@ import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerM
|
|
|
9
10
|
import { PushTopicManager } from "@applicaster/zapp-react-native-bridge/PushNotifications/PushTopicManager";
|
|
10
11
|
import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
|
|
11
12
|
import { getCellState } from "../../Cell/utils";
|
|
13
|
+
import { getColorFromData } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
14
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
15
|
+
|
|
16
|
+
const log = createLogger({
|
|
17
|
+
category: "Debug",
|
|
18
|
+
subsystem: "MasterCellUtils",
|
|
19
|
+
});
|
|
12
20
|
|
|
13
21
|
const hasElementSpecificViewType = (viewType) => (element) => {
|
|
14
22
|
if (R.isNil(element)) {
|
|
@@ -47,44 +55,37 @@ export const hasElementsSpecificViewType = (viewType) => (elements) => {
|
|
|
47
55
|
return R.any(hasElementSpecificViewType(viewType))(elements);
|
|
48
56
|
};
|
|
49
57
|
|
|
50
|
-
const isColorOrEmpty = (value) =>
|
|
51
|
-
value === null ||
|
|
52
|
-
value === undefined ||
|
|
53
|
-
value === "" ||
|
|
54
|
-
value.startsWith("#") ||
|
|
55
|
-
value.startsWith("rgba") ||
|
|
56
|
-
value === "transparent";
|
|
57
|
-
|
|
58
58
|
function resolveColorForProp(entry, style, colorProp) {
|
|
59
|
-
const
|
|
59
|
+
const colorFromProp = style[colorProp];
|
|
60
|
+
const nestedEntryValue = R.path(colorFromProp.split("."), entry);
|
|
61
|
+
|
|
62
|
+
log.log_error("resolvecolor resolveColorforProp", {
|
|
63
|
+
colorFromProp,
|
|
64
|
+
nestedEntryValue,
|
|
65
|
+
entry,
|
|
66
|
+
style,
|
|
67
|
+
colorProp,
|
|
68
|
+
});
|
|
60
69
|
|
|
61
|
-
if (
|
|
62
|
-
|
|
70
|
+
if (
|
|
71
|
+
nestedEntryValue === undefined &&
|
|
72
|
+
!validateColor(colorFromProp.replace(".00", "")) // https://github.com/dreamyguy/validate-color/issues/44
|
|
73
|
+
) {
|
|
74
|
+
logWarning(colorFromProp, style, entry, nestedEntryValue);
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (nestedEntryValue === null || typeof nestedEntryValue !== "object") {
|
|
70
|
-
logWarning(colorValue, style, entry, nestedEntryValue);
|
|
71
|
-
|
|
72
|
-
return style;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
nestedEntryValue = nestedEntryValue[part];
|
|
76
|
-
}
|
|
77
|
+
const colorValue = getColorFromData({
|
|
78
|
+
data: entry,
|
|
79
|
+
valueFromLayout: colorFromProp,
|
|
80
|
+
});
|
|
77
81
|
|
|
78
|
-
if (
|
|
79
|
-
logWarning(
|
|
82
|
+
if (!colorValue) {
|
|
83
|
+
logWarning(colorProp, style, entry, colorValue);
|
|
80
84
|
|
|
81
85
|
return style;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
style[colorProp] = nestedEntryValue;
|
|
86
|
-
|
|
87
|
-
return style;
|
|
88
|
+
return { ...style, [colorProp]: colorValue };
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
export function resolveColor(entry, style) {
|
|
@@ -96,6 +97,8 @@ export function resolveColor(entry, style) {
|
|
|
96
97
|
const styleKeys = Object.keys(style);
|
|
97
98
|
const colorKeys = styleKeys.filter((key) => /color/i.test(key));
|
|
98
99
|
|
|
100
|
+
log.log_error("resolvecolor resolveColor", { colorKeys, entry, style });
|
|
101
|
+
|
|
99
102
|
return colorKeys.reduce((acc, value) => {
|
|
100
103
|
return { ...style, ...resolveColorForProp(entry, acc, value) };
|
|
101
104
|
}, style);
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
12
|
+
import { ScreenContextProvider } from "../../Contexts/ScreenContext";
|
|
13
|
+
|
|
14
|
+
type Components = {
|
|
15
|
+
NavBar: React.ComponentType<any>;
|
|
16
|
+
Background: React.ComponentType<any>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
Components: Components;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const StackNavigator = ({ Components }: Props) => {
|
|
24
|
+
const { mainStack } = useNavigation();
|
|
25
|
+
|
|
26
|
+
const renderScreen = ({ route, state }, index) => {
|
|
27
|
+
const pathAttributes = getPathAttributes({ pathname: route });
|
|
28
|
+
|
|
29
|
+
if (isEmpty(pathAttributes)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
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
|
+
|
|
38
|
+
return (
|
|
39
|
+
<FocusableGroup
|
|
40
|
+
id={route}
|
|
41
|
+
preferredFocus={isLast(index, mainStack.length)}
|
|
42
|
+
excludeFromFocusSearching
|
|
43
|
+
key={routeId}
|
|
44
|
+
>
|
|
45
|
+
<ScreenDataContext.Provider value={state}>
|
|
46
|
+
<PathnameContext.Provider value={route}>
|
|
47
|
+
<ScreenContextProvider pathname={route}>
|
|
48
|
+
<Screen route={route} Components={Components} />
|
|
49
|
+
</ScreenContextProvider>
|
|
50
|
+
</PathnameContext.Provider>
|
|
51
|
+
</ScreenDataContext.Provider>
|
|
52
|
+
</FocusableGroup>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return <>{mainStack.map(renderScreen)}</>;
|
|
57
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StackNavigator } from "./StackNavigator";
|
|
@@ -68,6 +68,7 @@ type Props = {
|
|
|
68
68
|
mode?: VideoModalMode;
|
|
69
69
|
loadPipesData: (url: string, options: {}) => void;
|
|
70
70
|
disableCastAction?: boolean;
|
|
71
|
+
groupId?: string;
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
type ScreenStyles = ZappRiver["styles"] & {
|
|
@@ -123,6 +124,7 @@ const nativeStyles = {
|
|
|
123
124
|
playerScreen: {
|
|
124
125
|
flex: 1,
|
|
125
126
|
backgroundColor: "black",
|
|
127
|
+
overflow: "hidden",
|
|
126
128
|
},
|
|
127
129
|
playerWrapper: {
|
|
128
130
|
flex: 1,
|
|
@@ -200,6 +202,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
200
202
|
style: videoStyle,
|
|
201
203
|
mode = VideoModalMode.MAXIMIZED,
|
|
202
204
|
disableCastAction = false,
|
|
205
|
+
groupId,
|
|
203
206
|
} = props;
|
|
204
207
|
|
|
205
208
|
// Hooks
|
|
@@ -249,8 +252,8 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
249
252
|
return;
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
navigator.goBack();
|
|
253
255
|
showNavBar(true);
|
|
256
|
+
navigator.goBack();
|
|
254
257
|
}, [isModal, navigator.goBack, showNavBar]);
|
|
255
258
|
|
|
256
259
|
const playEntry = (entry) => navigator.replaceTop(entry, { mode });
|
|
@@ -629,6 +632,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
629
632
|
style={styles.focusableGroup}
|
|
630
633
|
preferredFocus
|
|
631
634
|
shouldUsePreferredFocus
|
|
635
|
+
groupId={groupId}
|
|
632
636
|
>
|
|
633
637
|
{/* Video player and components */}
|
|
634
638
|
<View
|
|
@@ -271,6 +271,12 @@ function ComponentsMapComponent(props: Props) {
|
|
|
271
271
|
|
|
272
272
|
if (y >= 0 && y + height <= contentHeight) {
|
|
273
273
|
scrollY.setValue(y);
|
|
274
|
+
} else {
|
|
275
|
+
if (y <= 0) {
|
|
276
|
+
scrollY.setValue(0);
|
|
277
|
+
} else if (y + height > contentHeight) {
|
|
278
|
+
scrollY.setValue(contentHeight - height);
|
|
279
|
+
}
|
|
274
280
|
}
|
|
275
281
|
}, []);
|
|
276
282
|
|
|
@@ -134,10 +134,10 @@ export const useGetProgressViewOffset = () => {
|
|
|
134
134
|
switch (contentPosition) {
|
|
135
135
|
case "on_bottom_status_bar":
|
|
136
136
|
// navbar height
|
|
137
|
-
return offset + height
|
|
137
|
+
return offset + height;
|
|
138
138
|
case "on_top_screen":
|
|
139
139
|
// navbar height + status bar height
|
|
140
|
-
return offset + height;
|
|
140
|
+
return offset + height + top;
|
|
141
141
|
default:
|
|
142
142
|
return offset;
|
|
143
143
|
}
|
|
@@ -7,14 +7,10 @@ import * as R from "ramda";
|
|
|
7
7
|
import {
|
|
8
8
|
useFeedLoader,
|
|
9
9
|
useLayoutVersion,
|
|
10
|
-
useNavigationPluginData,
|
|
11
10
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
12
11
|
import { GeneralContentScreen } from "../../GeneralContentScreen";
|
|
13
12
|
import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
|
|
14
13
|
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";
|
|
18
14
|
import {
|
|
19
15
|
useNavbarState,
|
|
20
16
|
useSetNavbarState,
|
|
@@ -51,8 +47,6 @@ export const River = (props: Props) => {
|
|
|
51
47
|
const { setTitle: setScreenTitle, setSummary: setScreenSummary } =
|
|
52
48
|
useSetNavbarState();
|
|
53
49
|
|
|
54
|
-
const navigationPluginData = useNavigationPluginData();
|
|
55
|
-
|
|
56
50
|
const rivers = useRivers();
|
|
57
51
|
const isV2 = useLayoutVersion({ isV2: true });
|
|
58
52
|
|
|
@@ -99,23 +93,6 @@ export const River = (props: Props) => {
|
|
|
99
93
|
}
|
|
100
94
|
}, [feedData, screenData, screenTitle, screenSummary]);
|
|
101
95
|
|
|
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
|
-
|
|
119
96
|
if (!river) {
|
|
120
97
|
utilsLogger.error({
|
|
121
98
|
message: `River ${screenData.id} not found!`,
|
|
@@ -14,7 +14,7 @@ type Props = { pathname: string; screenData: Record<string, any> };
|
|
|
14
14
|
* @param {Props}: see props above
|
|
15
15
|
*/
|
|
16
16
|
export function RouteManager(props: Props) {
|
|
17
|
-
const { pathname, screenData
|
|
17
|
+
const { pathname, screenData } = useRoute();
|
|
18
18
|
const pathAttributes = getPathAttributes({ pathname });
|
|
19
19
|
|
|
20
20
|
const { screenType, screenId } = R.last(pathAttributes);
|
|
@@ -25,7 +25,7 @@ export function RouteManager(props: Props) {
|
|
|
25
25
|
screenType={screenType}
|
|
26
26
|
screenId={screenId}
|
|
27
27
|
{...props}
|
|
28
|
-
screenData={
|
|
28
|
+
screenData={screenData}
|
|
29
29
|
/>
|
|
30
30
|
</TestId>
|
|
31
31
|
);
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { last } from "ramda";
|
|
3
|
+
import { View, StyleSheet } from "react-native";
|
|
4
|
+
import {
|
|
5
|
+
findMenuPlugin,
|
|
6
|
+
getNavigationProps,
|
|
7
|
+
getPathAttributes,
|
|
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 {
|
|
13
|
+
useNavbarState,
|
|
14
|
+
useScreenBackgroundColor,
|
|
15
|
+
} from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
16
|
+
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
17
|
+
|
|
18
|
+
import { NavBarContainer } from "../../Layout/TV/NavBarContainer";
|
|
19
|
+
import { ScreenResolver } from "../../ScreenResolver";
|
|
20
|
+
import { useInitialFocus, useRestoreFocus } from "./hooks";
|
|
21
|
+
import { isPlayerPlugin } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
22
|
+
|
|
23
|
+
const styles = StyleSheet.create({
|
|
24
|
+
container: {
|
|
25
|
+
position: "absolute",
|
|
26
|
+
top: 0,
|
|
27
|
+
left: 0,
|
|
28
|
+
right: 0,
|
|
29
|
+
bottom: 0,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
type Components = {
|
|
34
|
+
NavBar: React.ComponentType<any>;
|
|
35
|
+
Background: React.ComponentType<any>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type Props = {
|
|
39
|
+
route: string;
|
|
40
|
+
Components: Components;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getActiveRiver = ({
|
|
44
|
+
screenId,
|
|
45
|
+
screenType,
|
|
46
|
+
routeState,
|
|
47
|
+
rivers,
|
|
48
|
+
fallbackActiveRiver,
|
|
49
|
+
}) => {
|
|
50
|
+
if (rivers[screenId]) {
|
|
51
|
+
if (screenType === "hooks") {
|
|
52
|
+
const navigations = routeState?.state?.entry?.payload?.navigations;
|
|
53
|
+
|
|
54
|
+
if (navigations) {
|
|
55
|
+
return routeState.state.entry.payload;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return routeState?.state?.screen;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// screenId is not presented into rivers, use fallback instead
|
|
63
|
+
return fallbackActiveRiver;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getNavigations = ({
|
|
67
|
+
screenId,
|
|
68
|
+
screenType,
|
|
69
|
+
routeState,
|
|
70
|
+
rivers,
|
|
71
|
+
fallbackNavigations,
|
|
72
|
+
}) => {
|
|
73
|
+
if (rivers[screenId]) {
|
|
74
|
+
// river presents
|
|
75
|
+
if (screenType === "hooks") {
|
|
76
|
+
const navigations = routeState?.state?.entry?.payload?.navigations;
|
|
77
|
+
|
|
78
|
+
if (navigations) {
|
|
79
|
+
return navigations;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return routeState?.state?.screen?.navigations;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// screenId is not presented into rivers, use fallback instead
|
|
87
|
+
return fallbackNavigations;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const Screen = ({ route, Components }: Props) => {
|
|
91
|
+
if (isNilOrEmpty(route)) {
|
|
92
|
+
throw Error("Required props: route is missing");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const navigator = useNavigation();
|
|
96
|
+
const { plugins = [], rivers = [] } = usePickFromState(["plugins", "rivers"]);
|
|
97
|
+
|
|
98
|
+
const pathAttributes = getPathAttributes({ pathname: route });
|
|
99
|
+
const routeState = navigator.getStackForPathname(route);
|
|
100
|
+
|
|
101
|
+
const { screenType, screenId } = last(pathAttributes);
|
|
102
|
+
|
|
103
|
+
const backgroundColor = useScreenBackgroundColor(screenId);
|
|
104
|
+
|
|
105
|
+
useInitialFocus();
|
|
106
|
+
useRestoreFocus();
|
|
107
|
+
|
|
108
|
+
const activeRiver = getActiveRiver({
|
|
109
|
+
screenId,
|
|
110
|
+
screenType,
|
|
111
|
+
routeState,
|
|
112
|
+
rivers,
|
|
113
|
+
fallbackActiveRiver: navigator.activeRiver,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const navigationProps = getNavigationProps({
|
|
117
|
+
navigator: {
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
activeRiver,
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
screenTitle: routeState.state.entry?.title || routeState.screen?.name,
|
|
122
|
+
},
|
|
123
|
+
category: "menu",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const navigations = getNavigations({
|
|
127
|
+
screenId,
|
|
128
|
+
screenType,
|
|
129
|
+
routeState,
|
|
130
|
+
rivers,
|
|
131
|
+
fallbackNavigations: navigator.activeRiver.navigations,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const menuPlugin = findMenuPlugin(navigations, plugins);
|
|
135
|
+
|
|
136
|
+
const NavBar = (Components.NavBar ||
|
|
137
|
+
menuPlugin.module) as React.ComponentType<any>;
|
|
138
|
+
|
|
139
|
+
const isPlayer = isPlayerPlugin(routeState);
|
|
140
|
+
|
|
141
|
+
const { visible: isNavBarVisible } = useNavbarState();
|
|
142
|
+
|
|
143
|
+
const screenData =
|
|
144
|
+
screenType === "hooks" || isPlayer
|
|
145
|
+
? routeState?.state?.entry
|
|
146
|
+
: routeState?.state?.screen;
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<View style={[styles.container, { backgroundColor }]}>
|
|
150
|
+
<NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
|
|
151
|
+
<NavBar
|
|
152
|
+
isVisible={isNavBarVisible}
|
|
153
|
+
menuPlugin={menuPlugin}
|
|
154
|
+
navigationProps={navigationProps}
|
|
155
|
+
/>
|
|
156
|
+
</NavBarContainer>
|
|
157
|
+
<ScreenResolver
|
|
158
|
+
screenType={screenType}
|
|
159
|
+
screenId={screenId}
|
|
160
|
+
screenData={screenData}
|
|
161
|
+
groupId={route}
|
|
162
|
+
/>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
@@ -10,6 +10,8 @@ const mock_navBarVisibleFlag = true;
|
|
|
10
10
|
|
|
11
11
|
const mockIsNavBarVisible = jest.fn(() => mock_navBarVisibleFlag);
|
|
12
12
|
|
|
13
|
+
const mockIsOrientationCompatible = jest.fn(() => true);
|
|
14
|
+
|
|
13
15
|
jest.mock("react-native-safe-area-context", () => ({
|
|
14
16
|
useSafeAreaInsets: () => ({ top: 44 }),
|
|
15
17
|
}));
|
|
@@ -21,6 +23,7 @@ jest.mock("../../RouteManager", () => ({
|
|
|
21
23
|
jest.mock(
|
|
22
24
|
"@applicaster/zapp-react-native-utils/appUtils/orientationHelper",
|
|
23
25
|
() => ({
|
|
26
|
+
isOrientationCompatible: mockIsOrientationCompatible,
|
|
24
27
|
allowedOrientationsForScreen: jest.fn(),
|
|
25
28
|
getOrientation: jest.fn(),
|
|
26
29
|
getScreenOrientation: jest.fn(),
|