@applicaster/zapp-react-native-ui-components 12.0.0-alpha.4837064762 → 12.0.0-alpha.4885205607
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Components/BaseFocusable/index.tsx +3 -3
- package/Components/Cell/Cell.tsx +30 -13
- 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/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
- 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__/__snapshots__/index.test.tsx.snap +10 -50
- package/Components/Layout/TV/__tests__/index.test.tsx +20 -75
- package/Components/Layout/TV/index.tsx +29 -181
- package/Components/Layout/TV/index.web.tsx +33 -0
- package/Components/Layout/index.tsx +15 -11
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +88 -112
- package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +20 -0
- 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 +10 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
- package/Components/MasterCell/dataAdapter.ts +21 -7
- package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
- package/Components/MasterCell/index.tsx +5 -3
- package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
- package/Components/MasterCell/utils/index.ts +32 -29
- package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
- package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
- package/Components/Navigator/StackNavigator.tsx +57 -0
- package/Components/Navigator/index.ts +1 -0
- package/Components/PlayerContainer/PlayerContainer.tsx +3 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +24 -17
- package/Components/River/RefreshControl.tsx +55 -1
- package/Components/River/TV/River.tsx +0 -23
- package/Components/RouteManager/index.tsx +2 -2
- package/Components/Screen/TV/hooks/index.ts +3 -0
- package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
- package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
- package/Components/Screen/TV/index.web.tsx +59 -45
- 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 +32 -26
- package/Components/Transitioner/Transitioner.tsx +38 -43
- package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +12 -4
- package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
- package/Components/VideoLive/LiveImageManager.ts +28 -1
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +52 -24
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +26 -24
- package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +2 -14
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +56 -43
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +42 -32
- package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +104 -86
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +44 -5
- package/Components/VideoModal/ModalAnimation/utils.ts +7 -4
- package/Components/VideoModal/PlayerDetails.tsx +3 -3
- package/Components/VideoModal/PlayerWrapper.tsx +67 -60
- package/Components/VideoModal/VideoModal.tsx +17 -17
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +7 -0
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +32 -12
- package/Components/VideoModal/hooks/useModalSize.ts +10 -12
- package/Components/VideoModal/utils.ts +5 -2
- package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
- package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +4 -4
- package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
- package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
- package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
- package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
- package/Contexts/FocusableGroupContext/index.ts +7 -0
- package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
- package/Contexts/ScreenContext/index.tsx +2 -2
- package/Contexts/ScreenDataContext/index.tsx +20 -0
- package/events/index.ts +1 -0
- package/package.json +5 -5
- package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
- /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
- /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo } from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
+
import validateColor from "validate-color";
|
|
3
4
|
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
4
5
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
6
|
|
|
@@ -9,6 +10,13 @@ import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerM
|
|
|
9
10
|
import { PushTopicManager } from "@applicaster/zapp-react-native-bridge/PushNotifications/PushTopicManager";
|
|
10
11
|
import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
|
|
11
12
|
import { getCellState } from "../../Cell/utils";
|
|
13
|
+
import { getColorFromData } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
14
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
15
|
+
|
|
16
|
+
const log = createLogger({
|
|
17
|
+
category: "Debug",
|
|
18
|
+
subsystem: "MasterCellUtils",
|
|
19
|
+
});
|
|
12
20
|
|
|
13
21
|
const hasElementSpecificViewType = (viewType) => (element) => {
|
|
14
22
|
if (R.isNil(element)) {
|
|
@@ -47,44 +55,37 @@ export const hasElementsSpecificViewType = (viewType) => (elements) => {
|
|
|
47
55
|
return R.any(hasElementSpecificViewType(viewType))(elements);
|
|
48
56
|
};
|
|
49
57
|
|
|
50
|
-
const isColorOrEmpty = (value) =>
|
|
51
|
-
value === null ||
|
|
52
|
-
value === undefined ||
|
|
53
|
-
value === "" ||
|
|
54
|
-
value.startsWith("#") ||
|
|
55
|
-
value.startsWith("rgba") ||
|
|
56
|
-
value === "transparent";
|
|
57
|
-
|
|
58
58
|
function resolveColorForProp(entry, style, colorProp) {
|
|
59
|
-
const
|
|
59
|
+
const colorFromProp = style[colorProp];
|
|
60
|
+
const nestedEntryValue = R.path(colorFromProp.split("."), entry);
|
|
61
|
+
|
|
62
|
+
log.log_error("resolvecolor resolveColorforProp", {
|
|
63
|
+
colorFromProp,
|
|
64
|
+
nestedEntryValue,
|
|
65
|
+
entry,
|
|
66
|
+
style,
|
|
67
|
+
colorProp,
|
|
68
|
+
});
|
|
60
69
|
|
|
61
|
-
if (
|
|
62
|
-
|
|
70
|
+
if (
|
|
71
|
+
nestedEntryValue === undefined &&
|
|
72
|
+
!validateColor(colorFromProp.replace(".00", "")) // https://github.com/dreamyguy/validate-color/issues/44
|
|
73
|
+
) {
|
|
74
|
+
logWarning(colorFromProp, style, entry, nestedEntryValue);
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (nestedEntryValue === null || typeof nestedEntryValue !== "object") {
|
|
70
|
-
logWarning(colorValue, style, entry, nestedEntryValue);
|
|
71
|
-
|
|
72
|
-
return style;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
nestedEntryValue = nestedEntryValue[part];
|
|
76
|
-
}
|
|
77
|
+
const colorValue = getColorFromData({
|
|
78
|
+
data: entry,
|
|
79
|
+
valueFromLayout: colorFromProp,
|
|
80
|
+
});
|
|
77
81
|
|
|
78
|
-
if (
|
|
79
|
-
logWarning(
|
|
82
|
+
if (!colorValue) {
|
|
83
|
+
logWarning(colorProp, style, entry, colorValue);
|
|
80
84
|
|
|
81
85
|
return style;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
style[colorProp] = nestedEntryValue;
|
|
86
|
-
|
|
87
|
-
return style;
|
|
88
|
+
return { ...style, [colorProp]: colorValue };
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
export function resolveColor(entry, style) {
|
|
@@ -96,6 +97,8 @@ export function resolveColor(entry, style) {
|
|
|
96
97
|
const styleKeys = Object.keys(style);
|
|
97
98
|
const colorKeys = styleKeys.filter((key) => /color/i.test(key));
|
|
98
99
|
|
|
100
|
+
log.log_error("resolvecolor resolveColor", { colorKeys, entry, style });
|
|
101
|
+
|
|
99
102
|
return colorKeys.reduce((acc, value) => {
|
|
100
103
|
return { ...style, ...resolveColorForProp(entry, acc, value) };
|
|
101
104
|
}, style);
|
|
@@ -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);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { isEmpty } from "ramda";
|
|
3
|
+
|
|
4
|
+
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
5
|
+
import { getPathAttributes } from "@applicaster/zapp-react-native-utils/navigationUtils";
|
|
6
|
+
import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
|
|
7
|
+
import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
|
|
8
|
+
import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
|
|
9
|
+
|
|
10
|
+
import { Screen } from "../Screen/TV/index.web";
|
|
11
|
+
import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
12
|
+
import { ScreenContextProvider } from "../../Contexts/ScreenContext";
|
|
13
|
+
|
|
14
|
+
type Components = {
|
|
15
|
+
NavBar: React.ComponentType<any>;
|
|
16
|
+
Background: React.ComponentType<any>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
Components: Components;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const StackNavigator = ({ Components }: Props) => {
|
|
24
|
+
const { mainStack } = useNavigation();
|
|
25
|
+
|
|
26
|
+
const renderScreen = ({ route, state }, index) => {
|
|
27
|
+
const pathAttributes = getPathAttributes({ pathname: route });
|
|
28
|
+
|
|
29
|
+
if (isEmpty(pathAttributes)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// HACK: Adding index to route to avoid conflicts in the navigator.
|
|
34
|
+
// Startup hooks are all using PUSH navigation action to navigate, but their routes are always like root routes.
|
|
35
|
+
// This means some hooks can have the same routes, e.g., ["hooks/context-setter", "/hooks/login-plug", "hooks/context-setter"].
|
|
36
|
+
const routeId = `${route}--${index}`;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<FocusableGroup
|
|
40
|
+
id={route}
|
|
41
|
+
preferredFocus={isLast(index, mainStack.length)}
|
|
42
|
+
excludeFromFocusSearching
|
|
43
|
+
key={routeId}
|
|
44
|
+
>
|
|
45
|
+
<ScreenDataContext.Provider value={state}>
|
|
46
|
+
<PathnameContext.Provider value={route}>
|
|
47
|
+
<ScreenContextProvider pathname={route}>
|
|
48
|
+
<Screen route={route} Components={Components} />
|
|
49
|
+
</ScreenContextProvider>
|
|
50
|
+
</PathnameContext.Provider>
|
|
51
|
+
</ScreenDataContext.Provider>
|
|
52
|
+
</FocusableGroup>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return <>{mainStack.map(renderScreen)}</>;
|
|
57
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StackNavigator } from "./StackNavigator";
|
|
@@ -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 });
|
|
@@ -673,6 +675,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
673
675
|
setNextVideoPreloadThresholdPercentage={
|
|
674
676
|
setNextVideoPreloadThresholdPercentage
|
|
675
677
|
}
|
|
678
|
+
startComponentsAnimation={startComponentsAnimation}
|
|
676
679
|
>
|
|
677
680
|
{renderApplePlayer(applePlayerProps)}
|
|
678
681
|
</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,26 @@ 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
|
+
} else {
|
|
275
|
+
if (y <= 0) {
|
|
276
|
+
scrollY.setValue(0);
|
|
277
|
+
} else if (y + height > contentHeight) {
|
|
278
|
+
scrollY.setValue(contentHeight - height);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}, []);
|
|
282
|
+
|
|
263
283
|
if (shouldShowLoadingError) {
|
|
264
284
|
return <RiverError />;
|
|
265
285
|
}
|
|
@@ -298,20 +318,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
298
318
|
ListFooterComponent={renderFooter}
|
|
299
319
|
refreshControl={refreshControl}
|
|
300
320
|
onScrollBeginDrag={onScrollBeginDrag}
|
|
301
|
-
onScroll={
|
|
302
|
-
[
|
|
303
|
-
{
|
|
304
|
-
nativeEvent: {
|
|
305
|
-
contentOffset: {
|
|
306
|
-
y: scrollY,
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
{
|
|
312
|
-
useNativeDriver: false,
|
|
313
|
-
}
|
|
314
|
-
)}
|
|
321
|
+
onScroll={onScroll}
|
|
315
322
|
onMomentumScrollEnd={_onMomentumScrollEnd}
|
|
316
323
|
onScrollEndDrag={_onScrollEndDrag}
|
|
317
324
|
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;
|
|
138
|
+
case "on_top_screen":
|
|
139
|
+
// navbar height + status bar height
|
|
140
|
+
return offset + height + top;
|
|
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!`,
|
|
@@ -14,7 +14,7 @@ type Props = { pathname: string; screenData: Record<string, any> };
|
|
|
14
14
|
* @param {Props}: see props above
|
|
15
15
|
*/
|
|
16
16
|
export function RouteManager(props: Props) {
|
|
17
|
-
const { pathname, screenData
|
|
17
|
+
const { pathname, screenData } = useRoute();
|
|
18
18
|
const pathAttributes = getPathAttributes({ pathname });
|
|
19
19
|
|
|
20
20
|
const { screenType, screenId } = R.last(pathAttributes);
|
|
@@ -25,7 +25,7 @@ export function RouteManager(props: Props) {
|
|
|
25
25
|
screenType={screenType}
|
|
26
26
|
screenId={screenId}
|
|
27
27
|
{...props}
|
|
28
|
-
screenData={
|
|
28
|
+
screenData={screenData}
|
|
29
29
|
/>
|
|
30
30
|
</TestId>
|
|
31
31
|
);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useNavigationPluginData,
|
|
4
|
+
useIsNavBarVisible,
|
|
5
|
+
useRoute,
|
|
6
|
+
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
7
|
+
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
8
|
+
import {
|
|
9
|
+
setFocusOnContent,
|
|
10
|
+
setFocusOnMenu,
|
|
11
|
+
} from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux";
|
|
12
|
+
|
|
13
|
+
type Return =
|
|
14
|
+
| {
|
|
15
|
+
onContent: true;
|
|
16
|
+
onMenu: false;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
onContent: false;
|
|
20
|
+
onMenu: true;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getInitialFocus = (focusOnContent, isNavBarVisible): Return => {
|
|
24
|
+
if (focusOnContent) {
|
|
25
|
+
return {
|
|
26
|
+
onContent: true,
|
|
27
|
+
onMenu: false,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!focusOnContent && isNavBarVisible) {
|
|
32
|
+
// put focus on navbar
|
|
33
|
+
return {
|
|
34
|
+
onContent: false,
|
|
35
|
+
onMenu: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// we don't have navbar, put focus on content anyway
|
|
40
|
+
return {
|
|
41
|
+
onContent: true,
|
|
42
|
+
onMenu: false,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const useInitialFocus = (): void => {
|
|
47
|
+
const navigationPluginData = useNavigationPluginData();
|
|
48
|
+
|
|
49
|
+
const focusOnContent = toBooleanWithDefaultFalse(
|
|
50
|
+
navigationPluginData?.rules?.focus_on_content
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const isNavBarVisible = useIsNavBarVisible();
|
|
54
|
+
|
|
55
|
+
const { pathname: currentRoute } = useRoute();
|
|
56
|
+
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
const initialFocus = getInitialFocus(focusOnContent, isNavBarVisible);
|
|
59
|
+
|
|
60
|
+
if (initialFocus.onContent) {
|
|
61
|
+
setFocusOnContent(currentRoute);
|
|
62
|
+
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (initialFocus.onMenu) {
|
|
67
|
+
setFocusOnMenu(currentRoute);
|
|
68
|
+
}
|
|
69
|
+
}, []);
|
|
70
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { isNil } from "ramda";
|
|
3
|
+
import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
4
|
+
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
|
|
5
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
6
|
+
|
|
7
|
+
export const useRestoreFocus = (): void => {
|
|
8
|
+
const isScreenActive = useIsScreenActive();
|
|
9
|
+
const previousIsScreenActive = usePrevious(isScreenActive);
|
|
10
|
+
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
if (isNil(previousIsScreenActive)) {
|
|
13
|
+
// it's initial mounting of the screen, do nothing here
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!previousIsScreenActive && isScreenActive) {
|
|
18
|
+
// restore focus when we go back from next screen
|
|
19
|
+
focusManager.setInitialFocus();
|
|
20
|
+
}
|
|
21
|
+
}, [previousIsScreenActive, isScreenActive]);
|
|
22
|
+
};
|