Files
streamyfin/components/tv/TVSkipSegmentCard.tsx
2026-02-01 14:03:20 +01:00

118 lines
3.0 KiB
TypeScript

import { Ionicons } from "@expo/vector-icons";
import type { FC } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Pressable, Animated as RNAnimated, StyleSheet } from "react-native";
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { Text } from "@/components/common/Text";
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
export interface TVSkipSegmentCardProps {
show: boolean;
onPress: () => void;
type: "intro" | "credits";
/** Whether controls are visible - affects card position */
controlsVisible?: boolean;
}
// Position constants - same as TVNextEpisodeCountdown (they're mutually exclusive)
const BOTTOM_WITH_CONTROLS = 300;
const BOTTOM_WITHOUT_CONTROLS = 120;
export const TVSkipSegmentCard: FC<TVSkipSegmentCardProps> = ({
show,
onPress,
type,
controlsVisible = false,
}) => {
const { t } = useTranslation();
const { focused, handleFocus, handleBlur, animatedStyle } =
useTVFocusAnimation({
scaleAmount: 1.1,
duration: 120,
});
// Animated position based on controls visibility
const bottomPosition = useSharedValue(
controlsVisible ? BOTTOM_WITH_CONTROLS : BOTTOM_WITHOUT_CONTROLS,
);
useEffect(() => {
const target = controlsVisible
? BOTTOM_WITH_CONTROLS
: BOTTOM_WITHOUT_CONTROLS;
bottomPosition.value = withTiming(target, {
duration: 300,
easing: Easing.out(Easing.quad),
});
}, [controlsVisible, bottomPosition]);
const containerAnimatedStyle = useAnimatedStyle(() => ({
bottom: bottomPosition.value,
}));
const labelText =
type === "intro" ? t("player.skip_intro") : t("player.skip_credits");
if (!show) return null;
return (
<Animated.View
style={[styles.container, containerAnimatedStyle]}
pointerEvents='box-none'
>
<Pressable
onPress={onPress}
onFocus={handleFocus}
onBlur={handleBlur}
hasTVPreferredFocus={true}
>
<RNAnimated.View
style={[
styles.button,
animatedStyle,
{
backgroundColor: focused
? "rgba(255,255,255,0.3)"
: "rgba(255,255,255,0.1)",
borderColor: focused
? "rgba(255,255,255,0.8)"
: "rgba(255,255,255,0.2)",
},
]}
>
<Ionicons name='play-forward' size={20} color='#fff' />
<Text style={styles.label}>{labelText}</Text>
</RNAnimated.View>
</Pressable>
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
position: "absolute",
right: 80,
zIndex: 100,
},
button: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 10,
paddingHorizontal: 18,
borderRadius: 12,
borderWidth: 2,
gap: 8,
},
label: {
fontSize: 20,
color: "#fff",
fontWeight: "600",
},
});