diff --git a/src/components/player/components/PauseOverlay.tsx b/src/components/player/components/PauseOverlay.tsx index 31cd49a2..15e9f32a 100644 --- a/src/components/player/components/PauseOverlay.tsx +++ b/src/components/player/components/PauseOverlay.tsx @@ -1,259 +1,449 @@ import React, { useState, useRef, useEffect } from 'react'; -import { View, Text, TouchableOpacity, ScrollView, Animated, StyleSheet } from 'react-native'; +import { + View, + Text, + TouchableOpacity, + ScrollView, + Animated, + StyleSheet, +} from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import FastImage from '@d11/react-native-fast-image'; import { MaterialIcons } from '@expo/vector-icons'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useTranslation } from 'react-i18next'; // Delay before showing pause overlay (in milliseconds) const PAUSE_OVERLAY_DELAY = 5000; interface PauseOverlayProps { - visible: boolean; - onClose: () => void; - title: string; - episodeTitle?: string; - season?: number; - episode?: number; - year?: string | number; - type: string; - description: string; - cast: any[]; - screenDimensions: { width: number, height: number }; + visible: boolean; + onClose: () => void; + title: string; + episodeTitle?: string; + season?: number; + episode?: number; + year?: string | number; + type: string; + description: string; + cast: any[]; + screenDimensions: { width: number; height: number }; } export const PauseOverlay: React.FC = ({ - visible, - onClose, - title, - episodeTitle, - season, - episode, - year, - type, - description, - cast, - screenDimensions + visible, + onClose, + title, + episodeTitle, + season, + episode, + year, + type, + description, + cast, + screenDimensions, }) => { - const insets = useSafeAreaInsets(); + const insets = useSafeAreaInsets(); - // Internal state to track if overlay should actually be shown (after delay) - const [shouldShow, setShouldShow] = useState(false); - const delayTimerRef = useRef(null); + // Internal state to track if overlay should actually be shown (after delay) + const [shouldShow, setShouldShow] = useState(false); + const delayTimerRef = useRef(null); + const { t } = useTranslation(); + // Handle delay logic - show overlay only after paused for 5 seconds + useEffect(() => { + if (visible) { + // Start timer to show overlay after delay + delayTimerRef.current = setTimeout(() => { + setShouldShow(true); + }, PAUSE_OVERLAY_DELAY); + } else { + // Immediately hide when not paused + if (delayTimerRef.current) { + clearTimeout(delayTimerRef.current); + delayTimerRef.current = null; + } + setShouldShow(false); + } - // Handle delay logic - show overlay only after paused for 5 seconds - useEffect(() => { - if (visible) { - // Start timer to show overlay after delay - delayTimerRef.current = setTimeout(() => { - setShouldShow(true); - }, PAUSE_OVERLAY_DELAY); - } else { - // Immediately hide when not paused - if (delayTimerRef.current) { - clearTimeout(delayTimerRef.current); - delayTimerRef.current = null; - } - setShouldShow(false); - } + return () => { + if (delayTimerRef.current) { + clearTimeout(delayTimerRef.current); + delayTimerRef.current = null; + } + }; + }, [visible]); - return () => { - if (delayTimerRef.current) { - clearTimeout(delayTimerRef.current); - delayTimerRef.current = null; - } - }; - }, [visible]); + // Internal Animation State + const pauseOverlayOpacity = useRef( + new Animated.Value(shouldShow ? 1 : 0), + ).current; + const pauseOverlayTranslateY = useRef(new Animated.Value(12)).current; + const metadataOpacity = useRef(new Animated.Value(1)).current; + const metadataScale = useRef(new Animated.Value(1)).current; - // Internal Animation State - const pauseOverlayOpacity = useRef(new Animated.Value(shouldShow ? 1 : 0)).current; - const pauseOverlayTranslateY = useRef(new Animated.Value(12)).current; - const metadataOpacity = useRef(new Animated.Value(1)).current; - const metadataScale = useRef(new Animated.Value(1)).current; + // Cast Details State + const [selectedCastMember, setSelectedCastMember] = useState(null); + const [showCastDetails, setShowCastDetails] = useState(false); + const castDetailsOpacity = useRef(new Animated.Value(0)).current; + const castDetailsScale = useRef(new Animated.Value(0.95)).current; - // Cast Details State - const [selectedCastMember, setSelectedCastMember] = useState(null); - const [showCastDetails, setShowCastDetails] = useState(false); - const castDetailsOpacity = useRef(new Animated.Value(0)).current; - const castDetailsScale = useRef(new Animated.Value(0.95)).current; + useEffect(() => { + Animated.timing(pauseOverlayOpacity, { + toValue: shouldShow ? 1 : 0, + duration: 250, + useNativeDriver: true, + }).start(); + }, [shouldShow]); - useEffect(() => { - Animated.timing(pauseOverlayOpacity, { - toValue: shouldShow ? 1 : 0, - duration: 250, - useNativeDriver: true - }).start(); - }, [shouldShow]); + if (!shouldShow && !showCastDetails) return null; - if (!shouldShow && !showCastDetails) return null; + return ( + + + {/* Horizontal Fade */} + + + + - return ( - - - {/* Horizontal Fade */} - - - - + + {showCastDetails && selectedCastMember ? ( + + + { + Animated.parallel([ + Animated.timing(castDetailsOpacity, { + toValue: 0, + duration: 250, + useNativeDriver: true, + }), + Animated.timing(castDetailsScale, { + toValue: 0.95, + duration: 250, + useNativeDriver: true, + }), + ]).start(() => { + setShowCastDetails(false); + setSelectedCastMember(null); + Animated.parallel([ + Animated.timing(metadataOpacity, { + toValue: 1, + duration: 400, + useNativeDriver: true, + }), + Animated.spring(metadataScale, { + toValue: 1, + tension: 80, + friction: 8, + useNativeDriver: true, + }), + ]).start(); + }); + }} + > + + + {t('pause_overlay_screen.back_to_details')} + + - - {showCastDetails && selectedCastMember ? ( - - - { - Animated.parallel([ - Animated.timing(castDetailsOpacity, { toValue: 0, duration: 250, useNativeDriver: true }), - Animated.timing(castDetailsScale, { toValue: 0.95, duration: 250, useNativeDriver: true }) - ]).start(() => { - setShowCastDetails(false); - setSelectedCastMember(null); - Animated.parallel([ - Animated.timing(metadataOpacity, { toValue: 1, duration: 400, useNativeDriver: true }), - Animated.spring(metadataScale, { toValue: 1, tension: 80, friction: 8, useNativeDriver: true }) - ]).start(); - }); - }} - > - - Back to details - - - - {selectedCastMember.profile_path && ( - - - - )} - - - {selectedCastMember.name} - - {selectedCastMember.character && ( - - as {selectedCastMember.character} - - )} - {selectedCastMember.biography && ( - - {selectedCastMember.biography} - - )} - - - - - ) : ( - - - You're watching - - {title} - - {!!year && ( - - {`${year}${type === 'series' && season && episode ? ` • S${season}E${episode}` : ''}`} - - )} - {!!episodeTitle && ( - - {episodeTitle} - - )} - {description && ( - - {description} - - )} - {cast && cast.length > 0 && ( - - Cast - - {cast.slice(0, 6).map((castMember: any, index: number) => ( - { - setSelectedCastMember(castMember); - Animated.parallel([ - Animated.timing(metadataOpacity, { toValue: 0, duration: 250, useNativeDriver: true }), - Animated.timing(metadataScale, { toValue: 0.95, duration: 250, useNativeDriver: true }) - ]).start(() => { - setShowCastDetails(true); - Animated.parallel([ - Animated.timing(castDetailsOpacity, { toValue: 1, duration: 400, useNativeDriver: true }), - Animated.spring(castDetailsScale, { toValue: 1, tension: 80, friction: 8, useNativeDriver: true }) - ]).start(); - }); - }} - > - - {castMember.name} - - - ))} - - - )} - - - )} - - - - ); + + {selectedCastMember.profile_path && ( + + + + )} + + + {selectedCastMember.name} + + {selectedCastMember.character && ( + + {t('pause_overlay_screen.as')} {selectedCastMember.character} + + )} + {selectedCastMember.biography && ( + + {selectedCastMember.biography} + + )} + + + + + ) : ( + + + + {t('pause_overlay_screen.you_are_watching')} + + + {title} + + {!!year && ( + + {`${year}${type === 'series' && season && episode ? ` • S${season}E${episode}` : ''}`} + + )} + {!!episodeTitle && ( + + {episodeTitle} + + )} + {description && ( + + {description} + + )} + {cast && cast.length > 0 && ( + + + {t('pause_overlay_screen.cast')} + + + {cast.slice(0, 6).map((castMember: any, index: number) => ( + { + setSelectedCastMember(castMember); + Animated.parallel([ + Animated.timing(metadataOpacity, { + toValue: 0, + duration: 250, + useNativeDriver: true, + }), + Animated.timing(metadataScale, { + toValue: 0.95, + duration: 250, + useNativeDriver: true, + }), + ]).start(() => { + setShowCastDetails(true); + Animated.parallel([ + Animated.timing(castDetailsOpacity, { + toValue: 1, + duration: 400, + useNativeDriver: true, + }), + Animated.spring(castDetailsScale, { + toValue: 1, + tension: 80, + friction: 8, + useNativeDriver: true, + }), + ]).start(); + }); + }} + > + + {castMember.name} + + + ))} + + + )} + + + )} + + + + ); }; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 9de40bf3..4b0142f4 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1565,13 +1565,19 @@ "later": "Later", "dismiss": "Dismiss" }, - "major_update_screen":{ - "major_update_title":"Major update available", - "latest":"Latest:", - "downloading":"Downloading...", - "update_now":"Update Now", - "later":"Later", - "dismiss":"Dismiss", - "view_release":"View Release" - } + "major_update_screen": { + "major_update_title": "Major update available", + "latest": "Latest:", + "downloading": "Downloading...", + "update_now": "Update Now", + "later": "Later", + "dismiss": "Dismiss", + "view_release": "View Release" + }, + "pause_overlay_screen": { + "you_are_watching": "You're watching", + "back_to_details": "Back to Details", + "as":"as", + "cast":"Cast" + } } diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index a654b5ba..6a1da3d5 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -1573,5 +1573,11 @@ "later": "Dopo", "dismiss": "Ignora", "view_release": "Vedi Release" + }, + "pause_overlay_screen": { + "you_are_watching": "Stai guardando", + "back_to_details": "Torna ai dettagli", + "as": "come", + "cast": "Cast" } }