@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4054240460 → 12.0.0-alpha.4195481103
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 +4 -8
- 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 +84 -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/BorderContainerView/index.tsx +7 -2
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +6 -1
- package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
- package/Components/Navigator/StackNavigator.tsx +14 -5
- package/Components/Screen/TV/index.web.tsx +7 -17
- package/Components/Screen/index.tsx +1 -1
- package/Components/VideoLive/LiveImageManager.ts +10 -0
- package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +4 -10
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +8 -0
- package/Components/VideoModal/ModalAnimation/utils.ts +6 -4
- package/Components/VideoModal/PlayerWrapper.tsx +1 -1
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +32 -12
- 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/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
|
@@ -71,9 +71,9 @@ export class BaseFocusable<
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
getId(props?: { id: string; groupId: string }) {
|
|
74
|
-
const { id
|
|
74
|
+
const { id } = props || this.props;
|
|
75
75
|
|
|
76
|
-
return
|
|
76
|
+
return id;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
getRect() {
|
package/Components/Cell/Cell.tsx
CHANGED
|
@@ -11,10 +11,7 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
|
11
11
|
|
|
12
12
|
import { CellWithFocusable } from "./CellWithFocusable";
|
|
13
13
|
import { BaseFocusable } from "../BaseFocusable";
|
|
14
|
-
import {
|
|
15
|
-
isVizioPlatform,
|
|
16
|
-
TTSManager,
|
|
17
|
-
} from "@applicaster/zapp-react-native-utils/appUtils/platform/platformUtils";
|
|
14
|
+
import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
|
|
18
15
|
|
|
19
16
|
type Props = {
|
|
20
17
|
item: {
|
|
@@ -144,10 +141,9 @@ export class CellComponent extends React.Component<Props> {
|
|
|
144
141
|
) {
|
|
145
142
|
this.scrollVertically(focusable);
|
|
146
143
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
144
|
+
const accessibilityManager = AccessibilityManager.getInstance();
|
|
145
|
+
// Text from DSP should be localized already
|
|
146
|
+
accessibilityManager.readText({ text: title });
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
149
|
}
|
|
@@ -3,6 +3,8 @@ import * as React from "react";
|
|
|
3
3
|
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
4
4
|
import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
|
|
5
5
|
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
6
|
+
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
7
|
+
|
|
6
8
|
import { isAppleTV } from "../../Helpers/Platform";
|
|
7
9
|
import { useCellState } from "../MasterCell/utils";
|
|
8
10
|
|
|
@@ -54,7 +56,7 @@ type Props = {
|
|
|
54
56
|
skipFocusManagerRegistration?: boolean;
|
|
55
57
|
isFocusable?: boolean;
|
|
56
58
|
behavior: Behavior;
|
|
57
|
-
|
|
59
|
+
focused?: boolean;
|
|
58
60
|
};
|
|
59
61
|
|
|
60
62
|
export function CellWithFocusable(props: Props) {
|
|
@@ -70,7 +72,7 @@ export function CellWithFocusable(props: Props) {
|
|
|
70
72
|
skipFocusManagerRegistration,
|
|
71
73
|
isFocusable,
|
|
72
74
|
behavior,
|
|
73
|
-
|
|
75
|
+
focused,
|
|
74
76
|
} = props;
|
|
75
77
|
|
|
76
78
|
const isFocused = useCellFocusedState(
|
|
@@ -82,8 +84,7 @@ export function CellWithFocusable(props: Props) {
|
|
|
82
84
|
const state = useCellState({
|
|
83
85
|
id: item.id,
|
|
84
86
|
behavior,
|
|
85
|
-
focused: isFocused,
|
|
86
|
-
selected,
|
|
87
|
+
focused: isFocused || toBooleanWithDefaultFalse(focused),
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
const [focusedButtonId, setFocusedButtonId] = React.useState(undefined);
|
|
@@ -114,6 +115,7 @@ export function CellWithFocusable(props: Props) {
|
|
|
114
115
|
preferredFocus={preferredFocus}
|
|
115
116
|
skipFocusManagerRegistration={skipFocusManagerRegistration}
|
|
116
117
|
isFocusable={isFocusable}
|
|
118
|
+
focused={focused}
|
|
117
119
|
/>
|
|
118
120
|
);
|
|
119
121
|
}
|
|
@@ -226,13 +226,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
|
|
|
226
226
|
|
|
227
227
|
hasReceivedFocus() {}
|
|
228
228
|
|
|
229
|
-
scrollTo(
|
|
230
|
-
// Workaround for HL autoscroll/autoloop feature, we scroll manually on tvOS.
|
|
231
|
-
// Should be removed after refactoring autoscroll/autoloop feature
|
|
232
|
-
if (this.props.component?.component_type !== "horizontal_list_qb") {
|
|
233
|
-
this.props?.scrollToIndex?.(index);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
229
|
+
scrollTo() {}
|
|
236
230
|
|
|
237
231
|
render() {
|
|
238
232
|
const {
|
|
@@ -279,7 +273,9 @@ export class TvOSCellComponent extends React.Component<Props, State> {
|
|
|
279
273
|
index={index}
|
|
280
274
|
scrollTo={this.scrollTo}
|
|
281
275
|
preferredFocus={preferredFocus}
|
|
276
|
+
focused={this.props.focused}
|
|
282
277
|
behavior={behavior}
|
|
278
|
+
isFocusable={isFocusable}
|
|
283
279
|
/>
|
|
284
280
|
</View>
|
|
285
281
|
);
|
|
@@ -4,6 +4,7 @@ import * as R from "ramda";
|
|
|
4
4
|
import { BaseFocusable } from "../BaseFocusable";
|
|
5
5
|
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
6
6
|
import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
|
|
7
|
+
import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
9
10
|
initialFocus?: boolean;
|
|
@@ -23,7 +24,7 @@ type Props = {
|
|
|
23
24
|
style?: React.CSSProperties;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
class Focusable extends BaseFocusable<Props> {
|
|
27
28
|
isGroup: boolean;
|
|
28
29
|
mouse: boolean;
|
|
29
30
|
longPressTimeout = null;
|
|
@@ -198,3 +199,7 @@ export class Focusable extends BaseFocusable<Props> {
|
|
|
198
199
|
);
|
|
199
200
|
}
|
|
200
201
|
}
|
|
202
|
+
|
|
203
|
+
const FocusableWithFocusableContext = withFocusableContext(Focusable);
|
|
204
|
+
|
|
205
|
+
export { FocusableWithFocusableContext as Focusable };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
3
|
import { BaseFocusable } from "../BaseFocusable";
|
|
4
|
+
import { FocusableGroupContextProvider } from "../../Contexts/FocusableGroupContext";
|
|
5
|
+
import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
|
|
4
6
|
|
|
5
7
|
type Props = {
|
|
6
8
|
testID?: string;
|
|
@@ -29,7 +31,7 @@ type Props = {
|
|
|
29
31
|
excludeFromFocusSearching?: boolean;
|
|
30
32
|
};
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
class FocusableGroup extends BaseFocusable<Props> {
|
|
33
35
|
constructor(props) {
|
|
34
36
|
super(props);
|
|
35
37
|
this.isGroup = true;
|
|
@@ -82,8 +84,14 @@ export class FocusableGroup extends BaseFocusable<Props> {
|
|
|
82
84
|
style={style}
|
|
83
85
|
ref={this.ref}
|
|
84
86
|
>
|
|
85
|
-
{
|
|
87
|
+
<FocusableGroupContextProvider id={id}>
|
|
88
|
+
{children}
|
|
89
|
+
</FocusableGroupContextProvider>
|
|
86
90
|
</div>
|
|
87
91
|
);
|
|
88
92
|
}
|
|
89
93
|
}
|
|
94
|
+
|
|
95
|
+
const FocusableGroupWithFocusableContext = withFocusableContext(FocusableGroup);
|
|
96
|
+
|
|
97
|
+
export { FocusableGroupWithFocusableContext as FocusableGroup };
|
|
@@ -5,13 +5,30 @@ import * as R from "ramda";
|
|
|
5
5
|
import { ScreenLayoutContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenLayoutContext";
|
|
6
6
|
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
7
7
|
import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
useCurrentScreenData,
|
|
10
|
+
useNavbarState,
|
|
11
|
+
} from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
9
12
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
10
13
|
|
|
11
14
|
import { useScreenConfiguration } from "../../River/useScreenConfiguration";
|
|
12
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
getNavigationProps,
|
|
17
|
+
resolveNavigationPlugin,
|
|
18
|
+
routeIsPlayerScreen,
|
|
19
|
+
} from "@applicaster/zapp-react-native-utils/navigationUtils";
|
|
20
|
+
import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
21
|
+
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
22
|
+
import { NavBarContainer } from "./NavBarContainer";
|
|
23
|
+
|
|
24
|
+
type ComponentsExtraProps = {
|
|
25
|
+
Background?: Record<string, any>;
|
|
26
|
+
NavBar?: Record<string, any>;
|
|
27
|
+
};
|
|
13
28
|
|
|
14
29
|
type Props = {
|
|
30
|
+
ComponentsExtraProps?: ComponentsExtraProps;
|
|
31
|
+
NavBar: React.ComponentType<any>;
|
|
15
32
|
children: React.ReactNode;
|
|
16
33
|
};
|
|
17
34
|
|
|
@@ -30,9 +47,13 @@ const styles = StyleSheet.create({
|
|
|
30
47
|
container: { flex: 1, height: "100%", width: "100%" },
|
|
31
48
|
});
|
|
32
49
|
|
|
50
|
+
const isTVOS = isApplePlatform();
|
|
51
|
+
|
|
33
52
|
// eslint-disable-next-line react/display-name
|
|
34
53
|
export const ScreenContainer = React.memo(function ScreenContainer({
|
|
35
54
|
children,
|
|
55
|
+
ComponentsExtraProps,
|
|
56
|
+
NavBar,
|
|
36
57
|
}: Props) {
|
|
37
58
|
const { screenMarginBottom } = React.useContext(ScreenLayoutContext);
|
|
38
59
|
const { currentRoute, screenData } = useNavigation();
|
|
@@ -80,9 +101,67 @@ export const ScreenContainer = React.memo(function ScreenContainer({
|
|
|
80
101
|
[themeStyles]
|
|
81
102
|
);
|
|
82
103
|
|
|
104
|
+
const navigator = useNavigation();
|
|
105
|
+
const { activeRiver } = navigator;
|
|
106
|
+
const { title, visible } = useNavbarState();
|
|
107
|
+
|
|
108
|
+
const { plugins = [] } = usePickFromState([
|
|
109
|
+
"appState",
|
|
110
|
+
"remoteConfigurations",
|
|
111
|
+
"plugins",
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
const navigationProps = getNavigationProps({
|
|
115
|
+
navigator,
|
|
116
|
+
title,
|
|
117
|
+
category: "menu",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const menuPlugin = React.useMemo(
|
|
121
|
+
() =>
|
|
122
|
+
resolveNavigationPlugin({
|
|
123
|
+
category: "menu",
|
|
124
|
+
navigations: activeRiver?.navigations,
|
|
125
|
+
plugins,
|
|
126
|
+
}) as QuickBrickPlugin,
|
|
127
|
+
[]
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const NavBarComponent = React.useMemo(
|
|
131
|
+
() => NavBar || (menuPlugin.module as React.ComponentType<any>),
|
|
132
|
+
[]
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const [navBarReady, setNavBarReady] = React.useState(false);
|
|
136
|
+
|
|
137
|
+
const navBarContainer = (
|
|
138
|
+
<NavBarContainer isVisible={visible} onReady={setNavBarReady}>
|
|
139
|
+
<NavBarComponent
|
|
140
|
+
ref={ComponentsExtraProps?.NavBar?.ref}
|
|
141
|
+
isVisible={visible}
|
|
142
|
+
menuPlugin={menuPlugin}
|
|
143
|
+
navigationProps={navigationProps}
|
|
144
|
+
{...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
|
|
145
|
+
/>
|
|
146
|
+
</NavBarContainer>
|
|
147
|
+
);
|
|
148
|
+
|
|
83
149
|
return (
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
|
|
150
|
+
<>
|
|
151
|
+
{!isTVOS ? navBarContainer : null}
|
|
152
|
+
{navBarReady ? (
|
|
153
|
+
/**
|
|
154
|
+
Need to re-mount this component on each navigation event
|
|
155
|
+
It's a hack to work around the fact that we don't support
|
|
156
|
+
correct screen rendering in our navigator (shared currentRoute state)
|
|
157
|
+
**/
|
|
158
|
+
<View testID="screen-container" style={viewStyles}>
|
|
159
|
+
{children}
|
|
160
|
+
</View>
|
|
161
|
+
) : null}
|
|
162
|
+
{/** Render NavBarContainer over ScreenContainer to let tvOS native
|
|
163
|
+
focus manager works correctly to focus nav bar items first **/}
|
|
164
|
+
{isTVOS ? navBarContainer : null}
|
|
165
|
+
</>
|
|
87
166
|
);
|
|
88
167
|
});
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { View } from "react-native";
|
|
3
|
-
import { render } from "@testing-library/react-native";
|
|
3
|
+
import { render, waitFor } from "@testing-library/react-native";
|
|
4
|
+
import { WrappedWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
|
|
4
5
|
|
|
5
6
|
import { ScreenLayoutContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenLayoutContext";
|
|
6
7
|
|
|
7
8
|
const navigator_mock = {
|
|
8
9
|
currentRoute: "/river/A1234",
|
|
9
10
|
screenData: {},
|
|
11
|
+
activeRiver: {
|
|
12
|
+
navigations: {},
|
|
13
|
+
id: "A1234",
|
|
14
|
+
home: false,
|
|
15
|
+
},
|
|
10
16
|
};
|
|
11
17
|
|
|
18
|
+
jest.mock("@applicaster/zapp-react-native-utils/navigationUtils", () => ({
|
|
19
|
+
...jest.requireActual<any>(
|
|
20
|
+
"@applicaster/zapp-react-native-utils/navigationUtils"
|
|
21
|
+
),
|
|
22
|
+
resolveNavigationPlugin: () => ({}),
|
|
23
|
+
}));
|
|
24
|
+
|
|
12
25
|
jest.mock("@applicaster/quick-brick-core/App/ActionSetters", () => ({
|
|
13
26
|
useRiverInitialState: () => ({}),
|
|
14
27
|
}));
|
|
@@ -17,13 +30,13 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
|
|
|
17
30
|
useNavigation: () => navigator_mock,
|
|
18
31
|
}));
|
|
19
32
|
|
|
20
|
-
jest.mock("../../../River/useScreenConfiguration
|
|
33
|
+
jest.mock("../../../River/useScreenConfiguration", () => ({
|
|
21
34
|
useScreenConfiguration: () => ({}),
|
|
22
35
|
}));
|
|
23
36
|
|
|
24
|
-
jest.mock(
|
|
25
|
-
useCurrentScreenData
|
|
26
|
-
|
|
37
|
+
jest.mock(
|
|
38
|
+
"@applicaster/zapp-react-native-utils/reactHooks/screen/useCurrentScreenData"
|
|
39
|
+
);
|
|
27
40
|
|
|
28
41
|
jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
|
|
29
42
|
useTheme: () => ({}),
|
|
@@ -44,42 +57,39 @@ describe("ScreenContainer", () => {
|
|
|
44
57
|
const setScreenLayout = () => {};
|
|
45
58
|
|
|
46
59
|
const wrapper = ({ children }) => (
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
<WrappedWithProviders>
|
|
61
|
+
<ScreenLayoutContext.Provider
|
|
62
|
+
value={{ ...screenLayout, setScreenLayout }}
|
|
63
|
+
>
|
|
64
|
+
<ScreenContainer NavBar={View}>{children}</ScreenContainer>
|
|
65
|
+
</ScreenLayoutContext.Provider>
|
|
66
|
+
</WrappedWithProviders>
|
|
50
67
|
);
|
|
51
68
|
|
|
52
69
|
const TestComp = () => <View testID="test-component" />;
|
|
53
70
|
|
|
54
71
|
it("renders", () => {
|
|
55
|
-
const { toJSON } = render(
|
|
56
|
-
<ScreenContainer>
|
|
57
|
-
<TestComp />
|
|
58
|
-
</ScreenContainer>,
|
|
59
|
-
{ wrapper }
|
|
60
|
-
);
|
|
72
|
+
const { toJSON } = render(<TestComp />, { wrapper });
|
|
61
73
|
|
|
62
74
|
expect(toJSON()).toMatchSnapshot();
|
|
63
75
|
});
|
|
64
76
|
|
|
65
|
-
it("renders passed component", () => {
|
|
66
|
-
const { getByTestId } = render(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
);
|
|
77
|
+
it("renders passed component", async () => {
|
|
78
|
+
const { getByTestId } = render(<TestComp />, { wrapper });
|
|
79
|
+
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
return expect(getByTestId("screen-container")).toBeDefined();
|
|
82
|
+
});
|
|
72
83
|
|
|
73
84
|
expect(getByTestId("test-component")).toBeDefined();
|
|
74
85
|
});
|
|
75
86
|
|
|
76
|
-
it("sets margins provided by the theme", () => {
|
|
77
|
-
const { getByTestId } = render(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
);
|
|
87
|
+
it("sets margins provided by the theme", async () => {
|
|
88
|
+
const { getByTestId } = render(<TestComp />, { wrapper });
|
|
89
|
+
|
|
90
|
+
await waitFor(() => {
|
|
91
|
+
return expect(getByTestId("screen-container")).toBeDefined();
|
|
92
|
+
});
|
|
83
93
|
|
|
84
94
|
const screen = getByTestId("screen-container");
|
|
85
95
|
expect(screen.props.style).toMatchSnapshot();
|
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
3
|
exports[`ScreenContainer renders 1`] = `
|
|
4
|
-
<
|
|
4
|
+
<RNCSafeAreaProvider
|
|
5
|
+
onInsetsChange={[Function]}
|
|
5
6
|
style={
|
|
6
7
|
Array [
|
|
7
8
|
Object {
|
|
8
9
|
"flex": 1,
|
|
9
|
-
"height": "100%",
|
|
10
|
-
"width": "100%",
|
|
11
|
-
},
|
|
12
|
-
Object {
|
|
13
|
-
"backgroundColor": "transparent",
|
|
14
|
-
"marginBottom": 10,
|
|
15
|
-
"paddingBottom": 0,
|
|
16
|
-
"paddingTop": 0,
|
|
17
10
|
},
|
|
11
|
+
undefined,
|
|
18
12
|
]
|
|
19
13
|
}
|
|
20
|
-
testID="screen-container"
|
|
21
14
|
>
|
|
22
15
|
<View
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
style={
|
|
17
|
+
Array [
|
|
18
|
+
Object {
|
|
19
|
+
"position": "absolute",
|
|
20
|
+
"top": 0,
|
|
21
|
+
"width": 1920,
|
|
22
|
+
"zIndex": 10,
|
|
23
|
+
},
|
|
24
|
+
Object {
|
|
25
|
+
"maxHeight": 10,
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
testID="nav-bar-container"
|
|
30
|
+
>
|
|
31
|
+
<View
|
|
32
|
+
isVisible={true}
|
|
33
|
+
menuPlugin={Object {}}
|
|
34
|
+
navigationProps={null}
|
|
35
|
+
/>
|
|
36
|
+
</View>
|
|
37
|
+
</RNCSafeAreaProvider>
|
|
26
38
|
`;
|
|
27
39
|
|
|
28
40
|
exports[`ScreenContainer sets margins provided by the theme 1`] = `
|
|
@@ -21,10 +21,11 @@ const mockStore = configureStore()({
|
|
|
21
21
|
describe("Layout TV", () => {
|
|
22
22
|
const navigator = {
|
|
23
23
|
currentRoute: "/river/A1234",
|
|
24
|
+
canGoBack: jest.fn(),
|
|
24
25
|
mainStack: [
|
|
25
26
|
{
|
|
26
27
|
route: "/river/A1234",
|
|
27
|
-
state:
|
|
28
|
+
state: { screen: {} },
|
|
28
29
|
},
|
|
29
30
|
],
|
|
30
31
|
};
|
|
@@ -4,22 +4,14 @@ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
|
4
4
|
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
5
5
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
getNavigationProps,
|
|
9
|
-
resolveNavigationPlugin,
|
|
10
|
-
routeIsPlayerScreen,
|
|
11
|
-
} from "@applicaster/zapp-react-native-utils/navigationUtils";
|
|
12
|
-
|
|
13
7
|
import { getBackgroundImageUrl } from "../utils";
|
|
14
8
|
import { LayoutContainer } from "./LayoutContainer";
|
|
15
|
-
import { NavBarContainer } from "./NavBarContainer";
|
|
16
9
|
import { ScreenContainer } from "./ScreenContainer";
|
|
17
10
|
|
|
18
11
|
import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
|
|
19
|
-
import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
20
12
|
import { PathnameContext } from "../../../Contexts/PathnameContext";
|
|
21
|
-
import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
22
13
|
import { ScreenDataContext } from "../../../Contexts/ScreenDataContext";
|
|
14
|
+
import { ScreenContextProvider } from "../../../Contexts/ScreenContext";
|
|
23
15
|
|
|
24
16
|
type Components = {
|
|
25
17
|
NavBar: React.ComponentType<any>;
|
|
@@ -40,20 +32,9 @@ type Props = {
|
|
|
40
32
|
const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
|
|
41
33
|
const navigator = useNavigation();
|
|
42
34
|
const theme = useTheme();
|
|
43
|
-
const { title, visible } = useNavbarState();
|
|
44
|
-
const { activeRiver, currentRoute } = navigator;
|
|
45
|
-
|
|
46
|
-
const {
|
|
47
|
-
appState: { appReady = false } = {},
|
|
48
|
-
remoteConfigurations,
|
|
49
|
-
plugins = [],
|
|
50
|
-
} = usePickFromState(["appState", "remoteConfigurations", "plugins"]);
|
|
51
35
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
title,
|
|
55
|
-
category: "menu",
|
|
56
|
-
});
|
|
36
|
+
const { appState: { appReady = false } = {}, remoteConfigurations } =
|
|
37
|
+
usePickFromState(["appState", "remoteConfigurations", "plugins"]);
|
|
57
38
|
|
|
58
39
|
const backgroundColor = React.useMemo(() => theme.app_background_color, []);
|
|
59
40
|
|
|
@@ -62,37 +43,6 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
|
|
|
62
43
|
[remoteConfigurations]
|
|
63
44
|
);
|
|
64
45
|
|
|
65
|
-
const menuPlugin = React.useMemo(
|
|
66
|
-
() =>
|
|
67
|
-
resolveNavigationPlugin({
|
|
68
|
-
category: "menu",
|
|
69
|
-
navigations: activeRiver?.navigations,
|
|
70
|
-
plugins,
|
|
71
|
-
}) as QuickBrickPlugin,
|
|
72
|
-
[]
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const NavBar = React.useMemo(
|
|
76
|
-
() => Components.NavBar || (menuPlugin.module as React.ComponentType<any>),
|
|
77
|
-
[]
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
const [navBarReady, setNavBarReady] = React.useState(false);
|
|
81
|
-
|
|
82
|
-
const navBarContainer = (
|
|
83
|
-
<NavBarContainer isVisible={visible} onReady={setNavBarReady}>
|
|
84
|
-
<NavBar
|
|
85
|
-
ref={ComponentsExtraProps?.NavBar?.ref}
|
|
86
|
-
isVisible={visible && !routeIsPlayerScreen(currentRoute)}
|
|
87
|
-
menuPlugin={menuPlugin}
|
|
88
|
-
navigationProps={navigationProps}
|
|
89
|
-
{...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
|
|
90
|
-
/>
|
|
91
|
-
</NavBarContainer>
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const isTVOS = isApplePlatform();
|
|
95
|
-
|
|
96
46
|
if (!appReady) {
|
|
97
47
|
return null;
|
|
98
48
|
}
|
|
@@ -107,18 +57,14 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
|
|
|
107
57
|
>
|
|
108
58
|
<ScreenDataContext.Provider value={navigator.data}>
|
|
109
59
|
<PathnameContext.Provider value={navigator.currentRoute}>
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
) : null}
|
|
119
|
-
{/** Render NavBarContainer over ScreenContainer to let tvOS native
|
|
120
|
-
focus manager works correctly to focus nav bar items first **/}
|
|
121
|
-
{isTVOS ? navBarContainer : null}
|
|
60
|
+
<ScreenContextProvider pathname={navigator.currentRoute}>
|
|
61
|
+
<ScreenContainer
|
|
62
|
+
ComponentsExtraProps={ComponentsExtraProps}
|
|
63
|
+
NavBar={Components.NavBar}
|
|
64
|
+
>
|
|
65
|
+
{children}
|
|
66
|
+
</ScreenContainer>
|
|
67
|
+
</ScreenContextProvider>
|
|
122
68
|
</PathnameContext.Provider>
|
|
123
69
|
</ScreenDataContext.Provider>
|
|
124
70
|
</Components.Background>
|
|
@@ -22,6 +22,10 @@ const styles = StyleSheet.create({
|
|
|
22
22
|
borderWidth: 0,
|
|
23
23
|
borderColor: COLOR_TRANSPARENT,
|
|
24
24
|
},
|
|
25
|
+
border: {
|
|
26
|
+
...StyleSheet.absoluteFillObject,
|
|
27
|
+
zIndex: 1,
|
|
28
|
+
},
|
|
25
29
|
});
|
|
26
30
|
|
|
27
31
|
const getBorderPadding = (borderPosition: BorderPosition, borderWidth) => {
|
|
@@ -87,8 +91,9 @@ const Border = ({
|
|
|
87
91
|
|
|
88
92
|
return (
|
|
89
93
|
<View
|
|
94
|
+
pointerEvents="none"
|
|
90
95
|
style={{
|
|
91
|
-
...
|
|
96
|
+
...styles.border,
|
|
92
97
|
right: -containerPadding - paddingRight,
|
|
93
98
|
bottom: -containerPadding - paddingBottom,
|
|
94
99
|
left: -containerPadding - paddingLeft,
|
|
@@ -130,8 +135,8 @@ export const BorderContainerView = (props: Props) => {
|
|
|
130
135
|
{...props}
|
|
131
136
|
style={[style, styles.container, borderStyleForMissingBorder]}
|
|
132
137
|
>
|
|
133
|
-
{children}
|
|
134
138
|
<Border style={style} padding={padding} borderPosition={borderPosition} />
|
|
139
|
+
{children}
|
|
135
140
|
</View>
|
|
136
141
|
);
|
|
137
142
|
};
|
|
@@ -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
|
|
|
@@ -7,6 +7,10 @@ import type {
|
|
|
7
7
|
ContainerProps,
|
|
8
8
|
ContainerChildren,
|
|
9
9
|
} from "./types";
|
|
10
|
+
import {
|
|
11
|
+
useIsRTL,
|
|
12
|
+
applyRTLStylesIfNeeded,
|
|
13
|
+
} from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
10
14
|
|
|
11
15
|
const HorizontalSeparator = ({ width }: HorizontalSeparatorProps) => (
|
|
12
16
|
<View style={{ width }} />
|
|
@@ -17,6 +21,7 @@ export function ButtonContainerView({
|
|
|
17
21
|
children,
|
|
18
22
|
...otherProps
|
|
19
23
|
}: ContainerProps) {
|
|
24
|
+
const isRTL = useIsRTL();
|
|
20
25
|
const horizontalGutter = R.pathOr(0, ["horizontalGutter"], otherProps);
|
|
21
26
|
|
|
22
27
|
const filteredChildren = useFilterChildren<ContainerChildren>(children);
|
|
@@ -26,7 +31,7 @@ export function ButtonContainerView({
|
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
return (
|
|
29
|
-
<View style={style}>
|
|
34
|
+
<View style={applyRTLStylesIfNeeded(style, isRTL)}>
|
|
30
35
|
{insertBetween(
|
|
31
36
|
(index) => (
|
|
32
37
|
<HorizontalSeparator
|
|
@@ -9,4 +9,13 @@ describe("insertBetween", () => {
|
|
|
9
9
|
|
|
10
10
|
expect(result).toEqual([1, "|", 2, "|", 3]);
|
|
11
11
|
});
|
|
12
|
+
|
|
13
|
+
it("insertBetween", () => {
|
|
14
|
+
const separator = (index) => `separator:${index}`;
|
|
15
|
+
const items = [1, 2, 3];
|
|
16
|
+
|
|
17
|
+
const result = insertBetween(separator, items);
|
|
18
|
+
|
|
19
|
+
expect(result).toEqual([1, "separator:0", 2, "separator:1", 3]);
|
|
20
|
+
});
|
|
12
21
|
});
|
|
@@ -8,6 +8,8 @@ import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/
|
|
|
8
8
|
import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
|
|
9
9
|
|
|
10
10
|
import { Screen } from "../Screen/TV/index.web";
|
|
11
|
+
import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
12
|
+
import { ScreenContextProvider } from "../../Contexts/ScreenContext";
|
|
11
13
|
|
|
12
14
|
type Components = {
|
|
13
15
|
NavBar: React.ComponentType<any>;
|
|
@@ -19,25 +21,32 @@ type Props = {
|
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export const StackNavigator = ({ Components }: Props) => {
|
|
22
|
-
const { mainStack
|
|
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>
|
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
getNavigationProps,
|
|
@@ -9,12 +9,16 @@ import {
|
|
|
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,
|
|
@@ -149,9 +141,8 @@ export const Screen = ({ route, Components }: Props) => {
|
|
|
149
141
|
menuPlugin.module) as React.ComponentType<any>;
|
|
150
142
|
|
|
151
143
|
const isPlayer = isPlayerPlugin(routeState);
|
|
152
|
-
const isLogin = isLoginPlugin(routeState);
|
|
153
144
|
|
|
154
|
-
const isNavBarVisible =
|
|
145
|
+
const { visible: isNavBarVisible } = useNavbarState();
|
|
155
146
|
|
|
156
147
|
const screenData =
|
|
157
148
|
screenType === "hooks" || isPlayer
|
|
@@ -162,7 +153,6 @@ export const Screen = ({ route, Components }: Props) => {
|
|
|
162
153
|
<View style={[styles.container, { backgroundColor }]}>
|
|
163
154
|
<NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
|
|
164
155
|
<NavBar
|
|
165
|
-
groupId={route}
|
|
166
156
|
isVisible={isNavBarVisible}
|
|
167
157
|
menuPlugin={menuPlugin}
|
|
168
158
|
navigationProps={navigationProps}
|
|
@@ -525,6 +525,16 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
525
525
|
LiveImageManager.instance.onLiveImageError(this, error);
|
|
526
526
|
};
|
|
527
527
|
|
|
528
|
+
onVideoError = (error: Error) => {
|
|
529
|
+
log_error(
|
|
530
|
+
`onVideoError: live-image, fail to play, switching back to image. error: ${
|
|
531
|
+
error?.message
|
|
532
|
+
}, ${playerInfo(this.player)}`
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
LiveImageManager.instance.onLiveImageError(this, error);
|
|
536
|
+
};
|
|
537
|
+
|
|
528
538
|
onVideoEnd = (_event) => {
|
|
529
539
|
log_info(
|
|
530
540
|
`onVideoEnd: live-image, video completed, switching back to image, ${playerInfo(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useRef, useState } from "react";
|
|
2
|
-
import { Animated, View
|
|
2
|
+
import { Animated, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
5
|
|
|
@@ -33,10 +33,6 @@ type Props = {
|
|
|
33
33
|
additionalData?: { [key: string]: any };
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
const styles = StyleSheet.create({
|
|
37
|
-
flex: { flex: 1 },
|
|
38
|
-
});
|
|
39
|
-
|
|
40
36
|
export const AnimationView = ({
|
|
41
37
|
animationType,
|
|
42
38
|
style,
|
|
@@ -54,18 +50,17 @@ export const AnimationView = ({
|
|
|
54
50
|
tabletLandscapePlayerTopPosition,
|
|
55
51
|
setTabletLandscapePlayerTopPosition,
|
|
56
52
|
modalSnapPoints,
|
|
53
|
+
startComponentsAnimationDistance,
|
|
57
54
|
} = useModalAnimationContext();
|
|
58
55
|
|
|
59
56
|
const isRTL = useIsRTL();
|
|
60
57
|
const isAppActive = useAppState(true);
|
|
61
58
|
|
|
62
59
|
const {
|
|
63
|
-
height: screenHeight,
|
|
64
60
|
width: screenWidth,
|
|
65
61
|
deviceInfo: { isTablet, isTabletLandscape },
|
|
66
62
|
} = useDimensions("window", { deviceInfo: true });
|
|
67
63
|
|
|
68
|
-
const startAnimationDistance = Math.round((screenHeight * 60) / 100);
|
|
69
64
|
const { videoModalState } = useNavigation();
|
|
70
65
|
const bottomTabBarHeight = useGetBottomTabBarHeight();
|
|
71
66
|
const minimisedWidth = defaultAspectRatioWidth(minimisedHeight);
|
|
@@ -346,7 +341,7 @@ export const AnimationView = ({
|
|
|
346
341
|
animatedValue,
|
|
347
342
|
calculationData,
|
|
348
343
|
modalSnapPoint: modalSnapPoints[1],
|
|
349
|
-
|
|
344
|
+
startComponentsAnimationDistance,
|
|
350
345
|
startComponentsAnimation,
|
|
351
346
|
setStartComponentsAnimation,
|
|
352
347
|
getAnimationValue,
|
|
@@ -371,7 +366,7 @@ export const AnimationView = ({
|
|
|
371
366
|
animatedValue,
|
|
372
367
|
calculationData,
|
|
373
368
|
modalSnapPoint: modalSnapPoints[1],
|
|
374
|
-
|
|
369
|
+
startComponentsAnimationDistance,
|
|
375
370
|
startComponentsAnimation,
|
|
376
371
|
setStartComponentsAnimation,
|
|
377
372
|
getAnimationValue,
|
|
@@ -393,7 +388,6 @@ export const AnimationView = ({
|
|
|
393
388
|
ref={animationComponentRef}
|
|
394
389
|
style={[
|
|
395
390
|
...preparedStyles,
|
|
396
|
-
additionalData.saveArea ? styles.flex : null,
|
|
397
391
|
(videoModalState.mode === "MAXIMIZED" ||
|
|
398
392
|
videoModalState.mode === "MINIMIZED" ||
|
|
399
393
|
(videoModalState.mode === "PIP" && isAppActive)) &&
|
|
@@ -39,6 +39,7 @@ export type ModalAnimationContextT = {
|
|
|
39
39
|
setLastSnap: (value: number) => void;
|
|
40
40
|
tabletLandscapePlayerTopPosition: number;
|
|
41
41
|
setTabletLandscapePlayerTopPosition: (value: number) => void;
|
|
42
|
+
startComponentsAnimationDistance: number;
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
const ReactContext = React.createContext<ModalAnimationContextT>({
|
|
@@ -62,6 +63,7 @@ const ReactContext = React.createContext<ModalAnimationContextT>({
|
|
|
62
63
|
setLastSnap: () => null,
|
|
63
64
|
tabletLandscapePlayerTopPosition: 0,
|
|
64
65
|
setTabletLandscapePlayerTopPosition: () => null,
|
|
66
|
+
startComponentsAnimationDistance: 0,
|
|
65
67
|
});
|
|
66
68
|
|
|
67
69
|
export function useModalAnimationContext() {
|
|
@@ -97,6 +99,11 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
|
|
|
97
99
|
const { bottom: bottomSafeArea } = useSafeAreaInsets();
|
|
98
100
|
const bottomTabBarHeight = useGetBottomTabBarHeight();
|
|
99
101
|
|
|
102
|
+
const startComponentsAnimationDistance = React.useMemo(
|
|
103
|
+
() => Math.round((height * 60) / 100),
|
|
104
|
+
[height]
|
|
105
|
+
);
|
|
106
|
+
|
|
100
107
|
/*
|
|
101
108
|
If bottomTabBarHeight is equal 0 it means an app does not use bottomTabBar.
|
|
102
109
|
Because of this we need to minus bottom SafeArea offset.
|
|
@@ -134,6 +141,7 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
|
|
|
134
141
|
setLastSnap,
|
|
135
142
|
tabletLandscapePlayerTopPosition,
|
|
136
143
|
setTabletLandscapePlayerTopPosition,
|
|
144
|
+
startComponentsAnimationDistance,
|
|
137
145
|
}}
|
|
138
146
|
>
|
|
139
147
|
{children}
|
|
@@ -233,7 +233,7 @@ export const gestureListenerHelper = (data) => {
|
|
|
233
233
|
animatedValue,
|
|
234
234
|
calculationData,
|
|
235
235
|
modalSnapPoint,
|
|
236
|
-
|
|
236
|
+
startComponentsAnimationDistance,
|
|
237
237
|
startComponentsAnimation,
|
|
238
238
|
setStartComponentsAnimation,
|
|
239
239
|
getAnimationValue,
|
|
@@ -243,7 +243,7 @@ export const gestureListenerHelper = (data) => {
|
|
|
243
243
|
if (listenerValue <= 0) {
|
|
244
244
|
if (
|
|
245
245
|
preparedValue >= 0 &&
|
|
246
|
-
preparedValue <= modalSnapPoint -
|
|
246
|
+
preparedValue <= modalSnapPoint - startComponentsAnimationDistance
|
|
247
247
|
) {
|
|
248
248
|
!startComponentsAnimation && setStartComponentsAnimation(true);
|
|
249
249
|
|
|
@@ -263,9 +263,11 @@ export const gestureListenerHelper = (data) => {
|
|
|
263
263
|
animatedValue.setValue(value);
|
|
264
264
|
}
|
|
265
265
|
} else {
|
|
266
|
-
if (preparedValue >=
|
|
266
|
+
if (preparedValue >= startComponentsAnimationDistance) {
|
|
267
267
|
!startComponentsAnimation && setStartComponentsAnimation(true);
|
|
268
|
-
const value = Math.round(
|
|
268
|
+
const value = Math.round(
|
|
269
|
+
preparedValue - startComponentsAnimationDistance
|
|
270
|
+
);
|
|
269
271
|
|
|
270
272
|
animatedValue.setValue(
|
|
271
273
|
calculateAnimationValue(animationType, value, calculationData)
|
|
@@ -197,7 +197,7 @@ export const PlayerWrapper = (props: Props) => {
|
|
|
197
197
|
<AnimationComponent
|
|
198
198
|
animationType={ComponentAnimationType.moveUpComponent}
|
|
199
199
|
additionalData={{ saveArea: true }}
|
|
200
|
-
style={directionStyles(isTabletLandscape)}
|
|
200
|
+
style={[directionStyles(isTabletLandscape), defaultStyles.flex]}
|
|
201
201
|
>
|
|
202
202
|
<PathnameContext.Provider value={pathname}>
|
|
203
203
|
<ScreenContextProvider pathname={pathname}>
|
|
@@ -42,9 +42,14 @@ exports[`PlayerWrapper renders inline 1`] = `
|
|
|
42
42
|
}
|
|
43
43
|
animationType="moveUpComponent"
|
|
44
44
|
style={
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
Array [
|
|
46
|
+
Object {
|
|
47
|
+
"flexDirection": "column",
|
|
48
|
+
},
|
|
49
|
+
Object {
|
|
50
|
+
"flex": 1,
|
|
51
|
+
},
|
|
52
|
+
]
|
|
48
53
|
}
|
|
49
54
|
>
|
|
50
55
|
<View
|
|
@@ -120,9 +125,14 @@ exports[`PlayerWrapper renders inline and docked 1`] = `
|
|
|
120
125
|
}
|
|
121
126
|
animationType="moveUpComponent"
|
|
122
127
|
style={
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
Array [
|
|
129
|
+
Object {
|
|
130
|
+
"flexDirection": "column",
|
|
131
|
+
},
|
|
132
|
+
Object {
|
|
133
|
+
"flex": 1,
|
|
134
|
+
},
|
|
135
|
+
]
|
|
126
136
|
}
|
|
127
137
|
>
|
|
128
138
|
<View
|
|
@@ -198,9 +208,14 @@ exports[`PlayerWrapper renders inline on tablet in landscape orientation 1`] = `
|
|
|
198
208
|
}
|
|
199
209
|
animationType="moveUpComponent"
|
|
200
210
|
style={
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
211
|
+
Array [
|
|
212
|
+
Object {
|
|
213
|
+
"flexDirection": "row",
|
|
214
|
+
},
|
|
215
|
+
Object {
|
|
216
|
+
"flex": 1,
|
|
217
|
+
},
|
|
218
|
+
]
|
|
204
219
|
}
|
|
205
220
|
>
|
|
206
221
|
<View
|
|
@@ -292,9 +307,14 @@ exports[`PlayerWrapper renders properly 1`] = `
|
|
|
292
307
|
}
|
|
293
308
|
animationType="moveUpComponent"
|
|
294
309
|
style={
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
310
|
+
Array [
|
|
311
|
+
Object {
|
|
312
|
+
"flexDirection": "column",
|
|
313
|
+
},
|
|
314
|
+
Object {
|
|
315
|
+
"flex": 1,
|
|
316
|
+
},
|
|
317
|
+
]
|
|
298
318
|
}
|
|
299
319
|
>
|
|
300
320
|
<View
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { FocusableGroupContext } from "./FocusableGroupContext";
|
|
3
|
+
|
|
4
|
+
type ProviderArgs = {
|
|
5
|
+
id: string;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const FocusableGroupContextProvider = ({
|
|
10
|
+
id,
|
|
11
|
+
children,
|
|
12
|
+
}: ProviderArgs) => (
|
|
13
|
+
<FocusableGroupContext.Provider value={id}>
|
|
14
|
+
{children}
|
|
15
|
+
</FocusableGroupContext.Provider>
|
|
16
|
+
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React, { useContext } from "react";
|
|
2
|
+
import { Text } from "react-native";
|
|
3
|
+
import { render } from "@testing-library/react-native";
|
|
4
|
+
import { FocusableGroupContextProvider, FocusableGroupContext } from "../";
|
|
5
|
+
|
|
6
|
+
const MockComponent = () => {
|
|
7
|
+
const focusableContext = useContext(FocusableGroupContext) || "default";
|
|
8
|
+
|
|
9
|
+
return <Text testID={focusableContext} />;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe("FocusableGroupContextProvider", () => {
|
|
13
|
+
it("should provide focusable context to the wrapped component", () => {
|
|
14
|
+
const { getByTestId } = render(
|
|
15
|
+
<FocusableGroupContextProvider id="testId">
|
|
16
|
+
<MockComponent />
|
|
17
|
+
</FocusableGroupContextProvider>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(getByTestId("testId")).toBeTruthy();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "react-native";
|
|
3
|
+
import { render } from "@testing-library/react-native";
|
|
4
|
+
import { withFocusableContext } from "../withFocusableContext";
|
|
5
|
+
import { FocusableGroupContext } from "../FocusableGroupContext";
|
|
6
|
+
|
|
7
|
+
const MockComponent = ({ groupId }: { groupId: string }) => (
|
|
8
|
+
<Text testID={groupId} />
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const WrappedComponent = withFocusableContext(MockComponent);
|
|
12
|
+
|
|
13
|
+
describe("withFocusableContext HOC", () => {
|
|
14
|
+
it("should provide focusable context to the wrapped component", () => {
|
|
15
|
+
const { getByTestId } = render(<WrappedComponent />, {
|
|
16
|
+
// eslint-disable-next-line react/display-name
|
|
17
|
+
wrapper: ({ children }: { children: React.ReactElement }) => (
|
|
18
|
+
<FocusableGroupContext.Provider value="test">
|
|
19
|
+
{children}
|
|
20
|
+
</FocusableGroupContext.Provider>
|
|
21
|
+
),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(getByTestId("test")).toBeTruthy();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FocusableGroupContext } from "./FocusableGroupContext";
|
|
3
|
+
import { FocusableGroupContextProvider } from "./FocusableGroupContextProvider";
|
|
4
|
+
|
|
5
|
+
export { FocusableGroupContext, FocusableGroupContextProvider };
|
|
6
|
+
|
|
7
|
+
export const useGroupId = () => React.useContext(FocusableGroupContext);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FocusableGroupContext } from "./FocusableGroupContext";
|
|
3
|
+
|
|
4
|
+
export const withFocusableContext = (Component) => {
|
|
5
|
+
// eslint-disable-next-line react/display-name
|
|
6
|
+
return ({ groupId, ...props }: Record<string, any>) => (
|
|
7
|
+
<FocusableGroupContext.Consumer>
|
|
8
|
+
{(groupIdContext: string) => {
|
|
9
|
+
// eslint-disable-next-line react/display-name
|
|
10
|
+
const propsGroupId = groupId || null;
|
|
11
|
+
const providedGroupId = propsGroupId || groupIdContext;
|
|
12
|
+
|
|
13
|
+
return <Component {...props} groupId={providedGroupId} />;
|
|
14
|
+
}}
|
|
15
|
+
</FocusableGroupContext.Consumer>
|
|
16
|
+
);
|
|
17
|
+
};
|
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.4195481103",
|
|
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.4195481103",
|
|
38
|
+
"@applicaster/zapp-react-native-bridge": "12.0.0-alpha.4195481103",
|
|
39
|
+
"@applicaster/zapp-react-native-redux": "12.0.0-alpha.4195481103",
|
|
40
|
+
"@applicaster/zapp-react-native-utils": "12.0.0-alpha.4195481103",
|
|
41
41
|
"promise": "^8.3.0",
|
|
42
42
|
"react-router-native": "^5.1.2",
|
|
43
43
|
"url": "^0.11.0",
|
|
File without changes
|
|
File without changes
|