Merge branch 'feat/fade-in-controls' into develop

This commit is contained in:
Fredrik Burmester
2025-08-18 09:16:38 +02:00
2 changed files with 86 additions and 55 deletions

View File

@@ -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 && (

View File

@@ -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,
]}
/>
);
};