mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-30 22:39:21 +00:00
imrpoved metadataloadingscreen UI
This commit is contained in:
parent
51064a65b2
commit
4ac45a041a
1 changed files with 479 additions and 183 deletions
|
|
@ -1,19 +1,37 @@
|
|||
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||
import React, { useEffect, useRef, forwardRef, useImperativeHandle, useMemo } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
Animated,
|
||||
StatusBar,
|
||||
Easing,
|
||||
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,
|
||||
} 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;
|
||||
|
|
@ -23,44 +41,120 @@ 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: Animated.SharedValue<number>;
|
||||
baseColor: string;
|
||||
highlightColor: string;
|
||||
}) => {
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
const translateX = interpolate(
|
||||
shimmerProgress.value,
|
||||
[0, 1],
|
||||
[-width, width]
|
||||
);
|
||||
return {
|
||||
transform: [{ translateX }],
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={[
|
||||
{
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
borderRadius,
|
||||
marginBottom,
|
||||
backgroundColor: baseColor,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
style
|
||||
]}>
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
animatedStyle,
|
||||
]}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
'transparent',
|
||||
highlightColor,
|
||||
highlightColor,
|
||||
'transparent',
|
||||
]}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={[StyleSheet.absoluteFill, { width: width * 2 }]}
|
||||
/>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const MetadataLoadingScreen = forwardRef<MetadataLoadingScreenRef, MetadataLoadingScreenProps>(({
|
||||
type = 'movie',
|
||||
onExitComplete
|
||||
}, ref) => {
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
// Animation values - shimmer removed
|
||||
|
||||
// Scene transition animation values (matching tab navigator)
|
||||
const sceneOpacity = useRef(new Animated.Value(0)).current;
|
||||
const sceneScale = useRef(new Animated.Value(0.95)).current;
|
||||
const sceneTranslateY = useRef(new Animated.Value(8)).current;
|
||||
|
||||
// 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 = () => {
|
||||
const exitAnimation = Animated.parallel([
|
||||
Animated.timing(sceneOpacity, {
|
||||
toValue: 0,
|
||||
duration: 100,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(sceneScale, {
|
||||
toValue: 0.95,
|
||||
duration: 100,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(sceneTranslateY, {
|
||||
toValue: 8,
|
||||
duration: 100,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
exitAnimation.start(() => {
|
||||
onExitComplete?.();
|
||||
exitProgress.value = withTiming(1, {
|
||||
duration: 200,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
||||
}, (finished) => {
|
||||
'worklet';
|
||||
if (finished && onExitComplete) {
|
||||
runOnJS(onExitComplete)();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -70,70 +164,57 @@ export const MetadataLoadingScreen = forwardRef<MetadataLoadingScreenRef, Metada
|
|||
}));
|
||||
|
||||
useEffect(() => {
|
||||
// Scene entrance animation (matching tab navigator)
|
||||
const sceneAnimation = Animated.parallel([
|
||||
Animated.timing(sceneOpacity, {
|
||||
toValue: 1,
|
||||
duration: 100,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
||||
useNativeDriver: true,
|
||||
// Start shimmer animation
|
||||
shimmerProgress.value = withRepeat(
|
||||
withTiming(1, {
|
||||
duration: 1500,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0)
|
||||
}),
|
||||
Animated.timing(sceneScale, {
|
||||
toValue: 1,
|
||||
duration: 100,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(sceneTranslateY, {
|
||||
toValue: 0,
|
||||
duration: 100,
|
||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]);
|
||||
-1, // infinite
|
||||
false
|
||||
);
|
||||
|
||||
sceneAnimation.start();
|
||||
|
||||
// Shimmer effect removed
|
||||
// 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 () => {
|
||||
sceneAnimation.stop();
|
||||
cancelAnimation(shimmerProgress);
|
||||
cancelAnimation(heroOpacity);
|
||||
cancelAnimation(contentOpacity);
|
||||
cancelAnimation(castOpacity);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Shimmer translate removed
|
||||
// Animated styles
|
||||
const containerStyle = useAnimatedStyle(() => ({
|
||||
opacity: interpolate(exitProgress.value, [0, 1], [1, 0]),
|
||||
transform: [
|
||||
{ scale: interpolate(exitProgress.value, [0, 1], [1, 0.98]) },
|
||||
],
|
||||
}));
|
||||
|
||||
const SkeletonElement = ({
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
borderRadius = 8,
|
||||
marginBottom = 8,
|
||||
style = {},
|
||||
}: {
|
||||
width: number | string;
|
||||
height: number;
|
||||
borderRadius?: number;
|
||||
marginBottom?: number;
|
||||
style?: any;
|
||||
}) => (
|
||||
<View style={[
|
||||
{
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
borderRadius,
|
||||
marginBottom,
|
||||
backgroundColor: currentTheme.colors.card,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
style
|
||||
]}>
|
||||
{/* Pulsating overlay removed */}
|
||||
{/* Shimmer overlay removed */}
|
||||
</View>
|
||||
);
|
||||
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 (
|
||||
<SafeAreaView
|
||||
<SafeAreaView
|
||||
style={[styles.container, {
|
||||
backgroundColor: currentTheme.colors.darkBackground,
|
||||
}]}
|
||||
|
|
@ -144,107 +225,325 @@ export const MetadataLoadingScreen = forwardRef<MetadataLoadingScreenRef, Metada
|
|||
backgroundColor="transparent"
|
||||
barStyle="light-content"
|
||||
/>
|
||||
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.content,
|
||||
{
|
||||
opacity: sceneOpacity,
|
||||
transform: [
|
||||
{ scale: sceneScale },
|
||||
{ translateY: sceneTranslateY }
|
||||
],
|
||||
}
|
||||
]}
|
||||
>
|
||||
{/* Hero Skeleton */}
|
||||
<View style={styles.heroSection}>
|
||||
<SkeletonElement
|
||||
width="100%"
|
||||
height={height * 0.6}
|
||||
|
||||
<Animated.View style={[styles.content, containerStyle]}>
|
||||
{/* Hero Section Skeleton */}
|
||||
<Animated.View style={[styles.heroSection, { height: height * 0.65 }, heroStyle]}>
|
||||
<ShimmerSkeleton
|
||||
width="100%"
|
||||
height={height * 0.65}
|
||||
borderRadius={0}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
|
||||
{/* Overlay content on hero */}
|
||||
|
||||
{/* Back Button Skeleton */}
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
top: Platform.OS === 'android' ? 40 : 50,
|
||||
left: isTablet ? 32 : 16,
|
||||
zIndex: 10
|
||||
}}>
|
||||
<ShimmerSkeleton
|
||||
width={40}
|
||||
height={40}
|
||||
borderRadius={20}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Gradient overlay */}
|
||||
<View style={styles.heroOverlay}>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
'transparent',
|
||||
'rgba(0,0,0,0.4)',
|
||||
'rgba(0,0,0,0.8)',
|
||||
'rgba(0,0,0,0.05)',
|
||||
'rgba(0,0,0,0.15)',
|
||||
'rgba(0,0,0,0.35)',
|
||||
'rgba(0,0,0,0.65)',
|
||||
currentTheme.colors.darkBackground,
|
||||
]}
|
||||
locations={[0, 0.3, 0.55, 0.75, 0.9, 1]}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
|
||||
{/* Bottom hero content skeleton */}
|
||||
<View style={styles.heroBottomContent}>
|
||||
<SkeletonElement width="60%" height={32} borderRadius={16} />
|
||||
<SkeletonElement width="40%" height={20} borderRadius={10} />
|
||||
<View style={styles.genresRow}>
|
||||
<SkeletonElement width={80} height={24} borderRadius={12} marginBottom={0} style={{ marginRight: 8 }} />
|
||||
<SkeletonElement width={90} height={24} borderRadius={12} marginBottom={0} style={{ marginRight: 8 }} />
|
||||
<SkeletonElement width={70} height={24} borderRadius={12} marginBottom={0} />
|
||||
{/* Hero bottom content - Matches HeroSection.tsx structure */}
|
||||
<View style={[styles.heroBottomContent, { paddingHorizontal: horizontalPadding }]}>
|
||||
{/* Logo placeholder - Centered and larger */}
|
||||
<View style={{ alignItems: 'center', width: '100%', marginBottom: 16 }}>
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 400 : isLargeTablet ? 300 : width * 0.65}
|
||||
height={isTV ? 120 : isLargeTablet ? 100 : 90}
|
||||
borderRadius={12}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.buttonsRow}>
|
||||
<SkeletonElement width={120} height={44} borderRadius={22} marginBottom={0} style={{ marginRight: 12 }} />
|
||||
<SkeletonElement width={100} height={44} borderRadius={22} marginBottom={0} />
|
||||
|
||||
{/* Watch Progress Placeholder - Centered Glass Bar */}
|
||||
<View style={{ alignItems: 'center', width: '100%', marginBottom: 16 }}>
|
||||
<ShimmerSkeleton
|
||||
width="75%"
|
||||
height={45} // Matches glass background height + padding
|
||||
borderRadius={12}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
style={{ opacity: 0.5 }} // Slight transparency for glass effect
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Genre Info Row - Centered */}
|
||||
<View style={[styles.metaRow, { justifyContent: 'center', marginBottom: 20 }]}>
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 60 : 50}
|
||||
height={12}
|
||||
borderRadius={6}
|
||||
marginBottom={0}
|
||||
style={{ marginRight: 8 }}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<View style={{ width: 4, height: 4, borderRadius: 2, backgroundColor: 'rgba(255,255,255,0.3)', marginRight: 8 }} />
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 80 : 70}
|
||||
height={12}
|
||||
borderRadius={6}
|
||||
marginBottom={0}
|
||||
style={{ marginRight: 8 }}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<View style={{ width: 4, height: 4, borderRadius: 2, backgroundColor: 'rgba(255,255,255,0.3)', marginRight: 8 }} />
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 50 : 40}
|
||||
height={12}
|
||||
borderRadius={6}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Action buttons row - Play, Save, Collection, Rates */}
|
||||
<View style={[styles.buttonsRow, { justifyContent: 'center', gap: 6 }]}>
|
||||
{/* Play Button */}
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 180 : isLargeTablet ? 160 : isTablet ? 150 : (width - 32 - 100 - 24) / 2} // Calc based on screen width
|
||||
height={isTV ? 52 : isLargeTablet ? 48 : 46}
|
||||
borderRadius={isTV ? 26 : 23}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
|
||||
{/* Save Button */}
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 180 : isLargeTablet ? 160 : isTablet ? 150 : (width - 32 - 100 - 24) / 2}
|
||||
height={isTV ? 52 : isLargeTablet ? 48 : 46}
|
||||
borderRadius={isTV ? 26 : 23}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
|
||||
{/* Collection Icon */}
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 52 : isLargeTablet ? 48 : 46}
|
||||
height={isTV ? 52 : isLargeTablet ? 48 : 46}
|
||||
borderRadius={isTV ? 26 : 23}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
|
||||
{/* Ratings Icon (if series) - Always show for skeleton consistency */}
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 52 : isLargeTablet ? 48 : 46}
|
||||
height={isTV ? 52 : isLargeTablet ? 48 : 46}
|
||||
borderRadius={isTV ? 26 : 23}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
{/* Content Section Skeletons */}
|
||||
<View style={styles.contentSection}>
|
||||
{/* Synopsis skeleton */}
|
||||
<View style={styles.synopsisSection}>
|
||||
<SkeletonElement width="30%" height={24} borderRadius={12} />
|
||||
<SkeletonElement width="100%" height={16} borderRadius={8} />
|
||||
<SkeletonElement width="95%" height={16} borderRadius={8} />
|
||||
<SkeletonElement width="80%" height={16} borderRadius={8} />
|
||||
{/* Content Section */}
|
||||
<Animated.View style={[styles.contentSection, { paddingHorizontal: horizontalPadding }, contentStyle]}>
|
||||
{/* Description skeleton */}
|
||||
<View style={styles.descriptionSection}>
|
||||
<ShimmerSkeleton
|
||||
width="100%"
|
||||
height={isTV ? 18 : 15}
|
||||
borderRadius={4}
|
||||
marginBottom={10}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<ShimmerSkeleton
|
||||
width="95%"
|
||||
height={isTV ? 18 : 15}
|
||||
borderRadius={4}
|
||||
marginBottom={10}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<ShimmerSkeleton
|
||||
width="75%"
|
||||
height={isTV ? 18 : 15}
|
||||
borderRadius={4}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
{/* Cast section skeleton */}
|
||||
<View style={styles.castSection}>
|
||||
<SkeletonElement width="20%" height={24} borderRadius={12} />
|
||||
<View style={styles.castRow}>
|
||||
{[1, 2, 3, 4].map((item) => (
|
||||
<View key={item} style={styles.castItem}>
|
||||
<SkeletonElement width={80} height={80} borderRadius={40} marginBottom={8} />
|
||||
<SkeletonElement width={60} height={12} borderRadius={6} marginBottom={4} />
|
||||
<SkeletonElement width={70} height={10} borderRadius={5} marginBottom={0} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{/* Cast Section */}
|
||||
<Animated.View style={[styles.castSection, { paddingHorizontal: horizontalPadding }, castStyle]}>
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 80 : 60}
|
||||
height={isTV ? 24 : 20}
|
||||
borderRadius={4}
|
||||
marginBottom={16}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<View style={styles.castRow}>
|
||||
{[1, 2, 3, 4, 5].map((item) => (
|
||||
<View key={item} style={styles.castItem}>
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 100 : isLargeTablet ? 90 : isTablet ? 85 : 80}
|
||||
height={isTV ? 100 : isLargeTablet ? 90 : isTablet ? 85 : 80}
|
||||
borderRadius={isTV ? 50 : isLargeTablet ? 45 : isTablet ? 42 : 40}
|
||||
marginBottom={8}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 70 : 60}
|
||||
height={isTV ? 14 : 12}
|
||||
borderRadius={4}
|
||||
marginBottom={4}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
{/* Episodes/Details skeleton based on type */}
|
||||
{type === 'series' ? (
|
||||
<View style={styles.episodesSection}>
|
||||
<SkeletonElement width="25%" height={24} borderRadius={12} />
|
||||
<SkeletonElement width={150} height={36} borderRadius={18} />
|
||||
{/* Episodes/Recommendations Section */}
|
||||
{type === 'series' ? (
|
||||
<Animated.View style={[styles.episodesSection, { paddingHorizontal: horizontalPadding }, castStyle]}>
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 120 : 100}
|
||||
height={isTV ? 24 : 20}
|
||||
borderRadius={4}
|
||||
marginBottom={16}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
{/* Season selector */}
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 180 : 140}
|
||||
height={isTV ? 40 : 36}
|
||||
borderRadius={20}
|
||||
marginBottom={20}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
{/* Episode cards */}
|
||||
<View style={styles.episodeList}>
|
||||
{[1, 2, 3].map((item) => (
|
||||
<View key={item} style={styles.episodeItem}>
|
||||
<SkeletonElement width={120} height={68} borderRadius={8} marginBottom={0} style={{ marginRight: 12 }} />
|
||||
<View key={item} style={styles.episodeCard}>
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 200 : isLargeTablet ? 180 : isTablet ? 160 : 140}
|
||||
height={isTV ? 112 : isLargeTablet ? 100 : isTablet ? 90 : 80}
|
||||
borderRadius={8}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<View style={styles.episodeInfo}>
|
||||
<SkeletonElement width="80%" height={16} borderRadius={8} />
|
||||
<SkeletonElement width="60%" height={14} borderRadius={7} />
|
||||
<SkeletonElement width="90%" height={12} borderRadius={6} />
|
||||
<ShimmerSkeleton
|
||||
width="80%"
|
||||
height={isTV ? 16 : 14}
|
||||
borderRadius={4}
|
||||
marginBottom={6}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<ShimmerSkeleton
|
||||
width="60%"
|
||||
height={isTV ? 14 : 12}
|
||||
borderRadius={4}
|
||||
marginBottom={0}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.detailsSection}>
|
||||
<SkeletonElement width="25%" height={24} borderRadius={12} />
|
||||
<View style={styles.detailsGrid}>
|
||||
<SkeletonElement width="48%" height={60} borderRadius={8} />
|
||||
<SkeletonElement width="48%" height={60} borderRadius={8} />
|
||||
</View>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<Animated.View style={[styles.recommendationsSection, { paddingHorizontal: horizontalPadding }, castStyle]}>
|
||||
<ShimmerSkeleton
|
||||
width={isTV ? 140 : 110}
|
||||
height={isTV ? 24 : 20}
|
||||
borderRadius={4}
|
||||
marginBottom={16}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
<View style={styles.posterRow}>
|
||||
{[1, 2, 3, 4].map((item) => (
|
||||
<ShimmerSkeleton
|
||||
key={item}
|
||||
width={isTV ? 140 : isLargeTablet ? 120 : isTablet ? 110 : 100}
|
||||
height={isTV ? 210 : isLargeTablet ? 180 : isTablet ? 165 : 150}
|
||||
borderRadius={8}
|
||||
marginBottom={0}
|
||||
style={{ marginRight: 12 }}
|
||||
shimmerProgress={shimmerProgress}
|
||||
baseColor={baseColor}
|
||||
highlightColor={highlightColor}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
</Animated.View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
|
@ -258,7 +557,6 @@ const styles = StyleSheet.create({
|
|||
flex: 1,
|
||||
},
|
||||
heroSection: {
|
||||
height: height * 0.6,
|
||||
position: 'relative',
|
||||
},
|
||||
heroOverlay: {
|
||||
|
|
@ -266,54 +564,52 @@ const styles = StyleSheet.create({
|
|||
justifyContent: 'flex-end',
|
||||
},
|
||||
heroBottomContent: {
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
paddingBottom: 20,
|
||||
},
|
||||
genresRow: {
|
||||
metaRow: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 16,
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
buttonsRow: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
contentSection: {
|
||||
padding: 20,
|
||||
paddingTop: 16,
|
||||
},
|
||||
synopsisSection: {
|
||||
marginBottom: 32,
|
||||
descriptionSection: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
castSection: {
|
||||
marginBottom: 32,
|
||||
marginBottom: 24,
|
||||
},
|
||||
castRow: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 16,
|
||||
},
|
||||
castItem: {
|
||||
alignItems: 'center',
|
||||
marginRight: 16,
|
||||
},
|
||||
episodesSection: {
|
||||
marginBottom: 32,
|
||||
marginBottom: 24,
|
||||
},
|
||||
episodeItem: {
|
||||
episodeList: {
|
||||
gap: 16,
|
||||
},
|
||||
episodeCard: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 16,
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
episodeInfo: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
detailsSection: {
|
||||
marginBottom: 32,
|
||||
recommendationsSection: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
detailsGrid: {
|
||||
posterRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 16,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue