import React, { useEffect, useRef, forwardRef, useImperativeHandle, useMemo } from 'react'; import { View, StyleSheet, Dimensions, StatusBar, Platform, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { LinearGradient } from 'expo-linear-gradient'; import Animated, { useSharedValue, useAnimatedStyle, withRepeat, withTiming, withSequence, withDelay, Easing, interpolate, cancelAnimation, runOnJS, SharedValue, } from 'react-native-reanimated'; import { useTheme } from '../../contexts/ThemeContext'; const { width, height } = Dimensions.get('window'); // Responsive breakpoints const BREAKPOINTS = { phone: 0, tablet: 768, largeTablet: 1024, tv: 1440, }; interface MetadataLoadingScreenProps { type?: 'movie' | 'series'; onExitComplete?: () => void; } export interface MetadataLoadingScreenRef { exit: () => void; } // Animated shimmer skeleton component const ShimmerSkeleton = ({ width: elementWidth, height: elementHeight, borderRadius = 8, marginBottom = 8, style = {}, delay = 0, shimmerProgress, baseColor, highlightColor, }: { width: number | string; height: number; borderRadius?: number; marginBottom?: number; style?: any; delay?: number; shimmerProgress: SharedValue; baseColor: string; highlightColor: string; }) => { const animatedStyle = useAnimatedStyle(() => { const translateX = interpolate( shimmerProgress.value, [0, 1], [-width, width] ); return { transform: [{ translateX }], }; }); return ( ); }; export const MetadataLoadingScreen = forwardRef(({ type = 'movie', onExitComplete }, ref) => { const { currentTheme } = useTheme(); // Responsive sizing const deviceWidth = Dimensions.get('window').width; const deviceType = useMemo(() => { if (deviceWidth >= BREAKPOINTS.tv) return 'tv'; if (deviceWidth >= BREAKPOINTS.largeTablet) return 'largeTablet'; if (deviceWidth >= BREAKPOINTS.tablet) return 'tablet'; return 'phone'; }, [deviceWidth]); const isTV = deviceType === 'tv'; const isLargeTablet = deviceType === 'largeTablet'; const isTablet = deviceType === 'tablet'; const horizontalPadding = isTV ? 48 : isLargeTablet ? 32 : isTablet ? 24 : 16; // Shimmer animation const shimmerProgress = useSharedValue(0); // Staggered fade-in for sections const heroOpacity = useSharedValue(0); const contentOpacity = useSharedValue(0); const castOpacity = useSharedValue(0); // Exit animation value const exitProgress = useSharedValue(0); // Colors for skeleton const baseColor = currentTheme.colors.elevation1 || 'rgba(255,255,255,0.08)'; const highlightColor = 'rgba(255,255,255,0.12)'; // Exit animation function const exit = () => { exitProgress.value = withTiming(1, { duration: 200, easing: Easing.bezier(0.25, 0.1, 0.25, 1.0), }, (finished) => { 'worklet'; if (finished && onExitComplete) { runOnJS(onExitComplete)(); } }); }; // Expose exit method through ref useImperativeHandle(ref, () => ({ exit, })); useEffect(() => { // Start shimmer animation shimmerProgress.value = withRepeat( withTiming(1, { duration: 1500, easing: Easing.bezier(0.25, 0.1, 0.25, 1.0) }), -1, // infinite false ); // Staggered entrance animations heroOpacity.value = withTiming(1, { duration: 300 }); contentOpacity.value = withDelay(100, withTiming(1, { duration: 300 })); castOpacity.value = withDelay(200, withTiming(1, { duration: 300 })); return () => { cancelAnimation(shimmerProgress); cancelAnimation(heroOpacity); cancelAnimation(contentOpacity); cancelAnimation(castOpacity); }; }, []); // Animated styles const containerStyle = useAnimatedStyle(() => ({ opacity: interpolate(exitProgress.value, [0, 1], [1, 0]), transform: [ { scale: interpolate(exitProgress.value, [0, 1], [1, 0.98]) }, ], })); const heroStyle = useAnimatedStyle(() => ({ opacity: heroOpacity.value, })); const contentStyle = useAnimatedStyle(() => ({ opacity: contentOpacity.value, transform: [ { translateY: interpolate(contentOpacity.value, [0, 1], [10, 0]) }, ], })); const castStyle = useAnimatedStyle(() => ({ opacity: castOpacity.value, transform: [ { translateY: interpolate(castOpacity.value, [0, 1], [10, 0]) }, ], })); return ( {/* Hero Section Skeleton */} {/* Back Button Skeleton */} {/* Gradient overlay */} {/* Hero bottom content - Matches HeroSection.tsx structure */} {/* Logo placeholder - Centered and larger */} {/* Watch Progress Placeholder - Centered Glass Bar */} {/* Genre Info Row - Centered */} {/* Action buttons row - Play, Save, Collection, Rates */} {/* Play Button */} {/* Save Button */} {/* Collection Icon */} {/* Ratings Icon (if series) - Always show for skeleton consistency */} {/* Content Section */} {/* Description skeleton */} {/* Cast Section */} {[1, 2, 3, 4, 5].map((item) => ( ))} {/* Episodes/Recommendations Section */} {type === 'series' ? ( {/* Season selector */} {/* Episode cards */} {[1, 2, 3].map((item) => ( ))} ) : ( {[1, 2, 3, 4].map((item) => ( ))} )} ); }); const styles = StyleSheet.create({ container: { flex: 1, }, content: { flex: 1, }, heroSection: { position: 'relative', }, heroOverlay: { ...StyleSheet.absoluteFillObject, justifyContent: 'flex-end', }, heroBottomContent: { paddingBottom: 20, }, metaRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, }, buttonsRow: { flexDirection: 'row', alignItems: 'center', }, contentSection: { paddingTop: 16, }, descriptionSection: { marginBottom: 24, }, castSection: { marginBottom: 24, }, castRow: { flexDirection: 'row', }, castItem: { alignItems: 'center', marginRight: 16, }, episodesSection: { marginBottom: 24, }, episodeList: { gap: 16, }, episodeCard: { flexDirection: 'row', gap: 12, }, episodeInfo: { flex: 1, justifyContent: 'center', }, recommendationsSection: { marginBottom: 24, }, posterRow: { flexDirection: 'row', }, }); export default MetadataLoadingScreen;