@applicaster/quick-brick-player 14.0.0-rc.9 → 14.0.0-rc.90
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 +44 -88
- package/src/Player/AudioLayer/Layout/Controls/index.tsx +8 -18
- package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +169 -161
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +4 -42
- package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +5 -9
- package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +35 -83
- package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +6 -1
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +6 -66
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +1 -39
- package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +6 -18
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +1 -1
- package/src/Player/AudioLayer/utils.ts +1 -27
- package/src/Player/PlayerModal/GestureAnimatedScrollView.ts +7 -0
- package/src/Player/PlayerModal/PlayerModal.tsx +110 -0
- package/src/Player/PlayerModal/VideoPlayerModal.tsx +289 -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 +169 -0
- package/src/Player/PlayerModal/index.ts +3 -0
- package/src/Player/PlayerModal/index.web.ts +5 -0
- package/src/Player/PlayerModal/styles.ts +109 -0
- package/src/Player/PlayerModal/utils/index.ts +111 -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 +575 -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/PlayerImage/AnimatedImage.tsx +0 -87
- package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +0 -1
- package/src/Player/AudioLayer/Layout/PlayerImage/hooks/useChangePlayerState.ts +0 -99
- /package/src/Player/AudioLayer/Layout/{Debug → DebugComponent}/index.tsx +0 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import React, { ReactElement, useMemo, useRef } 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 { 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 { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
|
|
16
|
+
import {
|
|
17
|
+
SafeAreaView,
|
|
18
|
+
useSafeAreaFrame,
|
|
19
|
+
useSafeAreaInsets,
|
|
20
|
+
} from "react-native-safe-area-context";
|
|
21
|
+
import { GestureAnimatedScrollView } from "./GestureAnimatedScrollView";
|
|
22
|
+
import { PlayerDetailsWrapperHeightContext } from "./context";
|
|
23
|
+
import {
|
|
24
|
+
getInsetsOffset,
|
|
25
|
+
getWindowHeight,
|
|
26
|
+
getWindowWidth,
|
|
27
|
+
directionStyles,
|
|
28
|
+
getScaledPos,
|
|
29
|
+
getExtraContentPadding,
|
|
30
|
+
} from "./utils";
|
|
31
|
+
import { addOrientationChangeListener } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
|
|
32
|
+
|
|
33
|
+
const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView);
|
|
34
|
+
|
|
35
|
+
type AnimatedModalProps = {
|
|
36
|
+
pip?: boolean;
|
|
37
|
+
fullscreen?: boolean;
|
|
38
|
+
modal?: boolean;
|
|
39
|
+
content?: ReactElement;
|
|
40
|
+
collapsedContent?: ReactElement;
|
|
41
|
+
extraContent?: ReactElement;
|
|
42
|
+
aspectRatio?: number;
|
|
43
|
+
isTabletPortrait?: boolean;
|
|
44
|
+
style?: {
|
|
45
|
+
tabletLandscapeWidth?: number;
|
|
46
|
+
width?: number;
|
|
47
|
+
height?: number;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export function VideoPlayerModal({
|
|
52
|
+
aspectRatio = DEFAULT_IMAGE_RATIO_VIDEO,
|
|
53
|
+
content,
|
|
54
|
+
extraContent,
|
|
55
|
+
collapsedContent,
|
|
56
|
+
isTabletPortrait,
|
|
57
|
+
style,
|
|
58
|
+
pip = false,
|
|
59
|
+
fullscreen = false,
|
|
60
|
+
modal = true,
|
|
61
|
+
}: AnimatedModalProps) {
|
|
62
|
+
const enabled = !!modal && !fullscreen;
|
|
63
|
+
const isTablet = useIsTablet();
|
|
64
|
+
|
|
65
|
+
// remember initial width, ignore rotation changes
|
|
66
|
+
const width = useMemo(
|
|
67
|
+
() =>
|
|
68
|
+
isTablet && !isTabletPortrait
|
|
69
|
+
? style.tabletLandscapeWidth
|
|
70
|
+
: getWindowWidth(isTablet, !isTabletPortrait),
|
|
71
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
|
+
[]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const height = useMemo(
|
|
76
|
+
() => getWindowHeight(isTablet, !isTabletPortrait),
|
|
77
|
+
[]
|
|
78
|
+
); // remember initial height, ignore rotation changes
|
|
79
|
+
|
|
80
|
+
const MODAL_COLLAPSE_START = height * MODAL_COLLAPSE_RATIO;
|
|
81
|
+
|
|
82
|
+
const { minimised_height: minimisedHeight } = useConfiguration();
|
|
83
|
+
const scrollViewRef = useRef<typeof Animated.ScrollView | null>(null);
|
|
84
|
+
|
|
85
|
+
const { translateY, collapsed, gestureHandlerProps, expand, offset } =
|
|
86
|
+
useVideoModalState();
|
|
87
|
+
|
|
88
|
+
const frame = useSafeAreaFrame();
|
|
89
|
+
const collapsedHeight = frame.height - offset.current;
|
|
90
|
+
|
|
91
|
+
const heightAboveMinimised = height - collapsedHeight;
|
|
92
|
+
|
|
93
|
+
const styles = useStyles({ height: collapsedHeight });
|
|
94
|
+
|
|
95
|
+
const SCALE_FACTOR = 1 / (width / aspectRatio / minimisedHeight); // Scale factor for expanded content
|
|
96
|
+
|
|
97
|
+
const insets = useSafeAreaInsets();
|
|
98
|
+
|
|
99
|
+
const [isFullScreenMode, setFullScreenMode] = React.useState(
|
|
100
|
+
fullscreen || pip
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
React.useEffect(() => {
|
|
104
|
+
const listener = addOrientationChangeListener(({ toOrientation }) => {
|
|
105
|
+
/* ignored in collapsed mode non-PiP player */
|
|
106
|
+
if (collapsed && !pip) return;
|
|
107
|
+
setFullScreenMode(!(toOrientation === 1));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
listener.remove();
|
|
112
|
+
};
|
|
113
|
+
}, [collapsed, pip]);
|
|
114
|
+
|
|
115
|
+
const isFullScreenOrPIP = isFullScreenMode;
|
|
116
|
+
|
|
117
|
+
const vidHeight = width / aspectRatio;
|
|
118
|
+
|
|
119
|
+
// animated opacity for smooth cross-fade of collapsed content
|
|
120
|
+
const collapsedOpacity = !isFullScreenOrPIP
|
|
121
|
+
? translateY.interpolate({
|
|
122
|
+
inputRange: [MODAL_COLLAPSE_START, heightAboveMinimised],
|
|
123
|
+
outputRange: [0, 1],
|
|
124
|
+
extrapolate: "clamp",
|
|
125
|
+
})
|
|
126
|
+
: new Animated.Value(0);
|
|
127
|
+
|
|
128
|
+
// animated opacity for extra content
|
|
129
|
+
const expandedOpacity = !isFullScreenOrPIP
|
|
130
|
+
? translateY.interpolate({
|
|
131
|
+
inputRange: [0, heightAboveMinimised],
|
|
132
|
+
outputRange: [1, 0],
|
|
133
|
+
extrapolate: "clamp",
|
|
134
|
+
})
|
|
135
|
+
: new Animated.Value(1);
|
|
136
|
+
|
|
137
|
+
// animated x position for video content
|
|
138
|
+
const xPosition = !isFullScreenOrPIP
|
|
139
|
+
? translateY.interpolate({
|
|
140
|
+
inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
|
|
141
|
+
outputRange: [0, -(width - width * SCALE_FACTOR) / 2],
|
|
142
|
+
extrapolate: "clamp",
|
|
143
|
+
})
|
|
144
|
+
: new Animated.Value(0);
|
|
145
|
+
|
|
146
|
+
// animated y position for video content
|
|
147
|
+
const yPosition = !isFullScreenOrPIP
|
|
148
|
+
? translateY.interpolate({
|
|
149
|
+
inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
|
|
150
|
+
outputRange: [
|
|
151
|
+
0,
|
|
152
|
+
-(
|
|
153
|
+
PROGRESS_BAR_HEIGHT +
|
|
154
|
+
getInsetsOffset(insets, isTabletPortrait) +
|
|
155
|
+
getScaledPos(height, vidHeight, minimisedHeight, isTabletPortrait)
|
|
156
|
+
),
|
|
157
|
+
],
|
|
158
|
+
extrapolate: "clamp",
|
|
159
|
+
})
|
|
160
|
+
: new Animated.Value(0);
|
|
161
|
+
|
|
162
|
+
// animated position for video content
|
|
163
|
+
const scalePosition = !isFullScreenOrPIP
|
|
164
|
+
? translateY.interpolate({
|
|
165
|
+
inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
|
|
166
|
+
outputRange: [1, SCALE_FACTOR],
|
|
167
|
+
extrapolate: "clamp",
|
|
168
|
+
})
|
|
169
|
+
: new Animated.Value(1);
|
|
170
|
+
|
|
171
|
+
const borderTopRadiusAnimated = !isFullScreenOrPIP
|
|
172
|
+
? translateY.interpolate({
|
|
173
|
+
inputRange: [0, heightAboveMinimised],
|
|
174
|
+
outputRange: [MODAL_RADIUS, 0],
|
|
175
|
+
extrapolate: "clamp",
|
|
176
|
+
})
|
|
177
|
+
: new Animated.Value(0);
|
|
178
|
+
|
|
179
|
+
const [playerDetailsWrapperHeight, setPlayerDetailsWrapperHeight] =
|
|
180
|
+
React.useState(0);
|
|
181
|
+
|
|
182
|
+
const onScrollViewLayout = React.useCallback(
|
|
183
|
+
({
|
|
184
|
+
nativeEvent: {
|
|
185
|
+
layout: { height },
|
|
186
|
+
},
|
|
187
|
+
}) => {
|
|
188
|
+
setPlayerDetailsWrapperHeight(height);
|
|
189
|
+
},
|
|
190
|
+
[]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<PanGestureHandler
|
|
195
|
+
enabled={enabled}
|
|
196
|
+
waitFor={scrollViewRef}
|
|
197
|
+
activeOffsetY={[-25, 25]}
|
|
198
|
+
activeOffsetX={[-25, 25]}
|
|
199
|
+
{...gestureHandlerProps}
|
|
200
|
+
>
|
|
201
|
+
<AnimatedSafeAreaView pointerEvents="box-none" edges={["bottom"]}>
|
|
202
|
+
<Animated.View
|
|
203
|
+
pointerEvents={"auto"}
|
|
204
|
+
style={[
|
|
205
|
+
styles.modalWrapper,
|
|
206
|
+
{
|
|
207
|
+
borderTopLeftRadius: borderTopRadiusAnimated,
|
|
208
|
+
borderTopRightRadius: borderTopRadiusAnimated,
|
|
209
|
+
transform: [{ translateY: isFullScreenOrPIP ? 0 : translateY }],
|
|
210
|
+
height: frame.height,
|
|
211
|
+
},
|
|
212
|
+
]}
|
|
213
|
+
>
|
|
214
|
+
<Animated.View
|
|
215
|
+
pointerEvents={"auto"}
|
|
216
|
+
style={[
|
|
217
|
+
styles.modal,
|
|
218
|
+
collapsed ? styles.collapsedModalBg : null,
|
|
219
|
+
{
|
|
220
|
+
borderTopLeftRadius: borderTopRadiusAnimated,
|
|
221
|
+
borderTopRightRadius: borderTopRadiusAnimated,
|
|
222
|
+
},
|
|
223
|
+
]}
|
|
224
|
+
>
|
|
225
|
+
<Animated.View
|
|
226
|
+
pointerEvents={!collapsed ? "auto" : "none"}
|
|
227
|
+
style={[
|
|
228
|
+
pip ? styles.modalContentPiP : styles.modalContent,
|
|
229
|
+
directionStyles(isTabletPortrait),
|
|
230
|
+
]}
|
|
231
|
+
>
|
|
232
|
+
<Animated.View
|
|
233
|
+
style={[
|
|
234
|
+
styles.expandedContentContainer,
|
|
235
|
+
{
|
|
236
|
+
transform: [
|
|
237
|
+
{ translateX: xPosition },
|
|
238
|
+
{ translateY: yPosition },
|
|
239
|
+
{ scale: scalePosition },
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
]}
|
|
243
|
+
>
|
|
244
|
+
{content}
|
|
245
|
+
</Animated.View>
|
|
246
|
+
{extraContent ? (
|
|
247
|
+
<GestureAnimatedScrollView
|
|
248
|
+
ref={scrollViewRef}
|
|
249
|
+
contentContainerStyle={getExtraContentPadding(insets)}
|
|
250
|
+
bounces={false}
|
|
251
|
+
overScrollMode="never"
|
|
252
|
+
onLayout={onScrollViewLayout}
|
|
253
|
+
style={[
|
|
254
|
+
styles.extraContentScrollView,
|
|
255
|
+
{ opacity: expandedOpacity },
|
|
256
|
+
]}
|
|
257
|
+
>
|
|
258
|
+
<PlayerDetailsWrapperHeightContext.Provider
|
|
259
|
+
value={playerDetailsWrapperHeight}
|
|
260
|
+
>
|
|
261
|
+
{extraContent}
|
|
262
|
+
</PlayerDetailsWrapperHeightContext.Provider>
|
|
263
|
+
</GestureAnimatedScrollView>
|
|
264
|
+
) : null}
|
|
265
|
+
</Animated.View>
|
|
266
|
+
|
|
267
|
+
{!isFullScreenOrPIP ? (
|
|
268
|
+
<Animated.View
|
|
269
|
+
pointerEvents={collapsed ? "box-none" : "none"}
|
|
270
|
+
style={[
|
|
271
|
+
styles.collapsedBarContainer,
|
|
272
|
+
{ opacity: collapsedOpacity },
|
|
273
|
+
]}
|
|
274
|
+
>
|
|
275
|
+
<Pressable
|
|
276
|
+
testID="collapsedBarWrapper"
|
|
277
|
+
onPress={expand}
|
|
278
|
+
style={styles.collapsedBarWrapper}
|
|
279
|
+
>
|
|
280
|
+
{collapsedContent}
|
|
281
|
+
</Pressable>
|
|
282
|
+
</Animated.View>
|
|
283
|
+
) : null}
|
|
284
|
+
</Animated.View>
|
|
285
|
+
</Animated.View>
|
|
286
|
+
</AnimatedSafeAreaView>
|
|
287
|
+
</PanGestureHandler>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
@@ -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,169 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { State } from "react-native-gesture-handler";
|
|
4
|
+
|
|
5
|
+
import { VideoModalMode } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
6
|
+
import { useAnimationStateStore } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
|
|
7
|
+
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
|
|
8
|
+
import {
|
|
9
|
+
ANIMATION_DURATION,
|
|
10
|
+
DRAG_TO_COLLAPSE,
|
|
11
|
+
DRAG_TO_EXPAND,
|
|
12
|
+
} from "../consts";
|
|
13
|
+
import { Animated } from "react-native";
|
|
14
|
+
import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
15
|
+
|
|
16
|
+
const getAnimatedValue = (animatedValue: Animated.Value): number => {
|
|
17
|
+
return (animatedValue as any).__getValue() as number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const useVideoModalState = () => {
|
|
21
|
+
const {
|
|
22
|
+
yTranslate,
|
|
23
|
+
offset,
|
|
24
|
+
heightAboveMinimised,
|
|
25
|
+
gestureTranslationRef,
|
|
26
|
+
offsetAnimatedValueRef,
|
|
27
|
+
} = useModalAnimationContext();
|
|
28
|
+
|
|
29
|
+
const modalAnimatedValue = offsetAnimatedValueRef.current;
|
|
30
|
+
const gestureTranslation = gestureTranslationRef.current;
|
|
31
|
+
|
|
32
|
+
const updateModalTranslation = useCallback(
|
|
33
|
+
(toValue: number, duration: number = ANIMATION_DURATION) =>
|
|
34
|
+
Animated.timing(modalAnimatedValue, {
|
|
35
|
+
toValue,
|
|
36
|
+
duration,
|
|
37
|
+
useNativeDriver: true,
|
|
38
|
+
}),
|
|
39
|
+
[modalAnimatedValue]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const translateY = yTranslate.current;
|
|
43
|
+
|
|
44
|
+
const { maximiseVideoModal, minimiseVideoModal, videoModalState } =
|
|
45
|
+
useNavigation();
|
|
46
|
+
|
|
47
|
+
const videoModalMode = videoModalState.mode;
|
|
48
|
+
|
|
49
|
+
const prevModeStateRef = useRef(videoModalMode); // keep track of last non-PIP mode
|
|
50
|
+
const gestureStateRef = useRef<any>(State.UNDETERMINED); // keep track of gesture state
|
|
51
|
+
|
|
52
|
+
// Track last non-PIP mode
|
|
53
|
+
if (videoModalMode !== prevModeStateRef.current) {
|
|
54
|
+
if (["MAXIMIZED", "MINIMIZED"].includes(videoModalMode)) {
|
|
55
|
+
prevModeStateRef.current = videoModalMode;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const collapsed =
|
|
60
|
+
videoModalState.visible &&
|
|
61
|
+
((prevModeStateRef.current === "MINIMIZED" &&
|
|
62
|
+
videoModalState.mode === "PIP") || // If STILL in PIP mode, check if last non-PIP mode was MINIMIZED
|
|
63
|
+
videoModalState.mode === VideoModalMode.MINIMIZED); // Or if currently in MINIMIZED mode
|
|
64
|
+
|
|
65
|
+
// Gesture handler for modal
|
|
66
|
+
const onGestureEvent = useMemo(
|
|
67
|
+
() =>
|
|
68
|
+
Animated.event([{ nativeEvent: { translationY: gestureTranslation } }], {
|
|
69
|
+
useNativeDriver: true,
|
|
70
|
+
}),
|
|
71
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
|
+
[]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const onHandlerStateChange = useCallback(
|
|
76
|
+
(event: any) => {
|
|
77
|
+
const { state: newState, oldState, translationY } = event.nativeEvent;
|
|
78
|
+
|
|
79
|
+
if (newState === State.ACTIVE || oldState === State.ACTIVE) {
|
|
80
|
+
useAnimationStateStore.setState({
|
|
81
|
+
isAnimationInProgress: newState === State.ACTIVE,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update gesture state tracking
|
|
86
|
+
gestureStateRef.current = newState;
|
|
87
|
+
|
|
88
|
+
if (translationY !== 0) {
|
|
89
|
+
if (newState === State.BEGAN) {
|
|
90
|
+
// clean up gesture translation
|
|
91
|
+
gestureTranslation.setValue(0);
|
|
92
|
+
} else if (
|
|
93
|
+
newState === State.END ||
|
|
94
|
+
newState === State.FAILED ||
|
|
95
|
+
newState === State.CANCELLED
|
|
96
|
+
) {
|
|
97
|
+
const newY = getAnimatedValue(gestureTranslation);
|
|
98
|
+
|
|
99
|
+
// clean up gesture translation
|
|
100
|
+
gestureTranslation.setValue(0);
|
|
101
|
+
|
|
102
|
+
const v = getAnimatedValue(modalAnimatedValue);
|
|
103
|
+
|
|
104
|
+
// store current offset
|
|
105
|
+
modalAnimatedValue.setValue(v + newY);
|
|
106
|
+
|
|
107
|
+
const shouldCollapse = newY > offset.current * DRAG_TO_COLLAPSE;
|
|
108
|
+
|
|
109
|
+
const shouldExpand = newY < offset.current * DRAG_TO_EXPAND;
|
|
110
|
+
|
|
111
|
+
if (!collapsed && shouldCollapse) {
|
|
112
|
+
// Collapse
|
|
113
|
+
updateModalTranslation(offset.current).start(() => {
|
|
114
|
+
minimiseVideoModal();
|
|
115
|
+
});
|
|
116
|
+
} else if (collapsed && shouldExpand) {
|
|
117
|
+
// Expand
|
|
118
|
+
updateModalTranslation(0).start(() => {
|
|
119
|
+
maximiseVideoModal();
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
// Snap back to current state
|
|
123
|
+
updateModalTranslation(collapsed ? offset.current : 0).start();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
129
|
+
[collapsed]
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const gestureHandlerProps = useMemo(() => {
|
|
133
|
+
return {
|
|
134
|
+
onGestureEvent,
|
|
135
|
+
onHandlerStateChange,
|
|
136
|
+
};
|
|
137
|
+
}, [onGestureEvent, onHandlerStateChange]);
|
|
138
|
+
|
|
139
|
+
// Track video modal state changes & calling updateAnimatedPosition
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (videoModalState.visible) {
|
|
142
|
+
const mode =
|
|
143
|
+
videoModalMode === "PIP" ? prevModeStateRef.current : videoModalMode;
|
|
144
|
+
|
|
145
|
+
// Prevent changes during active gesture states to avoid interference
|
|
146
|
+
if ([State.ACTIVE].includes(gestureStateRef.current)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (mode === VideoModalMode.MAXIMIZED) {
|
|
151
|
+
updateModalTranslation(0).start();
|
|
152
|
+
} else if (mode === VideoModalMode.MINIMIZED) {
|
|
153
|
+
updateModalTranslation(heightAboveMinimised).start();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
157
|
+
}, [videoModalState.visible, videoModalState.mode, heightAboveMinimised]);
|
|
158
|
+
|
|
159
|
+
return useMemo(
|
|
160
|
+
() => ({
|
|
161
|
+
collapsed,
|
|
162
|
+
offset,
|
|
163
|
+
gestureHandlerProps,
|
|
164
|
+
translateY,
|
|
165
|
+
expand: maximiseVideoModal,
|
|
166
|
+
}),
|
|
167
|
+
[collapsed, offset, gestureHandlerProps, translateY, maximiseVideoModal]
|
|
168
|
+
);
|
|
169
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
|
|
2
|
+
import {
|
|
3
|
+
useIsTablet,
|
|
4
|
+
useNavigation,
|
|
5
|
+
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
6
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
import { StyleSheet } from "react-native";
|
|
9
|
+
|
|
10
|
+
export const useBackgroundColor = (): string => {
|
|
11
|
+
const {
|
|
12
|
+
modal_background_color: modalBackgroundColor,
|
|
13
|
+
tablet_landscape_player_container_background_color: tabletBackgroundColor,
|
|
14
|
+
} = useConfiguration();
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
videoModalState: { mode },
|
|
18
|
+
} = useNavigation();
|
|
19
|
+
|
|
20
|
+
const isTablet = useIsTablet();
|
|
21
|
+
|
|
22
|
+
const theme = useTheme<BaseThemePropertiesMobile>();
|
|
23
|
+
const isFullscreen = mode === "FULLSCREEN";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* For the new audio player, the background color setting work that way
|
|
27
|
+
* Layer 0 - modal_background_color
|
|
28
|
+
* Layer 1 - screen_background_color ( can be combined with layer 0 ), If the parameter is not set, it will be a solid black.
|
|
29
|
+
* audio_player_background_color is used only in the old player when the background image is not set
|
|
30
|
+
* modal_background_color is used as the background for player details, and in useBackgroundColor to set a background for a video modal.
|
|
31
|
+
* If it’s not set, theme?.status_background_color will be used instead
|
|
32
|
+
*
|
|
33
|
+
* This hook cover only case with layer 0: modal_background_color, layer 1: screen_background_color is used in the styles.expandedContentContainer
|
|
34
|
+
*
|
|
35
|
+
* For the video player, inline player will use tablet_landscape_player_container_background_color as background color, and transparent for fullscreen.
|
|
36
|
+
* Mobile player will use
|
|
37
|
+
* modal_background_color as background color.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
return isTablet
|
|
41
|
+
? !isFullscreen
|
|
42
|
+
? tabletBackgroundColor
|
|
43
|
+
: "transparent"
|
|
44
|
+
: modalBackgroundColor || theme?.status_background_color;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const useStyles = ({ height }: { height: number }) => {
|
|
48
|
+
const COLLAPSED_HEIGHT = height;
|
|
49
|
+
|
|
50
|
+
const backgroundColor = useBackgroundColor();
|
|
51
|
+
|
|
52
|
+
const BG_COLOR_DEFAULT_BLACK = "black";
|
|
53
|
+
const BG_COLOR_DEFAULT_TRANSPARENT = "transparent";
|
|
54
|
+
|
|
55
|
+
return useMemo(
|
|
56
|
+
() =>
|
|
57
|
+
StyleSheet.create({
|
|
58
|
+
modal: {
|
|
59
|
+
position: "absolute",
|
|
60
|
+
left: 0,
|
|
61
|
+
right: 0,
|
|
62
|
+
top: 0,
|
|
63
|
+
backgroundColor,
|
|
64
|
+
elevation: 10,
|
|
65
|
+
zIndex: 100,
|
|
66
|
+
height: "100%",
|
|
67
|
+
overflow: "hidden",
|
|
68
|
+
},
|
|
69
|
+
modalWrapper: {
|
|
70
|
+
backgroundColor: BG_COLOR_DEFAULT_BLACK,
|
|
71
|
+
},
|
|
72
|
+
collapsedModalBg: {
|
|
73
|
+
backgroundColor: BG_COLOR_DEFAULT_BLACK,
|
|
74
|
+
},
|
|
75
|
+
modalContent: {
|
|
76
|
+
flex: 1,
|
|
77
|
+
alignItems: "center",
|
|
78
|
+
},
|
|
79
|
+
modalContentPiP: {
|
|
80
|
+
flex: 1,
|
|
81
|
+
alignItems: "flex-start",
|
|
82
|
+
},
|
|
83
|
+
expandedContentContainer: {
|
|
84
|
+
justifyContent: "flex-start",
|
|
85
|
+
backgroundColor: BG_COLOR_DEFAULT_TRANSPARENT,
|
|
86
|
+
zIndex: 1,
|
|
87
|
+
flex: 0,
|
|
88
|
+
},
|
|
89
|
+
extraContentScrollView: {
|
|
90
|
+
width: "100%",
|
|
91
|
+
backgroundColor: BG_COLOR_DEFAULT_TRANSPARENT,
|
|
92
|
+
},
|
|
93
|
+
collapsedBarContainer: {
|
|
94
|
+
...StyleSheet.absoluteFillObject,
|
|
95
|
+
height: COLLAPSED_HEIGHT,
|
|
96
|
+
zIndex: 90,
|
|
97
|
+
justifyContent: "flex-end",
|
|
98
|
+
},
|
|
99
|
+
collapsedBarWrapper: {
|
|
100
|
+
flex: 1,
|
|
101
|
+
},
|
|
102
|
+
flexNone: {
|
|
103
|
+
...StyleSheet.absoluteFillObject,
|
|
104
|
+
flex: -1,
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
[backgroundColor, COLLAPSED_HEIGHT]
|
|
108
|
+
);
|
|
109
|
+
};
|