mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
Merge branch 'feat/fade-in-controls' into develop
This commit is contained in:
@@ -24,11 +24,13 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Slider } from "react-native-awesome-slider";
|
||||
import {
|
||||
import Animated, {
|
||||
runOnJS,
|
||||
type SharedValue,
|
||||
useAnimatedReaction,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Text } from "@/components/common/Text";
|
||||
@@ -147,11 +149,35 @@ export const Controls: FC<Props> = ({
|
||||
const min = useSharedValue(0);
|
||||
const max = useSharedValue(item.RunTimeTicks || 0);
|
||||
|
||||
// Animated opacity for smooth transitions
|
||||
const controlsOpacity = useSharedValue(showControls ? 1 : 0);
|
||||
|
||||
const wasPlayingRef = useRef(false);
|
||||
const lastProgressRef = useRef<number>(0);
|
||||
|
||||
const lightHapticFeedback = useHaptic("light");
|
||||
|
||||
// Animate controls opacity when showControls changes
|
||||
useEffect(() => {
|
||||
controlsOpacity.value = withTiming(showControls ? 1 : 0, {
|
||||
duration: 300,
|
||||
});
|
||||
}, [showControls, controlsOpacity]);
|
||||
|
||||
// Animated styles for controls
|
||||
const animatedControlsStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
opacity: controlsOpacity.value,
|
||||
};
|
||||
});
|
||||
|
||||
// Animated style for black overlay (75% opacity when visible)
|
||||
const animatedOverlayStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
opacity: controlsOpacity.value * 0.75,
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
prefetchAllTrickplayImages();
|
||||
}, []);
|
||||
@@ -701,10 +727,10 @@ export const Controls: FC<Props> = ({
|
||||
<VideoTouchOverlay
|
||||
screenWidth={screenWidth}
|
||||
screenHeight={screenHeight}
|
||||
showControls={showControls}
|
||||
onToggleControls={toggleControls}
|
||||
animatedStyle={animatedOverlayStyle}
|
||||
/>
|
||||
<View
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
position: "absolute",
|
||||
@@ -713,8 +739,8 @@ export const Controls: FC<Props> = ({
|
||||
width: settings?.safeAreaInControlsEnabled
|
||||
? screenWidth - insets.left - insets.right
|
||||
: screenWidth,
|
||||
opacity: showControls ? 1 : 0,
|
||||
},
|
||||
animatedControlsStyle,
|
||||
]}
|
||||
pointerEvents={showControls ? "auto" : "none"}
|
||||
className={"flex flex-row w-full pt-2"}
|
||||
@@ -793,33 +819,39 @@ export const Controls: FC<Props> = ({
|
||||
<Ionicons name='close' size={24} color='white' />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "50%", // Center vertically
|
||||
left: settings?.safeAreaInControlsEnabled ? insets.left : 0,
|
||||
right: settings?.safeAreaInControlsEnabled ? insets.right : 0,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
transform: [{ translateY: -22.5 }], // Adjust for the button's height (half of 45)
|
||||
paddingHorizontal: "28%", // Add some padding to the left and right
|
||||
}}
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
position: "absolute",
|
||||
top: "50%", // Center vertically
|
||||
left: settings?.safeAreaInControlsEnabled ? insets.left : 0,
|
||||
right: settings?.safeAreaInControlsEnabled ? insets.right : 0,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
transform: [{ translateY: -22.5 }], // Adjust for the button's height (half of 45)
|
||||
paddingHorizontal: 17,
|
||||
},
|
||||
animatedControlsStyle,
|
||||
]}
|
||||
pointerEvents={showControls ? "box-none" : "none"}
|
||||
>
|
||||
{/* Brightness Control */}
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: 50,
|
||||
height: 50,
|
||||
alignItems: "center",
|
||||
transform: [{ rotate: "270deg" }], // Rotate the slider to make it vertical
|
||||
left: 0,
|
||||
bottom: 30,
|
||||
opacity: showControls ? 1 : 0,
|
||||
justifyContent: "center",
|
||||
transform: [{ rotate: "270deg" }],
|
||||
}}
|
||||
>
|
||||
<BrightnessSlider />
|
||||
</View>
|
||||
|
||||
{/* Skip Backward */}
|
||||
{!Platform.isTV && (
|
||||
<TouchableOpacity onPress={handleSkipBackward}>
|
||||
<View
|
||||
@@ -827,7 +859,6 @@ export const Controls: FC<Props> = ({
|
||||
position: "relative",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
opacity: showControls ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<Ionicons
|
||||
@@ -853,9 +884,8 @@ export const Controls: FC<Props> = ({
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={Platform.isTV ? { flex: 1, alignItems: "center" } : {}}
|
||||
>
|
||||
{/* Play/Pause Button */}
|
||||
<View style={{ alignItems: "center" }}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
togglePlay();
|
||||
@@ -866,9 +896,6 @@ export const Controls: FC<Props> = ({
|
||||
name={isPlaying ? "pause" : "play"}
|
||||
size={50}
|
||||
color='white'
|
||||
style={{
|
||||
opacity: showControls ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Loader size={"large"} />
|
||||
@@ -876,6 +903,7 @@ export const Controls: FC<Props> = ({
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Skip Forward */}
|
||||
{!Platform.isTV && (
|
||||
<TouchableOpacity onPress={handleSkipForward}>
|
||||
<View
|
||||
@@ -883,7 +911,6 @@ export const Controls: FC<Props> = ({
|
||||
position: "relative",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
opacity: showControls ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<Ionicons name='refresh-outline' size={50} color='white' />
|
||||
@@ -901,21 +928,23 @@ export const Controls: FC<Props> = ({
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Volume/Audio Control */}
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: 50,
|
||||
height: 50,
|
||||
alignItems: "center",
|
||||
transform: [{ rotate: "270deg" }], // Rotate the slider to make it vertical
|
||||
bottom: 30,
|
||||
right: 0,
|
||||
justifyContent: "center",
|
||||
transform: [{ rotate: "270deg" }],
|
||||
opacity: showAudioSlider || showControls ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<AudioSlider setVisibility={setShowAudioSlider} />
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
<View
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
position: "absolute",
|
||||
@@ -923,6 +952,7 @@ export const Controls: FC<Props> = ({
|
||||
left: settings?.safeAreaInControlsEnabled ? insets.left : 0,
|
||||
bottom: settings?.safeAreaInControlsEnabled ? insets.bottom : 0,
|
||||
},
|
||||
animatedControlsStyle,
|
||||
]}
|
||||
className={"flex flex-col px-2"}
|
||||
onTouchStart={handleControlsInteraction}
|
||||
@@ -938,7 +968,6 @@ export const Controls: FC<Props> = ({
|
||||
style={{
|
||||
flexDirection: "column",
|
||||
alignSelf: "flex-end", // Shrink height based on content
|
||||
opacity: showControls ? 1 : 0,
|
||||
}}
|
||||
pointerEvents={showControls ? "box-none" : "none"}
|
||||
>
|
||||
@@ -987,9 +1016,6 @@ export const Controls: FC<Props> = ({
|
||||
</View>
|
||||
<View
|
||||
className={"flex flex-col-reverse rounded-lg items-center my-2"}
|
||||
style={{
|
||||
opacity: showControls ? 1 : 0,
|
||||
}}
|
||||
pointerEvents={showControls ? "box-none" : "none"}
|
||||
>
|
||||
<View className={"flex flex-col w-full shrink"}>
|
||||
@@ -1029,7 +1055,7 @@ export const Controls: FC<Props> = ({
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</>
|
||||
)}
|
||||
{settings.maxAutoPlayEpisodeCount.value !== -1 && (
|
||||
|
||||
@@ -1,38 +1,43 @@
|
||||
import { Pressable } from "react-native";
|
||||
import Animated, { type AnimatedStyle } from "react-native-reanimated";
|
||||
import { useTapDetection } from "./useTapDetection";
|
||||
|
||||
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
||||
|
||||
interface Props {
|
||||
screenWidth: number;
|
||||
screenHeight: number;
|
||||
showControls: boolean;
|
||||
onToggleControls: () => void;
|
||||
animatedStyle: AnimatedStyle;
|
||||
}
|
||||
|
||||
export const VideoTouchOverlay = ({
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
showControls,
|
||||
onToggleControls,
|
||||
animatedStyle,
|
||||
}: Props) => {
|
||||
const { handleTouchStart, handleTouchEnd } = useTapDetection({
|
||||
onValidTap: onToggleControls,
|
||||
});
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
<AnimatedPressable
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
backgroundColor: "black",
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
opacity: showControls ? 0.75 : 0,
|
||||
}}
|
||||
style={[
|
||||
{
|
||||
position: "absolute",
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
backgroundColor: "black",
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
animatedStyle,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user