@applicaster/zapp-react-native-ui-components 12.0.0-alpha.8433353116 → 12.0.0-alpha.8939308044
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/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
- 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/Image/Image.ios.tsx +5 -3
- package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -1
- package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
- package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
- 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/MasterCell/utils/index.ts +16 -5
- package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
- package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
- package/Components/PlayerContainer/PlayerContainer.tsx +19 -10
- package/Components/PlayerImageBackground/index.tsx +34 -8
- package/Components/River/ComponentsMap/ComponentsMap.tsx +86 -22
- package/Components/River/RefreshControl.tsx +55 -1
- package/Components/River/TV/River.tsx +0 -23
- package/Components/River/__tests__/componentsMap.test.js +6 -0
- 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 +176 -0
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +4 -0
- package/Components/Screen/index.tsx +36 -25
- package/Components/ScreenResolver/index.tsx +3 -1
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +20 -5
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +0 -5
- package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +78 -0
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +342 -0
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +151 -0
- package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +438 -0
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +154 -0
- package/Components/VideoModal/ModalAnimation/index.ts +15 -0
- package/Components/VideoModal/ModalAnimation/utils.ts +299 -0
- package/Components/VideoModal/PlayerWrapper.tsx +66 -43
- package/Components/VideoModal/VideoModal.tsx +25 -42
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +6 -1
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +151 -119
- package/Components/VideoModal/utils.ts +3 -3
- package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +1 -1
- package/Components/Viewport/ViewportTracker/index.tsx +1 -0
- package/Components/Viewport/utils.js +1 -1
- package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
- package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
- package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
- package/Contexts/ScreenContext/index.tsx +94 -25
- 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
- package/Components/VideoModal/hooks/usePosition.ts +0 -50
- /package/Contexts/{index.js → index.ts} +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Text } from "react-native";
|
|
3
3
|
import { omit } from "ramda";
|
|
4
|
-
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
5
4
|
import {
|
|
6
5
|
textTransform,
|
|
7
6
|
dateFormat,
|
|
@@ -9,6 +8,9 @@ import {
|
|
|
9
8
|
|
|
10
9
|
import { useTextLabel, withFocusedStyles } from "./hooks";
|
|
11
10
|
import { withScaledLineHeight } from "./utils";
|
|
11
|
+
import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
12
|
+
import { MeasurementPortalContext } from "../../../MeasurmentsPortal";
|
|
13
|
+
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
12
14
|
|
|
13
15
|
type Props = {
|
|
14
16
|
style: any;
|
|
@@ -32,14 +34,24 @@ const _Text = ({
|
|
|
32
34
|
...otherProps
|
|
33
35
|
}: Props) => {
|
|
34
36
|
const _label = useTextLabel({ label, entry });
|
|
37
|
+
const isMeasurement = React.useContext(MeasurementPortalContext);
|
|
35
38
|
|
|
36
39
|
if (isNilOrEmpty(_label)) {
|
|
37
40
|
return null;
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
// set maximum possible height for the text in case of measurement
|
|
44
|
+
const height =
|
|
45
|
+
isMeasurement && otherProps.numberOfLines && style.lineHeight
|
|
46
|
+
? toNumber(otherProps.numberOfLines) * toNumber(style.lineHeight)
|
|
47
|
+
: undefined;
|
|
48
|
+
|
|
40
49
|
return (
|
|
41
50
|
<Text
|
|
42
|
-
style={
|
|
51
|
+
style={[
|
|
52
|
+
withScaledLineHeight(withFocusedStyles({ style, otherProps })),
|
|
53
|
+
{ height },
|
|
54
|
+
]}
|
|
43
55
|
{...withoutLabel(otherProps)}
|
|
44
56
|
>
|
|
45
57
|
{dateTransformEnabled
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import
|
|
2
|
+
import RN from "react-native";
|
|
3
3
|
import { Provider } from "react-redux";
|
|
4
4
|
import TestRenderer from "react-test-renderer";
|
|
5
|
-
import
|
|
6
|
-
import CustomText from "../Text";
|
|
5
|
+
import configureStoreFn from "redux-mock-store";
|
|
7
6
|
|
|
8
|
-
const
|
|
7
|
+
const mockUseIsRTL = jest.fn(() => true);
|
|
8
|
+
const mockGetIsRTL = jest.fn(() => true);
|
|
9
|
+
const mockUseLocalizedStrings = jest.fn(() => ({}));
|
|
10
|
+
const mockGetUILanguage = jest.fn(() => "he-HE");
|
|
11
|
+
|
|
12
|
+
jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
|
|
13
|
+
useIsRTL: mockUseIsRTL,
|
|
14
|
+
getIsRTL: mockGetIsRTL,
|
|
15
|
+
useLocalizedStrings: mockUseLocalizedStrings,
|
|
16
|
+
getUILanguage: mockGetUILanguage,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const CustomText = require("../Text").default;
|
|
20
|
+
const mockStore = configureStoreFn();
|
|
9
21
|
|
|
10
22
|
const defaultProps = {
|
|
11
23
|
entry: {},
|
|
@@ -22,7 +34,7 @@ const getRenderedText = (label: string, store: any) => {
|
|
|
22
34
|
</Provider>
|
|
23
35
|
);
|
|
24
36
|
|
|
25
|
-
return testRenderer.root.findByType(Text).props.children;
|
|
37
|
+
return testRenderer.root.findByType(RN.Text).props.children;
|
|
26
38
|
};
|
|
27
39
|
|
|
28
40
|
describe("RTL app: Hebrew text contains english word", () => {
|
|
@@ -34,7 +46,7 @@ describe("RTL app: Hebrew text contains english word", () => {
|
|
|
34
46
|
remoteConfigurations: { localizations: { he: {} } },
|
|
35
47
|
appData: {
|
|
36
48
|
languageCode: "he",
|
|
37
|
-
countryCode: "
|
|
49
|
+
countryCode: "IL",
|
|
38
50
|
},
|
|
39
51
|
});
|
|
40
52
|
|
|
@@ -47,18 +59,22 @@ describe("RTL app: Hebrew text contains english word", () => {
|
|
|
47
59
|
|
|
48
60
|
it("Hebrew text contains not first english word", () => {
|
|
49
61
|
const renderedText = getRenderedText(textWithNotFirstEnglishWord, store);
|
|
50
|
-
|
|
51
62
|
expect(renderedText).toEqual(textWithNotFirstEnglishWord);
|
|
52
63
|
});
|
|
53
64
|
|
|
54
65
|
it("Hebrew text doesn't contain english word", () => {
|
|
55
66
|
const renderedText = getRenderedText(textWithoutEnglishWord, store);
|
|
56
|
-
|
|
57
67
|
expect(renderedText).toEqual(textWithoutEnglishWord);
|
|
58
68
|
});
|
|
59
69
|
});
|
|
60
70
|
|
|
61
71
|
describe("LTR app: English text", () => {
|
|
72
|
+
beforeAll(() => {
|
|
73
|
+
mockUseIsRTL.mockReturnValue(false);
|
|
74
|
+
mockGetIsRTL.mockReturnValue(false);
|
|
75
|
+
mockGetUILanguage.mockReturnValue("en-US");
|
|
76
|
+
});
|
|
77
|
+
|
|
62
78
|
const englishText = "Test sentence";
|
|
63
79
|
|
|
64
80
|
const store = mockStore({
|
|
@@ -71,7 +87,6 @@ describe("LTR app: English text", () => {
|
|
|
71
87
|
|
|
72
88
|
it("English text", () => {
|
|
73
89
|
const renderedText = getRenderedText(englishText, store);
|
|
74
|
-
|
|
75
90
|
expect(renderedText).toEqual(englishText);
|
|
76
91
|
});
|
|
77
92
|
});
|
|
@@ -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",
|
|
@@ -125,13 +125,24 @@ export const useFilterChildren = <
|
|
|
125
125
|
|
|
126
126
|
const filteredChildren = children.filter((child) => {
|
|
127
127
|
const item = child.props.item;
|
|
128
|
-
const
|
|
128
|
+
const actionIdentifier = child.props.pluginIdentifier;
|
|
129
|
+
const action = actions.actions[actionIdentifier];
|
|
130
|
+
|
|
129
131
|
// context value of specific plugin
|
|
130
|
-
|
|
132
|
+
if (action?.module && action.module.context) {
|
|
133
|
+
const currentValue = action.module.context._currentValue;
|
|
134
|
+
|
|
135
|
+
return currentValue?.isActionAvailable
|
|
136
|
+
? currentValue.isActionAvailable(item)
|
|
137
|
+
: true;
|
|
138
|
+
}
|
|
131
139
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
:
|
|
140
|
+
masterCellLogger.error({
|
|
141
|
+
message: `Action plugin for ${actionIdentifier} could not be found, check the configuration of your action button`,
|
|
142
|
+
data: { item, action: child.props.pluginIdentifier },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return false;
|
|
135
146
|
});
|
|
136
147
|
|
|
137
148
|
return filteredChildren;
|
|
@@ -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);
|
|
@@ -54,6 +54,11 @@ import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
|
54
54
|
import { usePlayNextOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayNextOverlay";
|
|
55
55
|
import { PlayNextState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/OverlaysObserver";
|
|
56
56
|
|
|
57
|
+
import {
|
|
58
|
+
useModalAnimationContext,
|
|
59
|
+
PlayerAnimationStateEnum,
|
|
60
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
61
|
+
|
|
57
62
|
type Props = {
|
|
58
63
|
Player: React.ComponentType<any>;
|
|
59
64
|
PlayerLoadingView?: React.ComponentType<any>; // 👀 we are not receiving this prop
|
|
@@ -63,6 +68,7 @@ type Props = {
|
|
|
63
68
|
mode?: VideoModalMode;
|
|
64
69
|
loadPipesData: (url: string, options: {}) => void;
|
|
65
70
|
disableCastAction?: boolean;
|
|
71
|
+
groupId?: string;
|
|
66
72
|
};
|
|
67
73
|
|
|
68
74
|
type ScreenStyles = ZappRiver["styles"] & {
|
|
@@ -145,13 +151,7 @@ const renderApplePlayer = ({
|
|
|
145
151
|
}) => {
|
|
146
152
|
const { title, summary } = item || {};
|
|
147
153
|
|
|
148
|
-
if (!
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// TODO: Check if it is not breaking default player logic on tvos
|
|
153
|
-
// Also if needs to add on tvos brightcove player for QB player
|
|
154
|
-
if (player.supportsNativeControls()) {
|
|
154
|
+
if (!isAppleTV() || !player) {
|
|
155
155
|
return null;
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -201,6 +201,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
201
201
|
style: videoStyle,
|
|
202
202
|
mode = VideoModalMode.MAXIMIZED,
|
|
203
203
|
disableCastAction = false,
|
|
204
|
+
groupId,
|
|
204
205
|
} = props;
|
|
205
206
|
|
|
206
207
|
// Hooks
|
|
@@ -235,6 +236,9 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
235
236
|
const screenData = useTargetScreenData(item);
|
|
236
237
|
const { setVisible: showNavBar } = useSetNavbarState();
|
|
237
238
|
|
|
239
|
+
const { isActiveGesture, startComponentsAnimation, setPlayerAnimationState } =
|
|
240
|
+
useModalAnimationContext();
|
|
241
|
+
|
|
238
242
|
const playerEvent = (event, ...args) => {
|
|
239
243
|
playerManager.invokeHandler(event, ...args);
|
|
240
244
|
};
|
|
@@ -247,7 +251,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
247
251
|
return;
|
|
248
252
|
}
|
|
249
253
|
|
|
250
|
-
navigator.goBack();
|
|
251
254
|
showNavBar(true);
|
|
252
255
|
}, [isModal, navigator.goBack, showNavBar]);
|
|
253
256
|
|
|
@@ -452,7 +455,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
452
455
|
if (disableMiniPlayer) {
|
|
453
456
|
navigator.closeVideoModal();
|
|
454
457
|
} else {
|
|
455
|
-
|
|
458
|
+
setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
|
|
456
459
|
}
|
|
457
460
|
}
|
|
458
461
|
|
|
@@ -627,6 +630,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
627
630
|
style={styles.focusableGroup}
|
|
628
631
|
preferredFocus
|
|
629
632
|
shouldUsePreferredFocus
|
|
633
|
+
groupId={groupId}
|
|
630
634
|
>
|
|
631
635
|
{/* Video player and components */}
|
|
632
636
|
<View
|
|
@@ -647,7 +651,11 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
647
651
|
autoplay={true}
|
|
648
652
|
controls={false}
|
|
649
653
|
disableCastAction={disableCastAction}
|
|
650
|
-
docked={
|
|
654
|
+
docked={
|
|
655
|
+
navigator.isVideoModalDocked() &&
|
|
656
|
+
!startComponentsAnimation &&
|
|
657
|
+
!isActiveGesture
|
|
658
|
+
}
|
|
651
659
|
entry={item}
|
|
652
660
|
fullscreen={mode === VideoModalMode.FULLSCREEN}
|
|
653
661
|
inline={inline}
|
|
@@ -665,6 +673,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
665
673
|
setNextVideoPreloadThresholdPercentage={
|
|
666
674
|
setNextVideoPreloadThresholdPercentage
|
|
667
675
|
}
|
|
676
|
+
startComponentsAnimation={startComponentsAnimation}
|
|
668
677
|
>
|
|
669
678
|
{renderApplePlayer(applePlayerProps)}
|
|
670
679
|
</Player>
|
|
@@ -1,38 +1,64 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from "react";
|
|
2
|
-
import { ImageBackground } from "react-native";
|
|
2
|
+
import { ImageBackground, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
|
|
5
|
+
import {
|
|
6
|
+
AnimationComponent,
|
|
7
|
+
ComponentAnimationType,
|
|
8
|
+
useModalAnimationContext,
|
|
9
|
+
PlayerAnimationStateEnum,
|
|
10
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
5
11
|
|
|
6
12
|
type Props = PropsWithChildren<{
|
|
7
13
|
entry: ZappEntry;
|
|
8
14
|
style?: { [K: string]: any };
|
|
9
15
|
imageStyle?: { [K: string]: any };
|
|
10
16
|
imageKey?: string;
|
|
17
|
+
defaultImageDimensions?: { [K: string]: any };
|
|
11
18
|
}>;
|
|
12
19
|
|
|
20
|
+
const imageSize = { width: "100%", height: "100%" };
|
|
21
|
+
|
|
13
22
|
const PlayerImageBackgroundComponent = ({
|
|
14
23
|
entry,
|
|
15
24
|
children,
|
|
16
25
|
style,
|
|
17
26
|
imageStyle,
|
|
18
27
|
imageKey,
|
|
28
|
+
defaultImageDimensions,
|
|
19
29
|
}: Props) => {
|
|
20
30
|
const source = React.useMemo(
|
|
21
31
|
() => ({ uri: imageSrcFromMediaItem(entry, imageKey) }),
|
|
22
32
|
[imageKey, entry]
|
|
23
33
|
);
|
|
24
34
|
|
|
35
|
+
const { playerAnimationState } = useModalAnimationContext();
|
|
36
|
+
|
|
25
37
|
if (!source) return <>{children}</>;
|
|
26
38
|
|
|
27
39
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
<View
|
|
41
|
+
style={
|
|
42
|
+
playerAnimationState === PlayerAnimationStateEnum.maximaze
|
|
43
|
+
? defaultImageDimensions
|
|
44
|
+
: style
|
|
45
|
+
}
|
|
33
46
|
>
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
<AnimationComponent
|
|
48
|
+
style={style}
|
|
49
|
+
animationType={ComponentAnimationType.player}
|
|
50
|
+
additionalData={defaultImageDimensions}
|
|
51
|
+
>
|
|
52
|
+
<ImageBackground
|
|
53
|
+
resizeMode="cover"
|
|
54
|
+
style={imageSize}
|
|
55
|
+
imageStyle={imageStyle}
|
|
56
|
+
source={source}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</ImageBackground>
|
|
60
|
+
</AnimationComponent>
|
|
61
|
+
</View>
|
|
36
62
|
);
|
|
37
63
|
};
|
|
38
64
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
-
import { View,
|
|
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";
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
import { useScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData";
|
|
22
22
|
import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
23
23
|
import { withComponentsMapProvider } from "@applicaster/zapp-react-native-ui-components/Decorators/ComponentsMapWrapper";
|
|
24
|
-
import {
|
|
24
|
+
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
25
|
+
import { useShallow } from "zustand/react/shallow";
|
|
25
26
|
|
|
26
27
|
type Props = {
|
|
27
28
|
feed: ZappFeed;
|
|
@@ -41,8 +42,10 @@ const styles = StyleSheet.create({
|
|
|
41
42
|
},
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
const
|
|
45
|
+
const getNearestValue = (value, optionA, optionB) =>
|
|
46
|
+
Math.abs(value - optionA) < Math.abs(value - optionB) ? optionA : optionB;
|
|
45
47
|
|
|
48
|
+
const scrollIndicatorInsets = { right: 1 };
|
|
46
49
|
const keyExtractor = R.compose(String, R.prop("id"));
|
|
47
50
|
|
|
48
51
|
function ComponentsMapComponent(props: Props) {
|
|
@@ -60,6 +63,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
60
63
|
refreshingPipesV1,
|
|
61
64
|
} = props;
|
|
62
65
|
|
|
66
|
+
const flatListRef = React.useRef<FlatList | null>(null);
|
|
63
67
|
const screenConfig = useScreenConfiguration(riverId);
|
|
64
68
|
const screenData = useScreenData(riverId);
|
|
65
69
|
const pullToRefreshEnabled = screenData?.rules?.pull_to_refresh_enabled;
|
|
@@ -194,26 +198,81 @@ function ComponentsMapComponent(props: Props) {
|
|
|
194
198
|
[]
|
|
195
199
|
);
|
|
196
200
|
|
|
197
|
-
const
|
|
198
|
-
({ nativeEvent }, isScrollingDone = false) => {
|
|
199
|
-
const {
|
|
200
|
-
contentOffset: { y },
|
|
201
|
-
} = nativeEvent;
|
|
201
|
+
const navBarStore = useScreenContextV2()._navBarStore;
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
scrollPosition: y,
|
|
206
|
-
});
|
|
207
|
-
},
|
|
208
|
-
[]
|
|
203
|
+
const scrollY = navBarStore(
|
|
204
|
+
useShallow(({ scrollYAnimated }) => scrollYAnimated)
|
|
209
205
|
);
|
|
210
206
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
207
|
+
const canMomentum = React.useRef(false);
|
|
208
|
+
|
|
209
|
+
const onScrollBeginDrag = React.useCallback(() => {
|
|
210
|
+
canMomentum.current = true;
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
/* onMomentumScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
|
|
214
|
+
const _onMomentumScrollEnd = React.useCallback(({ nativeEvent }) => {
|
|
215
|
+
if (!canMomentum.current) return;
|
|
216
|
+
canMomentum.current = false;
|
|
217
|
+
|
|
218
|
+
const { height: headerHeight, scrollState } = navBarStore.getState();
|
|
219
|
+
|
|
220
|
+
const offsetY = Math.max(0, nativeEvent.contentOffset.y);
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
!(scrollState === 0 || scrollState === -headerHeight) &&
|
|
224
|
+
flatListRef.current
|
|
225
|
+
) {
|
|
226
|
+
const offset =
|
|
227
|
+
getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
|
|
228
|
+
-headerHeight
|
|
229
|
+
? Math.round(offsetY + scrollState + headerHeight)
|
|
230
|
+
: Math.round(offsetY + scrollState);
|
|
231
|
+
|
|
232
|
+
flatListRef.current.scrollToOffset({
|
|
233
|
+
animated: true,
|
|
234
|
+
offset,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}, []);
|
|
238
|
+
|
|
239
|
+
/* onScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
|
|
240
|
+
const _onScrollEndDrag = React.useCallback(({ nativeEvent }) => {
|
|
241
|
+
const { height: headerHeight, scrollState } = navBarStore.getState();
|
|
242
|
+
|
|
243
|
+
const offsetY = Math.max(0, nativeEvent.contentOffset.y);
|
|
244
|
+
|
|
245
|
+
if (
|
|
246
|
+
!(scrollState === 0 || scrollState === -headerHeight) &&
|
|
247
|
+
flatListRef.current &&
|
|
248
|
+
nativeEvent.velocity.y === 0
|
|
249
|
+
) {
|
|
250
|
+
const offset =
|
|
251
|
+
getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
|
|
252
|
+
-headerHeight
|
|
253
|
+
? Math.round(offsetY + scrollState + headerHeight)
|
|
254
|
+
: Math.round(offsetY + scrollState);
|
|
255
|
+
|
|
256
|
+
flatListRef.current.scrollToOffset({
|
|
257
|
+
animated: true,
|
|
258
|
+
offset,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}, []);
|
|
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
|
+
}, []);
|
|
217
276
|
|
|
218
277
|
if (shouldShowLoadingError) {
|
|
219
278
|
return <RiverError />;
|
|
@@ -230,6 +289,9 @@ function ComponentsMapComponent(props: Props) {
|
|
|
230
289
|
>
|
|
231
290
|
<ViewportTracker>
|
|
232
291
|
<FlatList
|
|
292
|
+
ref={(ref) => {
|
|
293
|
+
flatListRef.current = ref;
|
|
294
|
+
}}
|
|
233
295
|
// Fix for WebView rerender crashes on Android API 28+
|
|
234
296
|
// https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
|
|
235
297
|
overScrollMode={Platform.OS === "android" ? "never" : "auto"}
|
|
@@ -249,8 +311,10 @@ function ComponentsMapComponent(props: Props) {
|
|
|
249
311
|
}
|
|
250
312
|
ListFooterComponent={renderFooter}
|
|
251
313
|
refreshControl={refreshControl}
|
|
252
|
-
|
|
253
|
-
|
|
314
|
+
onScrollBeginDrag={onScrollBeginDrag}
|
|
315
|
+
onScroll={onScroll}
|
|
316
|
+
onMomentumScrollEnd={_onMomentumScrollEnd}
|
|
317
|
+
onScrollEndDrag={_onScrollEndDrag}
|
|
254
318
|
scrollEventThrottle={16}
|
|
255
319
|
{...scrollViewExtraProps}
|
|
256
320
|
/>
|
|
@@ -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
|
}
|