From c34c7fbe83bd9bb4c2a51e28fdf895edf4d12409 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Wed, 13 Aug 2025 15:27:47 +0200 Subject: [PATCH] feat: fade in the controls (instead of on/off toggle) --- components/video-player/controls/Controls.tsx | 107 +++++++++++------- .../controls/VideoTouchOverlay.tsx | 33 +++--- 2 files changed, 85 insertions(+), 55 deletions(-) diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx index 4b33ad6b..57c6790f 100644 --- a/components/video-player/controls/Controls.tsx +++ b/components/video-player/controls/Controls.tsx @@ -25,11 +25,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"; @@ -148,11 +150,35 @@ export const Controls: FC = ({ 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(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(); }, []); @@ -708,10 +734,10 @@ export const Controls: FC = ({ - = ({ 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"} @@ -803,34 +829,39 @@ export const Controls: FC = ({ - + - + {/* Brightness Control */} + + {/* Skip Backward */} {!Platform.isTV && ( = ({ position: "relative", justifyContent: "center", alignItems: "center", - opacity: showControls ? 1 : 0, }} > = ({ )} - + {/* Play/Pause Button */} + { togglePlay(); @@ -877,9 +906,6 @@ export const Controls: FC = ({ name={isPlaying ? "pause" : "play"} size={50} color='white' - style={{ - opacity: showControls ? 1 : 0, - }} /> ) : ( @@ -887,6 +913,7 @@ export const Controls: FC = ({ + {/* Skip Forward */} {!Platform.isTV && ( = ({ position: "relative", justifyContent: "center", alignItems: "center", - opacity: showControls ? 1 : 0, }} > @@ -912,21 +938,23 @@ export const Controls: FC = ({ )} + + {/* Volume/Audio Control */} - + - = ({ left: settings?.safeAreaInControlsEnabled ? insets.left : 0, bottom: settings?.safeAreaInControlsEnabled ? insets.bottom : 0, }, + animatedControlsStyle, ]} className={"flex flex-col px-2"} onTouchStart={handleControlsInteraction} @@ -949,7 +978,6 @@ export const Controls: FC = ({ style={{ flexDirection: "column", alignSelf: "flex-end", // Shrink height based on content - opacity: showControls ? 1 : 0, }} pointerEvents={showControls ? "box-none" : "none"} > @@ -998,9 +1026,6 @@ export const Controls: FC = ({ @@ -1040,7 +1065,7 @@ export const Controls: FC = ({ - + )} {settings.maxAutoPlayEpisodeCount.value !== -1 && ( diff --git a/components/video-player/controls/VideoTouchOverlay.tsx b/components/video-player/controls/VideoTouchOverlay.tsx index 85385acf..709c1de6 100644 --- a/components/video-player/controls/VideoTouchOverlay.tsx +++ b/components/video-player/controls/VideoTouchOverlay.tsx @@ -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 ( - ); };