@applicaster/quick-brick-player 14.0.0-rc.8 → 14.0.0-rc.80
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/package.json +6 -11
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +28 -88
- package/src/Player/AudioLayer/Layout/Controls/index.tsx +8 -18
- package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +119 -125
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +5 -38
- package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +3 -8
- package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +5 -9
- package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +22 -72
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +1 -58
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +0 -33
- package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +6 -18
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +1 -1
- package/src/Player/PlayerModal/GestureAnimatedScrollView.ts +7 -0
- package/src/Player/PlayerModal/PlayerModal.tsx +115 -0
- package/src/Player/PlayerModal/VideoPlayerModal.tsx +271 -0
- package/src/Player/PlayerModal/consts/index.ts +17 -0
- package/src/Player/PlayerModal/context/index.ts +6 -0
- package/src/Player/PlayerModal/hooks/index.ts +201 -0
- package/src/Player/PlayerModal/index.ts +3 -0
- package/src/Player/PlayerModal/index.web.ts +5 -0
- package/src/Player/PlayerModal/styles.ts +104 -0
- package/src/Player/PlayerModal/utils/index.ts +29 -0
- package/src/Player/PlayerView/GenericTVPlayerView.tsx +42 -0
- package/src/Player/PlayerView/TVPlayerView.tsx +26 -0
- package/src/Player/PlayerView/TvOSPlayerView.tsx +51 -0
- package/src/Player/PlayerView/types.ts +41 -0
- package/src/Player/index.tsx +574 -380
- package/src/QuickBrickPlayer.tsx +16 -10
- package/src/utils/dimensions.ts +29 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/logger.ts +6 -0
- package/src/utils/playerHelpers.ts +11 -0
- package/src/utils/playerStyles.ts +50 -0
- /package/src/Player/AudioLayer/Layout/{Debug → DebugComponent}/index.tsx +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import React, { ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
import { Animated, Pressable } from "react-native";
|
|
4
|
+
import { PanGestureHandler } from "react-native-gesture-handler";
|
|
5
|
+
import { useStyles } from "./styles";
|
|
6
|
+
import { useVideoModalState } from "./hooks";
|
|
7
|
+
import { MODAL_COLLAPSE_RATIO, MODAL_RADIUS } from "./consts";
|
|
8
|
+
import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
9
|
+
import { getWindowHeight } from "./utils";
|
|
10
|
+
|
|
11
|
+
type AnimatedModalProps = {
|
|
12
|
+
content?: ReactElement;
|
|
13
|
+
collapsedContent?: ReactElement;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function AnimatedModal({
|
|
17
|
+
content,
|
|
18
|
+
collapsedContent,
|
|
19
|
+
}: AnimatedModalProps) {
|
|
20
|
+
const translateYRef = useModalAnimationContext().yTranslate;
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
translateY,
|
|
24
|
+
collapsed,
|
|
25
|
+
collapsedHeight,
|
|
26
|
+
gestureHandlerProps,
|
|
27
|
+
expand,
|
|
28
|
+
} = useVideoModalState(translateYRef);
|
|
29
|
+
|
|
30
|
+
const styles = useStyles({ height: collapsedHeight, type: "audio" });
|
|
31
|
+
|
|
32
|
+
const WINDOW_HEIGHT = getWindowHeight();
|
|
33
|
+
const heightAboveMinimised = WINDOW_HEIGHT - collapsedHeight;
|
|
34
|
+
|
|
35
|
+
const MODAL_COLLAPSE_START = WINDOW_HEIGHT * MODAL_COLLAPSE_RATIO;
|
|
36
|
+
|
|
37
|
+
// Interpolated opacities for smooth cross-fade
|
|
38
|
+
const collapsedOpacity = translateY.interpolate({
|
|
39
|
+
inputRange: [MODAL_COLLAPSE_START, heightAboveMinimised],
|
|
40
|
+
outputRange: [0, 1],
|
|
41
|
+
extrapolate: "clamp",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const expandedOpacity = translateY.interpolate({
|
|
45
|
+
inputRange: [0, heightAboveMinimised],
|
|
46
|
+
outputRange: [1, 0],
|
|
47
|
+
extrapolate: "clamp",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const borderTopRadiusAnimated = translateY.interpolate({
|
|
51
|
+
inputRange: [0, heightAboveMinimised],
|
|
52
|
+
outputRange: [MODAL_RADIUS, 0],
|
|
53
|
+
extrapolate: "clamp",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<PanGestureHandler {...gestureHandlerProps}>
|
|
58
|
+
<Animated.View
|
|
59
|
+
pointerEvents={"auto"}
|
|
60
|
+
style={[
|
|
61
|
+
styles.modalWrapper,
|
|
62
|
+
{
|
|
63
|
+
borderTopLeftRadius: borderTopRadiusAnimated,
|
|
64
|
+
borderTopRightRadius: borderTopRadiusAnimated,
|
|
65
|
+
transform: [{ translateY }],
|
|
66
|
+
height: WINDOW_HEIGHT,
|
|
67
|
+
},
|
|
68
|
+
]}
|
|
69
|
+
>
|
|
70
|
+
<Animated.View
|
|
71
|
+
style={[
|
|
72
|
+
styles.modal,
|
|
73
|
+
{
|
|
74
|
+
borderTopLeftRadius: borderTopRadiusAnimated,
|
|
75
|
+
borderTopRightRadius: borderTopRadiusAnimated,
|
|
76
|
+
height: WINDOW_HEIGHT,
|
|
77
|
+
},
|
|
78
|
+
]}
|
|
79
|
+
>
|
|
80
|
+
{/* Expanded Content (fades in) */}
|
|
81
|
+
<Animated.View
|
|
82
|
+
pointerEvents={!collapsed ? "auto" : "none"}
|
|
83
|
+
style={styles.modalContent}
|
|
84
|
+
>
|
|
85
|
+
<Animated.View
|
|
86
|
+
pointerEvents={!collapsed ? "auto" : "none"}
|
|
87
|
+
style={[
|
|
88
|
+
styles.expandedContentContainer,
|
|
89
|
+
{ opacity: expandedOpacity },
|
|
90
|
+
]}
|
|
91
|
+
>
|
|
92
|
+
{content}
|
|
93
|
+
</Animated.View>
|
|
94
|
+
</Animated.View>
|
|
95
|
+
{/* Collapsed Bar (fades out) */}
|
|
96
|
+
<Animated.View
|
|
97
|
+
pointerEvents={collapsed ? "box-none" : "none"}
|
|
98
|
+
style={[
|
|
99
|
+
styles.collapsedBarContainer,
|
|
100
|
+
{ opacity: collapsedOpacity },
|
|
101
|
+
]}
|
|
102
|
+
>
|
|
103
|
+
<Pressable
|
|
104
|
+
testID="collapsedBarWrapper"
|
|
105
|
+
onPress={expand}
|
|
106
|
+
style={styles.collapsedBarWrapper}
|
|
107
|
+
>
|
|
108
|
+
{collapsedContent}
|
|
109
|
+
</Pressable>
|
|
110
|
+
</Animated.View>
|
|
111
|
+
</Animated.View>
|
|
112
|
+
</Animated.View>
|
|
113
|
+
</PanGestureHandler>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import React, { ReactElement, useMemo, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { Animated, Pressable, StyleSheet, ViewStyle } from "react-native";
|
|
4
|
+
import { PanGestureHandler } from "react-native-gesture-handler";
|
|
5
|
+
import { useStyles } from "./styles";
|
|
6
|
+
import { useConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
|
|
7
|
+
import { useVideoModalState } from "./hooks";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_IMAGE_RATIO_VIDEO,
|
|
10
|
+
MODAL_COLLAPSE_RATIO,
|
|
11
|
+
MODAL_RADIUS,
|
|
12
|
+
VIDEO_TRANSITION_THRESHOLD,
|
|
13
|
+
} from "./consts";
|
|
14
|
+
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
15
|
+
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
16
|
+
import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/useModalAnimationContext";
|
|
17
|
+
import { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
|
|
18
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
19
|
+
import { GestureAnimatedScrollView } from "./GestureAnimatedScrollView";
|
|
20
|
+
import { PlayerDetailsWrapperHeightContext } from "./context";
|
|
21
|
+
import { getWindowHeight, getWindowWidth } from "./utils";
|
|
22
|
+
|
|
23
|
+
const orientationStyles = StyleSheet.create({
|
|
24
|
+
landscape: { flexDirection: "row" },
|
|
25
|
+
portrait: { flexDirection: "column" },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const directionStyles = (isLandscape: boolean): ViewStyle => {
|
|
29
|
+
return isLandscape ? orientationStyles.landscape : orientationStyles.portrait;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type AnimatedModalProps = {
|
|
33
|
+
pip?: boolean;
|
|
34
|
+
fullscreen?: boolean;
|
|
35
|
+
modal?: boolean;
|
|
36
|
+
content?: ReactElement;
|
|
37
|
+
collapsedContent?: ReactElement;
|
|
38
|
+
extraContent?: ReactElement;
|
|
39
|
+
aspectRatio?: number;
|
|
40
|
+
isTabletPortrait?: boolean;
|
|
41
|
+
style?: {
|
|
42
|
+
tabletLandscapeWidth?: number;
|
|
43
|
+
width?: number;
|
|
44
|
+
height?: number;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function VideoPlayerModal({
|
|
49
|
+
aspectRatio = DEFAULT_IMAGE_RATIO_VIDEO,
|
|
50
|
+
content,
|
|
51
|
+
extraContent,
|
|
52
|
+
collapsedContent,
|
|
53
|
+
isTabletPortrait,
|
|
54
|
+
style,
|
|
55
|
+
pip = false,
|
|
56
|
+
fullscreen = false,
|
|
57
|
+
modal = true,
|
|
58
|
+
}: AnimatedModalProps) {
|
|
59
|
+
const enabled = !!modal && !fullscreen;
|
|
60
|
+
const isTablet = useIsTablet();
|
|
61
|
+
|
|
62
|
+
const WINDOW_WIDTH = useMemo(() => getWindowWidth(), []); // remember initial width, ignore rotation changes
|
|
63
|
+
const height = useMemo(() => getWindowHeight(), []); // remember initial height, ignore rotation changes
|
|
64
|
+
const MODAL_COLLAPSE_START = height * MODAL_COLLAPSE_RATIO;
|
|
65
|
+
|
|
66
|
+
let width = WINDOW_WIDTH;
|
|
67
|
+
|
|
68
|
+
if (isTablet && !isTabletPortrait) {
|
|
69
|
+
width = style?.tabletLandscapeWidth || width;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { minimised_height: minimisedHeight } = useConfiguration();
|
|
73
|
+
const scrollViewRef = useRef<typeof Animated.ScrollView | null>(null);
|
|
74
|
+
|
|
75
|
+
const translateYRef = useModalAnimationContext().yTranslate;
|
|
76
|
+
// used when modal is false, should not be used in animation
|
|
77
|
+
const dummyRef = useRef(new Animated.Value(0));
|
|
78
|
+
|
|
79
|
+
const {
|
|
80
|
+
translateY,
|
|
81
|
+
collapsed,
|
|
82
|
+
collapsedHeight,
|
|
83
|
+
gestureHandlerProps,
|
|
84
|
+
expand,
|
|
85
|
+
} = useVideoModalState(modal ? translateYRef : dummyRef);
|
|
86
|
+
|
|
87
|
+
const heightAboveMinimised = height - collapsedHeight;
|
|
88
|
+
|
|
89
|
+
const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
|
|
90
|
+
|
|
91
|
+
const styles = useStyles({ height: collapsedHeight });
|
|
92
|
+
|
|
93
|
+
const SCALE_FACTOR = 1 / (width / aspectRatio / minimisedHeight); // Scale factor for expanded content
|
|
94
|
+
|
|
95
|
+
const insets = useSafeAreaInsets();
|
|
96
|
+
|
|
97
|
+
const isFullScreenOrPIP = fullscreen || pip;
|
|
98
|
+
|
|
99
|
+
// Interpolated opacities for smooth cross-fade
|
|
100
|
+
const collapsedOpacity = !isFullScreenOrPIP
|
|
101
|
+
? translateY.interpolate({
|
|
102
|
+
inputRange: [MODAL_COLLAPSE_START, heightAboveMinimised],
|
|
103
|
+
outputRange: [0, 1],
|
|
104
|
+
extrapolate: "clamp",
|
|
105
|
+
})
|
|
106
|
+
: new Animated.Value(0);
|
|
107
|
+
|
|
108
|
+
const expandedOpacity = !isFullScreenOrPIP
|
|
109
|
+
? translateY.interpolate({
|
|
110
|
+
inputRange: [0, heightAboveMinimised],
|
|
111
|
+
outputRange: [1, 0],
|
|
112
|
+
extrapolate: "clamp",
|
|
113
|
+
})
|
|
114
|
+
: new Animated.Value(1);
|
|
115
|
+
|
|
116
|
+
const xPosition = !isFullScreenOrPIP
|
|
117
|
+
? translateY.interpolate({
|
|
118
|
+
inputRange: [height * VIDEO_TRANSITION_THRESHOLD, heightAboveMinimised],
|
|
119
|
+
outputRange: [0, -(width - width * SCALE_FACTOR) / 2],
|
|
120
|
+
extrapolate: "clamp",
|
|
121
|
+
})
|
|
122
|
+
: new Animated.Value(0);
|
|
123
|
+
|
|
124
|
+
const vidHeight = width / aspectRatio;
|
|
125
|
+
|
|
126
|
+
const yPosition = !isFullScreenOrPIP
|
|
127
|
+
? translateY.interpolate({
|
|
128
|
+
inputRange: [
|
|
129
|
+
height * VIDEO_TRANSITION_THRESHOLD,
|
|
130
|
+
height - collapsedHeight,
|
|
131
|
+
],
|
|
132
|
+
outputRange: [
|
|
133
|
+
0,
|
|
134
|
+
isTablet && isTabletLandscape
|
|
135
|
+
? -((-PROGRESS_BAR_HEIGHT + height - vidHeight * SCALE_FACTOR) / 2)
|
|
136
|
+
: -(
|
|
137
|
+
PROGRESS_BAR_HEIGHT +
|
|
138
|
+
insets.top / 2 +
|
|
139
|
+
(WINDOW_WIDTH / aspectRatio -
|
|
140
|
+
(WINDOW_WIDTH / aspectRatio) * SCALE_FACTOR) /
|
|
141
|
+
2
|
|
142
|
+
),
|
|
143
|
+
],
|
|
144
|
+
extrapolate: "clamp",
|
|
145
|
+
})
|
|
146
|
+
: new Animated.Value(0);
|
|
147
|
+
|
|
148
|
+
const scalePosition = !isFullScreenOrPIP
|
|
149
|
+
? translateY.interpolate({
|
|
150
|
+
inputRange: [
|
|
151
|
+
height * VIDEO_TRANSITION_THRESHOLD,
|
|
152
|
+
height - collapsedHeight,
|
|
153
|
+
],
|
|
154
|
+
outputRange: [1, SCALE_FACTOR],
|
|
155
|
+
extrapolate: "clamp",
|
|
156
|
+
})
|
|
157
|
+
: new Animated.Value(1);
|
|
158
|
+
|
|
159
|
+
const borderTopRadiusAnimated = !isFullScreenOrPIP
|
|
160
|
+
? translateY.interpolate({
|
|
161
|
+
inputRange: [0, height - collapsedHeight],
|
|
162
|
+
outputRange: [MODAL_RADIUS, 0],
|
|
163
|
+
extrapolate: "clamp",
|
|
164
|
+
})
|
|
165
|
+
: new Animated.Value(0);
|
|
166
|
+
|
|
167
|
+
const [playerDetailsWrapperHeight, setPlayerDetailsWrapperHeight] =
|
|
168
|
+
React.useState(0);
|
|
169
|
+
|
|
170
|
+
const onScrollViewLayout = React.useCallback(
|
|
171
|
+
({
|
|
172
|
+
nativeEvent: {
|
|
173
|
+
layout: { height },
|
|
174
|
+
},
|
|
175
|
+
}) => {
|
|
176
|
+
setPlayerDetailsWrapperHeight(height);
|
|
177
|
+
},
|
|
178
|
+
[]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<PanGestureHandler
|
|
183
|
+
enabled={enabled}
|
|
184
|
+
waitFor={scrollViewRef}
|
|
185
|
+
{...gestureHandlerProps}
|
|
186
|
+
>
|
|
187
|
+
<Animated.View
|
|
188
|
+
pointerEvents={"auto"}
|
|
189
|
+
style={[
|
|
190
|
+
styles.modalWrapper,
|
|
191
|
+
{
|
|
192
|
+
borderTopLeftRadius: borderTopRadiusAnimated,
|
|
193
|
+
borderTopRightRadius: borderTopRadiusAnimated,
|
|
194
|
+
transform: [{ translateY: isFullScreenOrPIP ? 0 : translateY }],
|
|
195
|
+
height: height,
|
|
196
|
+
},
|
|
197
|
+
]}
|
|
198
|
+
>
|
|
199
|
+
<Animated.View
|
|
200
|
+
pointerEvents={"auto"}
|
|
201
|
+
style={[
|
|
202
|
+
styles.modal,
|
|
203
|
+
collapsed ? styles.collapsedModalBg : null,
|
|
204
|
+
{
|
|
205
|
+
borderTopLeftRadius: borderTopRadiusAnimated,
|
|
206
|
+
borderTopRightRadius: borderTopRadiusAnimated,
|
|
207
|
+
height: height,
|
|
208
|
+
},
|
|
209
|
+
]}
|
|
210
|
+
>
|
|
211
|
+
<Animated.View
|
|
212
|
+
pointerEvents={!collapsed ? "auto" : "none"}
|
|
213
|
+
style={[styles.modalContent, directionStyles(isTabletLandscape)]}
|
|
214
|
+
>
|
|
215
|
+
<Animated.View
|
|
216
|
+
style={[
|
|
217
|
+
styles.expandedContentContainer,
|
|
218
|
+
{
|
|
219
|
+
transform: [
|
|
220
|
+
{ translateX: xPosition },
|
|
221
|
+
{ translateY: yPosition },
|
|
222
|
+
{ scale: scalePosition },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
]}
|
|
226
|
+
>
|
|
227
|
+
{content}
|
|
228
|
+
</Animated.View>
|
|
229
|
+
{extraContent ? (
|
|
230
|
+
<GestureAnimatedScrollView
|
|
231
|
+
ref={scrollViewRef}
|
|
232
|
+
contentContainerStyle={{
|
|
233
|
+
paddingBottom: insets.bottom,
|
|
234
|
+
}}
|
|
235
|
+
onLayout={onScrollViewLayout}
|
|
236
|
+
style={[
|
|
237
|
+
styles.extraContentScrollView,
|
|
238
|
+
{ opacity: expandedOpacity },
|
|
239
|
+
]}
|
|
240
|
+
>
|
|
241
|
+
<PlayerDetailsWrapperHeightContext.Provider
|
|
242
|
+
value={playerDetailsWrapperHeight}
|
|
243
|
+
>
|
|
244
|
+
{extraContent}
|
|
245
|
+
</PlayerDetailsWrapperHeightContext.Provider>
|
|
246
|
+
</GestureAnimatedScrollView>
|
|
247
|
+
) : null}
|
|
248
|
+
</Animated.View>
|
|
249
|
+
|
|
250
|
+
{!isFullScreenOrPIP ? (
|
|
251
|
+
<Animated.View
|
|
252
|
+
pointerEvents={collapsed ? "box-none" : "none"}
|
|
253
|
+
style={[
|
|
254
|
+
styles.collapsedBarContainer,
|
|
255
|
+
{ opacity: collapsedOpacity },
|
|
256
|
+
]}
|
|
257
|
+
>
|
|
258
|
+
<Pressable
|
|
259
|
+
testID="collapsedBarWrapper"
|
|
260
|
+
onPress={expand}
|
|
261
|
+
style={styles.collapsedBarWrapper}
|
|
262
|
+
>
|
|
263
|
+
{collapsedContent}
|
|
264
|
+
</Pressable>
|
|
265
|
+
</Animated.View>
|
|
266
|
+
) : null}
|
|
267
|
+
</Animated.View>
|
|
268
|
+
</Animated.View>
|
|
269
|
+
</PanGestureHandler>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const DOCKED_PROGRESSBAR_HEIGHT = 3; // Height of the docked progress bar plugins/mobile-transport-controls/src/ProgressBar/layouts/DockedLayout.tsx
|
|
2
|
+
|
|
3
|
+
export const VIDEO_TRANSITION_THRESHOLD = 0.5; // Threshold for video transition
|
|
4
|
+
|
|
5
|
+
export const DRAG_TO_COLLAPSE = 0.02; // 2% to collapse
|
|
6
|
+
|
|
7
|
+
export const DRAG_TO_EXPAND = 0.02; // 2% to expand
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_IMAGE_RATIO_VIDEO = 16 / 9; // Default aspect ratio for video
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_IMAGE_RATIO_AUDIO = 1; // Default aspect ratio for audio
|
|
12
|
+
|
|
13
|
+
export const ANIMATION_DURATION = 250; // Default animation duration in milliseconds
|
|
14
|
+
|
|
15
|
+
export const MODAL_RADIUS = 16; // Default border radius for modal
|
|
16
|
+
|
|
17
|
+
export const MODAL_COLLAPSE_RATIO = 0.7; // 70% of the screen height
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
3
|
+
import { State } from "react-native-gesture-handler";
|
|
4
|
+
import { Animated } from "react-native";
|
|
5
|
+
|
|
6
|
+
import { VideoModalMode } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
7
|
+
import { useConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
|
|
8
|
+
import { getTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/getTabBarHeight";
|
|
9
|
+
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
|
|
10
|
+
import { isMenuVisible } from "@applicaster/zapp-react-native-ui-components/Components/Screen/navigationHandler";
|
|
11
|
+
import {
|
|
12
|
+
ANIMATION_DURATION,
|
|
13
|
+
DRAG_TO_COLLAPSE,
|
|
14
|
+
DRAG_TO_EXPAND,
|
|
15
|
+
} from "../consts";
|
|
16
|
+
import { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
|
|
17
|
+
import { usePlugins } from "@applicaster/zapp-react-native-redux";
|
|
18
|
+
import { getWindowHeight } from "../utils";
|
|
19
|
+
|
|
20
|
+
const bottomTabBarHeight = getTabBarHeight();
|
|
21
|
+
|
|
22
|
+
export const useVideoModalState = (
|
|
23
|
+
translateYRef: React.MutableRefObject<Animated.Value>
|
|
24
|
+
) => {
|
|
25
|
+
const { minimised_height: minimisedHeight } = useConfiguration();
|
|
26
|
+
|
|
27
|
+
const WINDOW_HEIGHT = getWindowHeight();
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
maximiseVideoModal,
|
|
31
|
+
minimiseVideoModal,
|
|
32
|
+
videoModalState,
|
|
33
|
+
currentRoute,
|
|
34
|
+
screenData,
|
|
35
|
+
} = useNavigation();
|
|
36
|
+
|
|
37
|
+
const plugins = usePlugins();
|
|
38
|
+
|
|
39
|
+
const menuVisible = isMenuVisible(currentRoute, screenData, plugins);
|
|
40
|
+
|
|
41
|
+
const collapsed =
|
|
42
|
+
videoModalState.visible &&
|
|
43
|
+
videoModalState.mode === VideoModalMode.MINIMIZED;
|
|
44
|
+
|
|
45
|
+
const { bottom } = useSafeAreaInsets();
|
|
46
|
+
|
|
47
|
+
const initialBottomOffset = useRef(bottom).current;
|
|
48
|
+
|
|
49
|
+
const collapsedHeight =
|
|
50
|
+
minimisedHeight +
|
|
51
|
+
initialBottomOffset +
|
|
52
|
+
(menuVisible ? bottomTabBarHeight : 0) +
|
|
53
|
+
PROGRESS_BAR_HEIGHT;
|
|
54
|
+
|
|
55
|
+
const heightAboveMinimised = WINDOW_HEIGHT - collapsedHeight;
|
|
56
|
+
|
|
57
|
+
const translateY = translateYRef.current;
|
|
58
|
+
|
|
59
|
+
const handleExpand = useCallback(
|
|
60
|
+
(toValue: number) => {
|
|
61
|
+
return Animated.timing(translateYRef.current, {
|
|
62
|
+
toValue,
|
|
63
|
+
duration: ANIMATION_DURATION,
|
|
64
|
+
useNativeDriver: true,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
[translateYRef]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const handleCollapse = useCallback(
|
|
71
|
+
(toValue: number) => {
|
|
72
|
+
return Animated.timing(translateYRef.current, {
|
|
73
|
+
toValue,
|
|
74
|
+
duration: ANIMATION_DURATION,
|
|
75
|
+
useNativeDriver: true,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
[translateYRef]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const offset = useRef(heightAboveMinimised);
|
|
82
|
+
|
|
83
|
+
// Gesture handler for modal
|
|
84
|
+
const onGestureEvent = useCallback(
|
|
85
|
+
(event: any) => {
|
|
86
|
+
if (event.nativeEvent.state === State.ACTIVE) {
|
|
87
|
+
const y = event.nativeEvent.translationY;
|
|
88
|
+
let newY = offset.current + y;
|
|
89
|
+
if (newY < 0) newY = 0;
|
|
90
|
+
|
|
91
|
+
if (newY > heightAboveMinimised) {
|
|
92
|
+
newY = heightAboveMinimised;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
translateY.setValue(newY);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
[collapsedHeight, translateY]
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const onHandlerStateChange = useCallback(
|
|
102
|
+
(event: any) => {
|
|
103
|
+
if (event.nativeEvent.oldState === State.ACTIVE) {
|
|
104
|
+
const { translationY } = event.nativeEvent;
|
|
105
|
+
let newY = offset.current + translationY;
|
|
106
|
+
if (newY < 0) newY = 0;
|
|
107
|
+
|
|
108
|
+
if (newY > heightAboveMinimised) {
|
|
109
|
+
newY = heightAboveMinimised;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const shouldCollapse =
|
|
113
|
+
translationY > heightAboveMinimised * DRAG_TO_COLLAPSE;
|
|
114
|
+
|
|
115
|
+
const shouldExpand =
|
|
116
|
+
translationY < -heightAboveMinimised * DRAG_TO_EXPAND;
|
|
117
|
+
|
|
118
|
+
if (!collapsed && shouldCollapse) {
|
|
119
|
+
// Collapse
|
|
120
|
+
Animated.timing(translateY, {
|
|
121
|
+
toValue: heightAboveMinimised,
|
|
122
|
+
duration: ANIMATION_DURATION,
|
|
123
|
+
useNativeDriver: true,
|
|
124
|
+
}).start(() => {
|
|
125
|
+
minimiseVideoModal();
|
|
126
|
+
offset.current = heightAboveMinimised;
|
|
127
|
+
});
|
|
128
|
+
} else if (collapsed && shouldExpand) {
|
|
129
|
+
// Expand
|
|
130
|
+
Animated.timing(translateY, {
|
|
131
|
+
toValue: 0,
|
|
132
|
+
duration: ANIMATION_DURATION,
|
|
133
|
+
useNativeDriver: true,
|
|
134
|
+
}).start(() => {
|
|
135
|
+
maximiseVideoModal();
|
|
136
|
+
offset.current = 0;
|
|
137
|
+
translateY.setValue(0);
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
// Snap back to current state
|
|
141
|
+
Animated.spring(translateY, {
|
|
142
|
+
toValue: collapsed ? heightAboveMinimised : 0,
|
|
143
|
+
useNativeDriver: true,
|
|
144
|
+
}).start(() => {
|
|
145
|
+
offset.current = collapsed ? heightAboveMinimised : 0;
|
|
146
|
+
translateY.setValue(offset.current);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
} else if (event.nativeEvent.state === State.BEGAN) {
|
|
150
|
+
// Gesture started, set offset
|
|
151
|
+
translateY.stopAnimation((value: number) => {
|
|
152
|
+
offset.current = value;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
[collapsedHeight, translateY, collapsed, videoModalState]
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const gestureHandlerProps = useMemo(() => {
|
|
160
|
+
return {
|
|
161
|
+
onGestureEvent,
|
|
162
|
+
onHandlerStateChange,
|
|
163
|
+
};
|
|
164
|
+
}, [onGestureEvent, onHandlerStateChange]);
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (videoModalState.visible) {
|
|
168
|
+
if (videoModalState.mode === VideoModalMode.MAXIMIZED) {
|
|
169
|
+
handleExpand(0).start(() => {
|
|
170
|
+
maximiseVideoModal();
|
|
171
|
+
offset.current = 0;
|
|
172
|
+
translateY.setValue(0);
|
|
173
|
+
});
|
|
174
|
+
} else if (videoModalState.mode === VideoModalMode.MINIMIZED) {
|
|
175
|
+
handleCollapse(heightAboveMinimised).start(() => {
|
|
176
|
+
minimiseVideoModal();
|
|
177
|
+
offset.current = heightAboveMinimised;
|
|
178
|
+
translateY.setValue(heightAboveMinimised);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}, [videoModalState.visible, videoModalState.mode, collapsedHeight]);
|
|
183
|
+
|
|
184
|
+
return useMemo(
|
|
185
|
+
() => ({
|
|
186
|
+
collapsed,
|
|
187
|
+
collapsedHeight,
|
|
188
|
+
offset,
|
|
189
|
+
gestureHandlerProps,
|
|
190
|
+
translateY,
|
|
191
|
+
expand: maximiseVideoModal,
|
|
192
|
+
}),
|
|
193
|
+
[
|
|
194
|
+
collapsed,
|
|
195
|
+
collapsedHeight,
|
|
196
|
+
gestureHandlerProps,
|
|
197
|
+
translateY,
|
|
198
|
+
maximiseVideoModal,
|
|
199
|
+
]
|
|
200
|
+
);
|
|
201
|
+
};
|