@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4300857220 → 12.0.0-alpha.4337072244
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Components/BaseFocusable/index.tsx +1 -1
- package/Components/Cell/Cell.tsx +5 -5
- package/Components/Cell/CellWithFocusable.tsx +17 -5
- package/Components/FocusableGroup/index.tsx +1 -0
- package/Components/HandlePlayable/HandlePlayable.tsx +3 -0
- package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -50
- package/Components/Layout/TV/__tests__/index.test.tsx +18 -74
- package/Components/Layout/TV/index.tsx +43 -102
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
- package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
- package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +4 -1
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
- package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
- package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
- package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
- package/Components/PlayerContainer/PlayerContainer.tsx +4 -1
- package/Components/River/ComponentsMap/ComponentsMap.tsx +18 -17
- package/Components/River/RefreshControl.tsx +55 -1
- package/Components/River/TV/River.tsx +0 -23
- package/Components/Screen/TV/hooks/index.ts +3 -0
- package/Components/Screen/TV/hooks/useInitialFocus.ts +67 -0
- package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
- package/Components/Screen/TV/index.web.tsx +174 -0
- package/Components/ScreenResolver/index.tsx +3 -1
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +13 -2
- package/Components/VideoModal/PlayerWrapper.tsx +2 -2
- package/Components/VideoModal/VideoModal.tsx +19 -19
- package/Contexts/ScreenContext/index.tsx +2 -2
- package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
- package/events/index.ts +1 -0
- package/package.json +5 -5
- package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
|
@@ -61,7 +61,10 @@ export function ButtonContainerView({
|
|
|
61
61
|
<View style={applyRTLStylesIfNeeded(style, isRTL)}>
|
|
62
62
|
{insertBetween(
|
|
63
63
|
(index) => (
|
|
64
|
-
<HorizontalSeparator
|
|
64
|
+
<HorizontalSeparator
|
|
65
|
+
key={`separator_${index}`}
|
|
66
|
+
width={horizontalGutter}
|
|
67
|
+
/>
|
|
65
68
|
),
|
|
66
69
|
cloneElementsWithIds(buttonIds, filteredChildren)
|
|
67
70
|
)}
|
|
@@ -29,7 +29,10 @@ export function ButtonContainerView({
|
|
|
29
29
|
<View style={style}>
|
|
30
30
|
{insertBetween(
|
|
31
31
|
(index) => (
|
|
32
|
-
<HorizontalSeparator
|
|
32
|
+
<HorizontalSeparator
|
|
33
|
+
key={`separator_${index}`}
|
|
34
|
+
width={horizontalGutter}
|
|
35
|
+
/>
|
|
33
36
|
),
|
|
34
37
|
filteredChildren
|
|
35
38
|
)}
|
|
@@ -51,9 +51,7 @@ export const CollapsibleTextContainer = ({
|
|
|
51
51
|
func: (data) => {
|
|
52
52
|
return getColorFromData({
|
|
53
53
|
data,
|
|
54
|
-
|
|
55
|
-
key: "background_color",
|
|
56
|
-
defaultColor: additionalStyles.backgroundColor,
|
|
54
|
+
valueFromLayout: additionalStyles.backgroundColor,
|
|
57
55
|
});
|
|
58
56
|
},
|
|
59
57
|
propName: "backgroundColor",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import { MeasurementPortalContext } from "./MeasurementsPortal";
|
|
3
4
|
|
|
4
5
|
type Props = {
|
|
5
6
|
Component: React.ElementType;
|
|
@@ -19,13 +20,16 @@ const _MeasurementsPortal = (
|
|
|
19
20
|
{ Component, onLayout, componentProps, containerStyle }: Props,
|
|
20
21
|
ref
|
|
21
22
|
) => (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
// hack for cell to determine if cell is in measurement mode
|
|
24
|
+
<MeasurementPortalContext.Provider value={{}}>
|
|
25
|
+
<View
|
|
26
|
+
onLayout={onLayout}
|
|
27
|
+
style={[styles.container, containerStyle]}
|
|
28
|
+
ref={ref}
|
|
29
|
+
>
|
|
30
|
+
<Component {...componentProps} />
|
|
31
|
+
</View>
|
|
32
|
+
</MeasurementPortalContext.Provider>
|
|
29
33
|
);
|
|
30
34
|
|
|
31
35
|
export const MeasurementsPortal = React.forwardRef(_MeasurementsPortal);
|
|
@@ -35,7 +35,16 @@ const MeasurementsPortal = ({ Component, onLayout, componentProps }: Props) => {
|
|
|
35
35
|
);
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
type MeasurementPortalContextType = {
|
|
39
|
+
measureComponent?: (
|
|
40
|
+
comp: React.ElementType,
|
|
41
|
+
props: Record<any, any>
|
|
42
|
+
) => Promise<any>;
|
|
43
|
+
measuringInProgress?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const MeasurementPortalContext =
|
|
47
|
+
React.createContext<null | MeasurementPortalContextType>(null);
|
|
39
48
|
|
|
40
49
|
const MeasurementsPortalContextProvider = ({ children }) => {
|
|
41
50
|
const Component = React.useRef(View);
|
|
@@ -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"] & {
|
|
@@ -200,6 +201,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
200
201
|
style: videoStyle,
|
|
201
202
|
mode = VideoModalMode.MAXIMIZED,
|
|
202
203
|
disableCastAction = false,
|
|
204
|
+
groupId,
|
|
203
205
|
} = props;
|
|
204
206
|
|
|
205
207
|
// Hooks
|
|
@@ -249,7 +251,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
249
251
|
return;
|
|
250
252
|
}
|
|
251
253
|
|
|
252
|
-
navigator.goBack();
|
|
253
254
|
showNavBar(true);
|
|
254
255
|
}, [isModal, navigator.goBack, showNavBar]);
|
|
255
256
|
|
|
@@ -629,6 +630,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
629
630
|
style={styles.focusableGroup}
|
|
630
631
|
preferredFocus
|
|
631
632
|
shouldUsePreferredFocus
|
|
633
|
+
groupId={groupId}
|
|
632
634
|
>
|
|
633
635
|
{/* Video player and components */}
|
|
634
636
|
<View
|
|
@@ -671,6 +673,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
671
673
|
setNextVideoPreloadThresholdPercentage={
|
|
672
674
|
setNextVideoPreloadThresholdPercentage
|
|
673
675
|
}
|
|
676
|
+
startComponentsAnimation={startComponentsAnimation}
|
|
674
677
|
>
|
|
675
678
|
{renderApplePlayer(applePlayerProps)}
|
|
676
679
|
</Player>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
-
import { View, StyleSheet, Platform,
|
|
3
|
+
import { View, StyleSheet, Platform, FlatList } from "react-native";
|
|
4
4
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
5
5
|
import { RiverItem } from "../RiverItem";
|
|
6
6
|
import { RiverFooter } from "../RiverFooter";
|
|
@@ -217,7 +217,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
217
217
|
|
|
218
218
|
const { height: headerHeight, scrollState } = navBarStore.getState();
|
|
219
219
|
|
|
220
|
-
const offsetY = nativeEvent.contentOffset.y;
|
|
220
|
+
const offsetY = Math.max(0, nativeEvent.contentOffset.y);
|
|
221
221
|
|
|
222
222
|
if (
|
|
223
223
|
!(scrollState === 0 || scrollState === -headerHeight) &&
|
|
@@ -240,7 +240,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
240
240
|
const _onScrollEndDrag = React.useCallback(({ nativeEvent }) => {
|
|
241
241
|
const { height: headerHeight, scrollState } = navBarStore.getState();
|
|
242
242
|
|
|
243
|
-
const offsetY = nativeEvent.contentOffset.y;
|
|
243
|
+
const offsetY = Math.max(0, nativeEvent.contentOffset.y);
|
|
244
244
|
|
|
245
245
|
if (
|
|
246
246
|
!(scrollState === 0 || scrollState === -headerHeight) &&
|
|
@@ -260,6 +260,20 @@ function ComponentsMapComponent(props: Props) {
|
|
|
260
260
|
}
|
|
261
261
|
}, []);
|
|
262
262
|
|
|
263
|
+
const onScroll = React.useCallback((event) => {
|
|
264
|
+
const {
|
|
265
|
+
nativeEvent: {
|
|
266
|
+
contentOffset: { y },
|
|
267
|
+
layoutMeasurement: { height },
|
|
268
|
+
contentSize: { height: contentHeight },
|
|
269
|
+
},
|
|
270
|
+
} = event;
|
|
271
|
+
|
|
272
|
+
if (y >= 0 && y + height <= contentHeight) {
|
|
273
|
+
scrollY.setValue(y);
|
|
274
|
+
}
|
|
275
|
+
}, []);
|
|
276
|
+
|
|
263
277
|
if (shouldShowLoadingError) {
|
|
264
278
|
return <RiverError />;
|
|
265
279
|
}
|
|
@@ -298,20 +312,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
298
312
|
ListFooterComponent={renderFooter}
|
|
299
313
|
refreshControl={refreshControl}
|
|
300
314
|
onScrollBeginDrag={onScrollBeginDrag}
|
|
301
|
-
onScroll={
|
|
302
|
-
[
|
|
303
|
-
{
|
|
304
|
-
nativeEvent: {
|
|
305
|
-
contentOffset: {
|
|
306
|
-
y: scrollY,
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
{
|
|
312
|
-
useNativeDriver: false,
|
|
313
|
-
}
|
|
314
|
-
)}
|
|
315
|
+
onScroll={onScroll}
|
|
315
316
|
onMomentumScrollEnd={_onMomentumScrollEnd}
|
|
316
317
|
onScrollEndDrag={_onScrollEndDrag}
|
|
317
318
|
scrollEventThrottle={16}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
RefreshControl as RNRefreshControl,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
} from "react-native";
|
|
3
7
|
import * as R from "ramda";
|
|
4
8
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
5
9
|
import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
@@ -8,10 +12,30 @@ import { useSendAnalyticsEventWithFunction } from "@applicaster/zapp-react-nativ
|
|
|
8
12
|
import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
9
13
|
import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
|
|
10
14
|
import { useDispatch } from "react-redux";
|
|
15
|
+
import { useShallow } from "zustand/react/shallow";
|
|
16
|
+
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
17
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
11
18
|
|
|
12
19
|
const BRIGHTNESS_THRESHOLD = 160;
|
|
13
20
|
const ABOVE_DEFAULT_COLOR = "gray";
|
|
14
21
|
const BELOW_DEFAULT_COLOR = "white";
|
|
22
|
+
const BASE_INDICATOR_HEIGHT = 40;
|
|
23
|
+
const BASE_INDICATOR_OFFSET = 16;
|
|
24
|
+
|
|
25
|
+
/** Return spacing required to position indicator below the navbar bottom line (Android only) */
|
|
26
|
+
const getRefreshControlOffset = () => {
|
|
27
|
+
return Platform.OS === "ios"
|
|
28
|
+
? 0
|
|
29
|
+
: BASE_INDICATOR_HEIGHT + BASE_INDICATOR_OFFSET;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const styles = StyleSheet.create({
|
|
33
|
+
// eslint-disable-next-line react-native/no-color-literals
|
|
34
|
+
control: {
|
|
35
|
+
zIndex: 1,
|
|
36
|
+
backgroundColor: "transparent",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
15
39
|
|
|
16
40
|
const getBrightness = (RGBcolor) => {
|
|
17
41
|
const RGBvalues = RGBcolor.substring(
|
|
@@ -91,6 +115,34 @@ export const usePullToRefresh = (
|
|
|
91
115
|
};
|
|
92
116
|
};
|
|
93
117
|
|
|
118
|
+
/** Returns the offset for the progress view of the RefreshControl component
|
|
119
|
+
* based on navbar content position */
|
|
120
|
+
export const useGetProgressViewOffset = () => {
|
|
121
|
+
const { _navBarStore } = useScreenContextV2();
|
|
122
|
+
const { top } = useSafeAreaInsets();
|
|
123
|
+
|
|
124
|
+
const offset = getRefreshControlOffset();
|
|
125
|
+
|
|
126
|
+
const { height, contentPosition } = _navBarStore(
|
|
127
|
+
useShallow(({ contentPosition, height }) => ({ contentPosition, height }))
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (height === 0) {
|
|
131
|
+
return offset;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
switch (contentPosition) {
|
|
135
|
+
case "on_bottom_status_bar":
|
|
136
|
+
// navbar height
|
|
137
|
+
return offset + height - top;
|
|
138
|
+
case "on_top_screen":
|
|
139
|
+
// navbar height + status bar height
|
|
140
|
+
return offset + height;
|
|
141
|
+
default:
|
|
142
|
+
return offset;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
94
146
|
export function RefreshControl(props: {
|
|
95
147
|
pullToRefreshPipesV1RefreshingStateUpdater?: (refreshing: boolean) => void;
|
|
96
148
|
refreshingPipesV1?: boolean;
|
|
@@ -211,6 +263,7 @@ export function RefreshControl(props: {
|
|
|
211
263
|
refreshing={refreshing}
|
|
212
264
|
onRefresh={onRefreshHandler}
|
|
213
265
|
colors={[preparedIndicatorColor]}
|
|
266
|
+
progressViewOffset={useGetProgressViewOffset()}
|
|
214
267
|
progressBackgroundColor={preparedIndicatorBackgroundColor}
|
|
215
268
|
// @ts-ignore in RN68 this `size` became string
|
|
216
269
|
size={indicatorSize}
|
|
@@ -222,6 +275,7 @@ export function RefreshControl(props: {
|
|
|
222
275
|
: null
|
|
223
276
|
}
|
|
224
277
|
titleColor={preparedTitleUnderIndicatorColor}
|
|
278
|
+
style={styles.control}
|
|
225
279
|
/>
|
|
226
280
|
);
|
|
227
281
|
}
|
|
@@ -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!`,
|
|
@@ -0,0 +1,67 @@
|
|
|
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 { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
9
|
+
|
|
10
|
+
type Return =
|
|
11
|
+
| {
|
|
12
|
+
onContent: true;
|
|
13
|
+
onMenu: false;
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
onContent: false;
|
|
17
|
+
onMenu: true;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getInitialFocus = (focusOnContent, isNavBarVisible): Return => {
|
|
21
|
+
if (focusOnContent) {
|
|
22
|
+
return {
|
|
23
|
+
onContent: true,
|
|
24
|
+
onMenu: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!focusOnContent && isNavBarVisible) {
|
|
29
|
+
// put focus on navbar
|
|
30
|
+
return {
|
|
31
|
+
onContent: false,
|
|
32
|
+
onMenu: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// we don't have navbar, put focus on content anyway
|
|
37
|
+
return {
|
|
38
|
+
onContent: true,
|
|
39
|
+
onMenu: false,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const useInitialFocus = (): void => {
|
|
44
|
+
const navigationPluginData = useNavigationPluginData();
|
|
45
|
+
|
|
46
|
+
const focusOnContent = toBooleanWithDefaultFalse(
|
|
47
|
+
navigationPluginData?.rules?.focus_on_content
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const isNavBarVisible = useIsNavBarVisible();
|
|
51
|
+
|
|
52
|
+
const { pathname: currentRoute } = useRoute();
|
|
53
|
+
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const initialFocus = getInitialFocus(focusOnContent, isNavBarVisible);
|
|
56
|
+
|
|
57
|
+
if (initialFocus.onContent) {
|
|
58
|
+
focusManager.setFocusOnContent(currentRoute);
|
|
59
|
+
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (initialFocus.onMenu) {
|
|
64
|
+
focusManager.setFocusOnMenu(currentRoute);
|
|
65
|
+
}
|
|
66
|
+
}, []);
|
|
67
|
+
};
|
|
@@ -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,174 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { last, pathOr } from "ramda";
|
|
3
|
+
import { View, StyleSheet } from "react-native";
|
|
4
|
+
import {
|
|
5
|
+
getNavigationProps,
|
|
6
|
+
getPathAttributes,
|
|
7
|
+
resolveNavigationPlugin,
|
|
8
|
+
} from "@applicaster/zapp-react-native-utils/navigationUtils";
|
|
9
|
+
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
10
|
+
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
11
|
+
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
12
|
+
import { useScreenBackgroundColor } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
13
|
+
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
14
|
+
|
|
15
|
+
import { NavBarContainer } from "../../Layout/TV/NavBarContainer";
|
|
16
|
+
import { ScreenResolver } from "../../ScreenResolver";
|
|
17
|
+
import { useInitialFocus, useRestoreFocus } from "./hooks";
|
|
18
|
+
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
container: {
|
|
21
|
+
position: "absolute",
|
|
22
|
+
top: 0,
|
|
23
|
+
left: 0,
|
|
24
|
+
right: 0,
|
|
25
|
+
bottom: 0,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
type Components = {
|
|
30
|
+
NavBar: React.ComponentType<any>;
|
|
31
|
+
Background: React.ComponentType<any>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type Props = {
|
|
35
|
+
route: string;
|
|
36
|
+
Components: Components;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const isPlayer = (routeState): boolean => {
|
|
40
|
+
const pluginType = pathOr(
|
|
41
|
+
"unknown_plugin_type",
|
|
42
|
+
["state", "screen", "plugin_type"],
|
|
43
|
+
routeState
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return pluginType === "player";
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getActiveRiver = ({
|
|
50
|
+
screenId,
|
|
51
|
+
screenType,
|
|
52
|
+
routeState,
|
|
53
|
+
rivers,
|
|
54
|
+
fallbackActiveRiver,
|
|
55
|
+
}) => {
|
|
56
|
+
if (rivers[screenId]) {
|
|
57
|
+
if (screenType === "hooks") {
|
|
58
|
+
const navigations = routeState?.state?.entry?.payload?.navigations;
|
|
59
|
+
|
|
60
|
+
if (navigations) {
|
|
61
|
+
return routeState.state.entry.payload;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return routeState?.state?.screen;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// screenId is not presented into rivers, use fallback instead
|
|
69
|
+
return fallbackActiveRiver;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const getNavigations = ({
|
|
73
|
+
screenId,
|
|
74
|
+
screenType,
|
|
75
|
+
routeState,
|
|
76
|
+
rivers,
|
|
77
|
+
fallbackNavigations,
|
|
78
|
+
}) => {
|
|
79
|
+
if (rivers[screenId]) {
|
|
80
|
+
// river presents
|
|
81
|
+
if (screenType === "hooks") {
|
|
82
|
+
const navigations = routeState?.state?.entry?.payload?.navigations;
|
|
83
|
+
|
|
84
|
+
if (navigations) {
|
|
85
|
+
return navigations;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return routeState?.state?.screen?.navigations;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// screenId is not presented into rivers, use fallback instead
|
|
93
|
+
return fallbackNavigations;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const Screen = ({ route, Components }: Props) => {
|
|
97
|
+
if (isNilOrEmpty(route)) {
|
|
98
|
+
throw Error("Required props: route is missing");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const navigator = useNavigation();
|
|
102
|
+
const { plugins = [], rivers = [] } = usePickFromState(["plugins", "rivers"]);
|
|
103
|
+
|
|
104
|
+
const pathAttributes = getPathAttributes({ pathname: route });
|
|
105
|
+
const routeState = navigator.getStackForPathname(route);
|
|
106
|
+
|
|
107
|
+
const { screenType, screenId } = last(pathAttributes);
|
|
108
|
+
|
|
109
|
+
const backgroundColor = useScreenBackgroundColor(screenId);
|
|
110
|
+
|
|
111
|
+
useInitialFocus();
|
|
112
|
+
useRestoreFocus();
|
|
113
|
+
|
|
114
|
+
const activeRiver = getActiveRiver({
|
|
115
|
+
screenId,
|
|
116
|
+
screenType,
|
|
117
|
+
routeState,
|
|
118
|
+
rivers,
|
|
119
|
+
fallbackActiveRiver: navigator.activeRiver,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const navigationProps = getNavigationProps({
|
|
123
|
+
navigator: {
|
|
124
|
+
// @ts-ignore
|
|
125
|
+
activeRiver,
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
screenTitle: routeState.state.entry?.title || routeState.screen?.name,
|
|
128
|
+
},
|
|
129
|
+
category: "menu",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const navigations = getNavigations({
|
|
133
|
+
screenId,
|
|
134
|
+
screenType,
|
|
135
|
+
routeState,
|
|
136
|
+
rivers,
|
|
137
|
+
fallbackNavigations: navigator.activeRiver.navigations,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const menuPlugin = resolveNavigationPlugin({
|
|
141
|
+
category: "menu",
|
|
142
|
+
navigations,
|
|
143
|
+
plugins,
|
|
144
|
+
}) as QuickBrickPlugin;
|
|
145
|
+
|
|
146
|
+
const NavBar = (Components.NavBar ||
|
|
147
|
+
menuPlugin.module) as React.ComponentType<any>;
|
|
148
|
+
|
|
149
|
+
const isNavBarVisible = !isPlayer(routeState);
|
|
150
|
+
|
|
151
|
+
const screenData =
|
|
152
|
+
screenType === "hooks"
|
|
153
|
+
? routeState?.state?.entry
|
|
154
|
+
: routeState?.state?.screen;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<View style={[styles.container, { backgroundColor }]}>
|
|
158
|
+
<NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
|
|
159
|
+
<NavBar
|
|
160
|
+
groupId={route}
|
|
161
|
+
isVisible={isNavBarVisible}
|
|
162
|
+
menuPlugin={menuPlugin}
|
|
163
|
+
navigationProps={navigationProps}
|
|
164
|
+
/>
|
|
165
|
+
</NavBarContainer>
|
|
166
|
+
<ScreenResolver
|
|
167
|
+
screenType={screenType}
|
|
168
|
+
screenId={screenId}
|
|
169
|
+
screenData={screenData}
|
|
170
|
+
groupId={route}
|
|
171
|
+
/>
|
|
172
|
+
</View>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
@@ -32,12 +32,13 @@ type Props = {
|
|
|
32
32
|
nextFocusLeft?: React.Ref<any>;
|
|
33
33
|
nextFocusUp?: React.Ref<any>;
|
|
34
34
|
};
|
|
35
|
+
groupId?: string;
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
export function ScreenResolverComponent(props: Props) {
|
|
38
39
|
useScreenAnalytics(props);
|
|
39
40
|
|
|
40
|
-
const { screenType, screenId, screenData } = props;
|
|
41
|
+
const { screenType, screenId, screenData, groupId } = props;
|
|
41
42
|
|
|
42
43
|
const { hookPlugin } = screenData || {};
|
|
43
44
|
|
|
@@ -81,6 +82,7 @@ export function ScreenResolverComponent(props: Props) {
|
|
|
81
82
|
item={screenData}
|
|
82
83
|
mode={mode === "PIP" ? "PIP" : "FULLSCREEN"}
|
|
83
84
|
isModal={false}
|
|
85
|
+
groupId={groupId}
|
|
84
86
|
/>
|
|
85
87
|
);
|
|
86
88
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import React, { useContext } from "react";
|
|
2
2
|
import { Animated } from "react-native";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
useSafeAreaFrame,
|
|
6
|
+
useSafeAreaInsets,
|
|
7
|
+
} from "react-native-safe-area-context";
|
|
5
8
|
import { useGetBottomTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
6
9
|
|
|
7
10
|
import { useConfiguration } from "../utils";
|
|
@@ -91,8 +94,16 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
|
|
|
91
94
|
const dragVideoPlayerY = React.useRef(new Animated.Value(0)).current;
|
|
92
95
|
|
|
93
96
|
const { height } = useSafeAreaFrame();
|
|
97
|
+
const { bottom: bottomSafeArea } = useSafeAreaInsets();
|
|
94
98
|
const bottomTabBarHeight = useGetBottomTabBarHeight();
|
|
95
|
-
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
If bottomTabBarHeight is equal 0 it means an app does not use bottomTabBar.
|
|
102
|
+
Because of this we need to minus bottom SafeArea offset.
|
|
103
|
+
*/
|
|
104
|
+
const minValue =
|
|
105
|
+
height - minimisedHeight - (bottomTabBarHeight || bottomSafeArea);
|
|
106
|
+
|
|
96
107
|
const modalSnapPoints = React.useMemo(() => [0, minValue], [minValue]);
|
|
97
108
|
// Last snap state which will helps us to make smooth responder to scrollview animation
|
|
98
109
|
const [lastSnap, setLastSnap] = React.useState<number>(modalSnapPoints[0]);
|
|
@@ -96,7 +96,7 @@ const getEdges = (isTablet: boolean, isInlineModal: boolean) => {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (!isInlineModal && Platform.OS === "android") {
|
|
99
|
-
return [];
|
|
99
|
+
return ["top", "left", "right", "bottom"];
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
return ["top"];
|
|
@@ -161,7 +161,7 @@ export const PlayerWrapper = (props: Props) => {
|
|
|
161
161
|
width: isInlineModal && docked ? undefined : baseDimensions.width,
|
|
162
162
|
aspectRatio: !isInlineModal && !pip ? getScreenAspectRatio() : 16 / 9,
|
|
163
163
|
}),
|
|
164
|
-
[baseDimensions, isInlineModal, pip]
|
|
164
|
+
[baseDimensions, isInlineModal, pip, docked]
|
|
165
165
|
);
|
|
166
166
|
|
|
167
167
|
const containerDimensions: DimensionsT = React.useMemo(
|