@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4536343143 → 12.0.0-alpha.4584840011
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 +2 -2
- package/Components/Cell/Cell.tsx +26 -5
- package/Components/Cell/CellWithFocusable.tsx +6 -4
- package/Components/Cell/TvOSCellComponent.tsx +3 -7
- package/Components/Focusable/Focusable.tsx +6 -1
- package/Components/FocusableGroup/index.tsx +10 -2
- package/Components/Layout/TV/ScreenContainer.tsx +79 -5
- 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__/index.test.tsx +2 -1
- package/Components/Layout/TV/index.tsx +11 -65
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -1
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +7 -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.tsx +6 -1
- 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 +17 -30
- package/Components/Navigator/StackNavigator.tsx +14 -5
- package/Components/PlayerContainer/PlayerContainer.tsx +2 -0
- package/Components/Screen/TV/index.web.tsx +9 -23
- 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 +6 -11
- package/Components/Transitioner/Scene.tsx +12 -11
- package/Components/Transitioner/Transitioner.tsx +32 -44
- package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +8 -4
- package/Components/VideoLive/LiveImageManager.ts +10 -0
- 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/PlayerWrapper.tsx +2 -2
- 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/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/ScreenContext/index.tsx +0 -6
- package/package.json +5 -5
- /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
- /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
|
@@ -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 =
|
|
39
|
+
const focused =
|
|
40
|
+
focusableId === focusedButtonId ||
|
|
41
|
+
toBooleanWithDefaultFalse(otherProps.focused);
|
|
39
42
|
|
|
40
43
|
const additionalStyles = focused ? focusedStyles : normalStyles;
|
|
41
44
|
|
|
@@ -108,6 +108,26 @@ const prepareEntry = (entry) => {
|
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
const previewPlayback =
|
|
112
|
+
entry.extensions?.["brightcove"]?.["preview_playback"];
|
|
113
|
+
|
|
114
|
+
if (previewPlayback) {
|
|
115
|
+
return {
|
|
116
|
+
...entry,
|
|
117
|
+
extensions: {
|
|
118
|
+
...entry.extensions,
|
|
119
|
+
brightcove: {
|
|
120
|
+
...entry?.extensions?.["brightcove"],
|
|
121
|
+
video_id: previewPlayback,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (entry.extensions?.["brightcove"]?.["video_id"]) {
|
|
128
|
+
return entry;
|
|
129
|
+
}
|
|
130
|
+
|
|
111
131
|
const src = entry.content?.src;
|
|
112
132
|
|
|
113
133
|
if (!src) {
|
|
@@ -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
|
|
@@ -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,7 @@ 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";
|
|
12
14
|
|
|
13
15
|
const hasElementSpecificViewType = (viewType) => (element) => {
|
|
14
16
|
if (R.isNil(element)) {
|
|
@@ -47,44 +49,29 @@ export const hasElementsSpecificViewType = (viewType) => (elements) => {
|
|
|
47
49
|
return R.any(hasElementSpecificViewType(viewType))(elements);
|
|
48
50
|
};
|
|
49
51
|
|
|
50
|
-
const isColorOrEmpty = (value) =>
|
|
51
|
-
value === null ||
|
|
52
|
-
value === undefined ||
|
|
53
|
-
value === "" ||
|
|
54
|
-
value.startsWith("#") ||
|
|
55
|
-
value.startsWith("rgba") ||
|
|
56
|
-
value === "transparent";
|
|
57
|
-
|
|
58
52
|
function resolveColorForProp(entry, style, colorProp) {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
const colorFromProp = style[colorProp];
|
|
54
|
+
const nestedEntryValue = R.path(colorFromProp.split("."), entry);
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
nestedEntryValue === undefined &&
|
|
58
|
+
!validateColor(colorFromProp.replace(".00", "")) // https://github.com/dreamyguy/validate-color/issues/44
|
|
59
|
+
) {
|
|
60
|
+
logWarning(colorFromProp, style, entry, nestedEntryValue);
|
|
63
61
|
}
|
|
64
62
|
|
|
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
|
-
}
|
|
63
|
+
const colorValue = getColorFromData({
|
|
64
|
+
data: entry,
|
|
65
|
+
valueFromLayout: colorFromProp,
|
|
66
|
+
});
|
|
77
67
|
|
|
78
|
-
if (
|
|
79
|
-
logWarning(
|
|
68
|
+
if (!colorValue) {
|
|
69
|
+
logWarning(colorProp, style, entry, colorValue);
|
|
80
70
|
|
|
81
71
|
return style;
|
|
82
72
|
}
|
|
83
73
|
|
|
84
|
-
|
|
85
|
-
style[colorProp] = nestedEntryValue;
|
|
86
|
-
|
|
87
|
-
return style;
|
|
74
|
+
return { ...style, [colorProp]: colorValue };
|
|
88
75
|
}
|
|
89
76
|
|
|
90
77
|
export function resolveColor(entry, style) {
|
|
@@ -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
|
|
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={
|
|
41
|
+
preferredFocus={isLast(index, mainStack.length)}
|
|
35
42
|
excludeFromFocusSearching
|
|
36
|
-
key={
|
|
43
|
+
key={routeId}
|
|
37
44
|
>
|
|
38
45
|
<ScreenDataContext.Provider value={state}>
|
|
39
46
|
<PathnameContext.Provider value={route}>
|
|
40
|
-
<
|
|
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,6 +253,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
showNavBar(true);
|
|
256
|
+
navigator.goBack();
|
|
255
257
|
}, [isModal, navigator.goBack, showNavBar]);
|
|
256
258
|
|
|
257
259
|
const playEntry = (entry) => navigator.replaceTop(entry, { mode });
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { last
|
|
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 {
|
|
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 =
|
|
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 =
|
|
141
|
+
const { visible: isNavBarVisible } = useNavbarState();
|
|
155
142
|
|
|
156
143
|
const screenData =
|
|
157
144
|
screenType === "hooks" || isPlayer
|
|
@@ -162,7 +149,6 @@ export const Screen = ({ route, Components }: Props) => {
|
|
|
162
149
|
<View style={[styles.container, { backgroundColor }]}>
|
|
163
150
|
<NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
|
|
164
151
|
<NavBar
|
|
165
|
-
groupId={route}
|
|
166
152
|
isVisible={isNavBarVisible}
|
|
167
153
|
menuPlugin={menuPlugin}
|
|
168
154
|
navigationProps={navigationProps}
|
|
@@ -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(),
|
|
@@ -3,14 +3,18 @@ import React from "react";
|
|
|
3
3
|
import * as R from "ramda";
|
|
4
4
|
import { View } from "react-native";
|
|
5
5
|
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
6
|
+
import {
|
|
7
|
+
isOrientationCompatible,
|
|
8
|
+
useGetScreenOrientation,
|
|
9
|
+
} from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
|
|
6
10
|
|
|
7
11
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
8
12
|
import { getComponentModule } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
9
13
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
shouldNavBarDisplayMenu,
|
|
15
|
+
getNavBarProps,
|
|
16
|
+
getScreenId,
|
|
12
17
|
} from "@applicaster/zapp-react-native-utils/navigationUtils";
|
|
13
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
14
18
|
import {
|
|
15
19
|
useDimensions,
|
|
16
20
|
useRoute,
|
|
@@ -21,20 +25,16 @@ import {
|
|
|
21
25
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
22
26
|
import { getNavigationPluginModule } from "@applicaster/zapp-react-native-app/App/Layout/layoutHelpers";
|
|
23
27
|
|
|
24
|
-
import { ScreenContextProvider } from "../../Contexts/ScreenContext";
|
|
25
28
|
import { RouteManager } from "../RouteManager";
|
|
26
29
|
import { useScreenConfiguration } from "../River/useScreenConfiguration";
|
|
27
30
|
import { useScreenOrientationHandler } from "./orientationHandler";
|
|
28
31
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
: "screen_id" in screenData
|
|
36
|
-
? screenData.screen_id
|
|
37
|
-
: 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
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
function colorIsNotTransparent(color) {
|
|
@@ -45,9 +45,7 @@ function colorIsNotTransparent(color) {
|
|
|
45
45
|
R.replace("rgba(", "")
|
|
46
46
|
)(color);
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return alpha > 0;
|
|
48
|
+
return Number(layers?.[3]) > 0;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
function isValidColor(string) {
|
|
@@ -67,9 +65,12 @@ export function Screen(_props: Props) {
|
|
|
67
65
|
// Gets the data for the current screen configuration
|
|
68
66
|
const currentScreenData = useCurrentScreenData();
|
|
69
67
|
const { backgroundColor } = useScreenConfiguration(currentScreenData.id);
|
|
70
|
-
const insets = useSafeAreaInsets();
|
|
71
68
|
|
|
72
|
-
const {
|
|
69
|
+
const {
|
|
70
|
+
width: screenWidth,
|
|
71
|
+
height,
|
|
72
|
+
deviceInfo,
|
|
73
|
+
} = useDimensions("screen", {
|
|
73
74
|
fullDimensions: true,
|
|
74
75
|
});
|
|
75
76
|
|
|
@@ -81,30 +82,19 @@ export function Screen(_props: Props) {
|
|
|
81
82
|
|
|
82
83
|
const { title } = useNavbarState();
|
|
83
84
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (props) {
|
|
99
|
-
return {
|
|
100
|
-
...props,
|
|
101
|
-
id: pathname,
|
|
102
|
-
pathname: pathname,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return null;
|
|
107
|
-
}, [currentRiver, pathname]);
|
|
85
|
+
const isTablet = deviceInfo?.isTablet;
|
|
86
|
+
|
|
87
|
+
const { appData } = usePickFromState(["appData"]);
|
|
88
|
+
const isTabletPortrait = appData?.isTabletPortrait;
|
|
89
|
+
|
|
90
|
+
const layoutData = { isTablet, isTabletPortrait, width: screenWidth, height };
|
|
91
|
+
|
|
92
|
+
const hasMenu = shouldNavBarDisplayMenu(currentRiver, plugins);
|
|
93
|
+
|
|
94
|
+
const navBarProps = React.useMemo<MobileNavBarPluginProps | null>(
|
|
95
|
+
getNavBarProps(currentRiver, pathname, title),
|
|
96
|
+
[currentRiver, pathname]
|
|
97
|
+
);
|
|
108
98
|
|
|
109
99
|
const NavBar = React.useMemo(
|
|
110
100
|
() =>
|
|
@@ -126,16 +116,12 @@ export function Screen(_props: Props) {
|
|
|
126
116
|
|
|
127
117
|
const style = React.useMemo(
|
|
128
118
|
() => ({
|
|
129
|
-
|
|
119
|
+
...screenStyles,
|
|
130
120
|
backgroundColor: isValidColor(backgroundColor)
|
|
131
121
|
? backgroundColor
|
|
132
122
|
: theme.app_background_color,
|
|
133
|
-
paddingTop: 0,
|
|
134
|
-
// paddingBottom: insets.bottom, // Should be applied directly by UI plugin
|
|
135
|
-
// paddingLeft: insets.left, // Should be applied directly by UI plugin
|
|
136
|
-
// paddingRight: insets.right, // Should be applied directly by UI plugin
|
|
137
123
|
}),
|
|
138
|
-
[
|
|
124
|
+
[theme.app_background_color, backgroundColor]
|
|
139
125
|
);
|
|
140
126
|
|
|
141
127
|
const targetScreenData =
|
|
@@ -143,16 +129,27 @@ export function Screen(_props: Props) {
|
|
|
143
129
|
|
|
144
130
|
useScreenOrientationHandler({ screenData: targetScreenData });
|
|
145
131
|
|
|
132
|
+
const orientation = useGetScreenOrientation(targetScreenData);
|
|
133
|
+
|
|
134
|
+
// We prevent rendering of the screen until UI is actually rotated to screen desired orientation.
|
|
135
|
+
// This saves unnecessary re-renders and user will not see distorted aspect screen.
|
|
136
|
+
if (
|
|
137
|
+
!isOrientationCompatible({
|
|
138
|
+
orientation,
|
|
139
|
+
layoutData,
|
|
140
|
+
})
|
|
141
|
+
) {
|
|
142
|
+
return <View style={style} />;
|
|
143
|
+
}
|
|
144
|
+
|
|
146
145
|
return (
|
|
147
|
-
<
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
</View>
|
|
156
|
-
</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>
|
|
157
154
|
);
|
|
158
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,6 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
3
|
-
import { useDispatch } from "react-redux";
|
|
2
|
+
import { path, prop } from "ramda";
|
|
4
3
|
import {
|
|
5
4
|
findPluginByType,
|
|
6
5
|
findPluginByIdentifier,
|
|
@@ -15,7 +14,6 @@ import { componentsLogger } from "../../Helpers/logger";
|
|
|
15
14
|
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
16
15
|
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
17
16
|
import { useScreenAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks";
|
|
18
|
-
import { setAppStateHasFinishedTransitioning } from "@applicaster/zapp-react-native-redux/appState";
|
|
19
17
|
|
|
20
18
|
const logger = componentsLogger.addSubsystem("ScreenResolver");
|
|
21
19
|
|
|
@@ -54,11 +52,8 @@ export function ScreenResolverComponent(props: Props) {
|
|
|
54
52
|
|
|
55
53
|
const [, setScreenContext] = ZappPipesScreenContext.useZappPipesContext();
|
|
56
54
|
|
|
57
|
-
const dispatch = useDispatch();
|
|
58
|
-
|
|
59
55
|
React.useEffect(() => {
|
|
60
56
|
setScreenContext(rivers[screenId]);
|
|
61
|
-
dispatch(setAppStateHasFinishedTransitioning());
|
|
62
57
|
}, [screenId]);
|
|
63
58
|
|
|
64
59
|
const ScreenPlugin =
|
|
@@ -100,14 +95,14 @@ export function ScreenResolverComponent(props: Props) {
|
|
|
100
95
|
}
|
|
101
96
|
|
|
102
97
|
const ScreenComponent =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
path(["module", "Component"], ScreenPlugin) ||
|
|
99
|
+
prop("module", ScreenPlugin) ||
|
|
100
|
+
prop("Component", ScreenPlugin) ||
|
|
106
101
|
ScreenPlugin;
|
|
107
102
|
|
|
108
103
|
const configuration =
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
prop("configuration", ScreenPlugin) ||
|
|
105
|
+
prop("__plugin_configuration", ScreenComponent);
|
|
111
106
|
|
|
112
107
|
if (ScreenComponent) {
|
|
113
108
|
return (
|
|
@@ -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
|
-
|
|
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
|
-
{
|
|
69
|
+
<ScreenContextProvider pathname={pathname}>
|
|
70
|
+
{children}
|
|
71
|
+
</ScreenContextProvider>
|
|
71
72
|
</PathnameContext.Provider>
|
|
72
73
|
</ScreenDataContext.Provider>
|
|
73
74
|
{isAnimating && <Animated.View style={overlayStyle} />}
|