mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +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 {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Animated,
|
|
||||||
StatusBar,
|
StatusBar,
|
||||||
Easing,
|
Platform,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
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';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
|
||||||
|
// Responsive breakpoints
|
||||||
|
const BREAKPOINTS = {
|
||||||
|
phone: 0,
|
||||||
|
tablet: 768,
|
||||||
|
largeTablet: 1024,
|
||||||
|
tv: 1440,
|
||||||
|
};
|
||||||
|
|
||||||
interface MetadataLoadingScreenProps {
|
interface MetadataLoadingScreenProps {
|
||||||
type?: 'movie' | 'series';
|
type?: 'movie' | 'series';
|
||||||
onExitComplete?: () => void;
|
onExitComplete?: () => void;
|
||||||
|
|
@ -23,44 +41,120 @@ export interface MetadataLoadingScreenRef {
|
||||||
exit: () => void;
|
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>(({
|
export const MetadataLoadingScreen = forwardRef<MetadataLoadingScreenRef, MetadataLoadingScreenProps>(({
|
||||||
type = 'movie',
|
type = 'movie',
|
||||||
onExitComplete
|
onExitComplete
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
// Animation values - shimmer removed
|
// Responsive sizing
|
||||||
|
const deviceWidth = Dimensions.get('window').width;
|
||||||
// Scene transition animation values (matching tab navigator)
|
const deviceType = useMemo(() => {
|
||||||
const sceneOpacity = useRef(new Animated.Value(0)).current;
|
if (deviceWidth >= BREAKPOINTS.tv) return 'tv';
|
||||||
const sceneScale = useRef(new Animated.Value(0.95)).current;
|
if (deviceWidth >= BREAKPOINTS.largeTablet) return 'largeTablet';
|
||||||
const sceneTranslateY = useRef(new Animated.Value(8)).current;
|
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
|
// Exit animation function
|
||||||
const exit = () => {
|
const exit = () => {
|
||||||
const exitAnimation = Animated.parallel([
|
exitProgress.value = withTiming(1, {
|
||||||
Animated.timing(sceneOpacity, {
|
duration: 200,
|
||||||
toValue: 0,
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
||||||
duration: 100,
|
}, (finished) => {
|
||||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
'worklet';
|
||||||
useNativeDriver: true,
|
if (finished && onExitComplete) {
|
||||||
}),
|
runOnJS(onExitComplete)();
|
||||||
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?.();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -70,70 +164,57 @@ export const MetadataLoadingScreen = forwardRef<MetadataLoadingScreenRef, Metada
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Scene entrance animation (matching tab navigator)
|
// Start shimmer animation
|
||||||
const sceneAnimation = Animated.parallel([
|
shimmerProgress.value = withRepeat(
|
||||||
Animated.timing(sceneOpacity, {
|
withTiming(1, {
|
||||||
toValue: 1,
|
duration: 1500,
|
||||||
duration: 100,
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0)
|
||||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
|
||||||
useNativeDriver: true,
|
|
||||||
}),
|
}),
|
||||||
Animated.timing(sceneScale, {
|
-1, // infinite
|
||||||
toValue: 1,
|
false
|
||||||
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,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
sceneAnimation.start();
|
// Staggered entrance animations
|
||||||
|
heroOpacity.value = withTiming(1, { duration: 300 });
|
||||||
// Shimmer effect removed
|
contentOpacity.value = withDelay(100, withTiming(1, { duration: 300 }));
|
||||||
|
castOpacity.value = withDelay(200, withTiming(1, { duration: 300 }));
|
||||||
|
|
||||||
return () => {
|
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 = ({
|
const heroStyle = useAnimatedStyle(() => ({
|
||||||
width: elementWidth,
|
opacity: heroOpacity.value,
|
||||||
height: elementHeight,
|
}));
|
||||||
borderRadius = 8,
|
|
||||||
marginBottom = 8,
|
const contentStyle = useAnimatedStyle(() => ({
|
||||||
style = {},
|
opacity: contentOpacity.value,
|
||||||
}: {
|
transform: [
|
||||||
width: number | string;
|
{ translateY: interpolate(contentOpacity.value, [0, 1], [10, 0]) },
|
||||||
height: number;
|
],
|
||||||
borderRadius?: number;
|
}));
|
||||||
marginBottom?: number;
|
|
||||||
style?: any;
|
const castStyle = useAnimatedStyle(() => ({
|
||||||
}) => (
|
opacity: castOpacity.value,
|
||||||
<View style={[
|
transform: [
|
||||||
{
|
{ translateY: interpolate(castOpacity.value, [0, 1], [10, 0]) },
|
||||||
width: elementWidth,
|
],
|
||||||
height: elementHeight,
|
}));
|
||||||
borderRadius,
|
|
||||||
marginBottom,
|
|
||||||
backgroundColor: currentTheme.colors.card,
|
|
||||||
overflow: 'hidden',
|
|
||||||
},
|
|
||||||
style
|
|
||||||
]}>
|
|
||||||
{/* Pulsating overlay removed */}
|
|
||||||
{/* Shimmer overlay removed */}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={[styles.container, {
|
style={[styles.container, {
|
||||||
backgroundColor: currentTheme.colors.darkBackground,
|
backgroundColor: currentTheme.colors.darkBackground,
|
||||||
}]}
|
}]}
|
||||||
|
|
@ -144,107 +225,325 @@ export const MetadataLoadingScreen = forwardRef<MetadataLoadingScreenRef, Metada
|
||||||
backgroundColor="transparent"
|
backgroundColor="transparent"
|
||||||
barStyle="light-content"
|
barStyle="light-content"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Animated.View
|
<Animated.View style={[styles.content, containerStyle]}>
|
||||||
style={[
|
{/* Hero Section Skeleton */}
|
||||||
styles.content,
|
<Animated.View style={[styles.heroSection, { height: height * 0.65 }, heroStyle]}>
|
||||||
{
|
<ShimmerSkeleton
|
||||||
opacity: sceneOpacity,
|
width="100%"
|
||||||
transform: [
|
height={height * 0.65}
|
||||||
{ scale: sceneScale },
|
|
||||||
{ translateY: sceneTranslateY }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{/* Hero Skeleton */}
|
|
||||||
<View style={styles.heroSection}>
|
|
||||||
<SkeletonElement
|
|
||||||
width="100%"
|
|
||||||
height={height * 0.6}
|
|
||||||
borderRadius={0}
|
borderRadius={0}
|
||||||
marginBottom={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}>
|
<View style={styles.heroOverlay}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[
|
colors={[
|
||||||
'transparent',
|
'transparent',
|
||||||
'rgba(0,0,0,0.4)',
|
'rgba(0,0,0,0.05)',
|
||||||
'rgba(0,0,0,0.8)',
|
'rgba(0,0,0,0.15)',
|
||||||
|
'rgba(0,0,0,0.35)',
|
||||||
|
'rgba(0,0,0,0.65)',
|
||||||
currentTheme.colors.darkBackground,
|
currentTheme.colors.darkBackground,
|
||||||
]}
|
]}
|
||||||
|
locations={[0, 0.3, 0.55, 0.75, 0.9, 1]}
|
||||||
style={StyleSheet.absoluteFill}
|
style={StyleSheet.absoluteFill}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Bottom hero content skeleton */}
|
{/* Hero bottom content - Matches HeroSection.tsx structure */}
|
||||||
<View style={styles.heroBottomContent}>
|
<View style={[styles.heroBottomContent, { paddingHorizontal: horizontalPadding }]}>
|
||||||
<SkeletonElement width="60%" height={32} borderRadius={16} />
|
{/* Logo placeholder - Centered and larger */}
|
||||||
<SkeletonElement width="40%" height={20} borderRadius={10} />
|
<View style={{ alignItems: 'center', width: '100%', marginBottom: 16 }}>
|
||||||
<View style={styles.genresRow}>
|
<ShimmerSkeleton
|
||||||
<SkeletonElement width={80} height={24} borderRadius={12} marginBottom={0} style={{ marginRight: 8 }} />
|
width={isTV ? 400 : isLargeTablet ? 300 : width * 0.65}
|
||||||
<SkeletonElement width={90} height={24} borderRadius={12} marginBottom={0} style={{ marginRight: 8 }} />
|
height={isTV ? 120 : isLargeTablet ? 100 : 90}
|
||||||
<SkeletonElement width={70} height={24} borderRadius={12} marginBottom={0} />
|
borderRadius={12}
|
||||||
|
marginBottom={0}
|
||||||
|
shimmerProgress={shimmerProgress}
|
||||||
|
baseColor={baseColor}
|
||||||
|
highlightColor={highlightColor}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.buttonsRow}>
|
|
||||||
<SkeletonElement width={120} height={44} borderRadius={22} marginBottom={0} style={{ marginRight: 12 }} />
|
{/* Watch Progress Placeholder - Centered Glass Bar */}
|
||||||
<SkeletonElement width={100} height={44} borderRadius={22} marginBottom={0} />
|
<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>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</Animated.View>
|
||||||
|
|
||||||
{/* Content Section Skeletons */}
|
{/* Content Section */}
|
||||||
<View style={styles.contentSection}>
|
<Animated.View style={[styles.contentSection, { paddingHorizontal: horizontalPadding }, contentStyle]}>
|
||||||
{/* Synopsis skeleton */}
|
{/* Description skeleton */}
|
||||||
<View style={styles.synopsisSection}>
|
<View style={styles.descriptionSection}>
|
||||||
<SkeletonElement width="30%" height={24} borderRadius={12} />
|
<ShimmerSkeleton
|
||||||
<SkeletonElement width="100%" height={16} borderRadius={8} />
|
width="100%"
|
||||||
<SkeletonElement width="95%" height={16} borderRadius={8} />
|
height={isTV ? 18 : 15}
|
||||||
<SkeletonElement width="80%" height={16} borderRadius={8} />
|
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>
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
{/* Cast section skeleton */}
|
{/* Cast Section */}
|
||||||
<View style={styles.castSection}>
|
<Animated.View style={[styles.castSection, { paddingHorizontal: horizontalPadding }, castStyle]}>
|
||||||
<SkeletonElement width="20%" height={24} borderRadius={12} />
|
<ShimmerSkeleton
|
||||||
<View style={styles.castRow}>
|
width={isTV ? 80 : 60}
|
||||||
{[1, 2, 3, 4].map((item) => (
|
height={isTV ? 24 : 20}
|
||||||
<View key={item} style={styles.castItem}>
|
borderRadius={4}
|
||||||
<SkeletonElement width={80} height={80} borderRadius={40} marginBottom={8} />
|
marginBottom={16}
|
||||||
<SkeletonElement width={60} height={12} borderRadius={6} marginBottom={4} />
|
shimmerProgress={shimmerProgress}
|
||||||
<SkeletonElement width={70} height={10} borderRadius={5} marginBottom={0} />
|
baseColor={baseColor}
|
||||||
</View>
|
highlightColor={highlightColor}
|
||||||
))}
|
/>
|
||||||
</View>
|
<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>
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
{/* Episodes/Details skeleton based on type */}
|
{/* Episodes/Recommendations Section */}
|
||||||
{type === 'series' ? (
|
{type === 'series' ? (
|
||||||
<View style={styles.episodesSection}>
|
<Animated.View style={[styles.episodesSection, { paddingHorizontal: horizontalPadding }, castStyle]}>
|
||||||
<SkeletonElement width="25%" height={24} borderRadius={12} />
|
<ShimmerSkeleton
|
||||||
<SkeletonElement width={150} height={36} borderRadius={18} />
|
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) => (
|
{[1, 2, 3].map((item) => (
|
||||||
<View key={item} style={styles.episodeItem}>
|
<View key={item} style={styles.episodeCard}>
|
||||||
<SkeletonElement width={120} height={68} borderRadius={8} marginBottom={0} style={{ marginRight: 12 }} />
|
<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}>
|
<View style={styles.episodeInfo}>
|
||||||
<SkeletonElement width="80%" height={16} borderRadius={8} />
|
<ShimmerSkeleton
|
||||||
<SkeletonElement width="60%" height={14} borderRadius={7} />
|
width="80%"
|
||||||
<SkeletonElement width="90%" height={12} borderRadius={6} />
|
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>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
) : (
|
</Animated.View>
|
||||||
<View style={styles.detailsSection}>
|
) : (
|
||||||
<SkeletonElement width="25%" height={24} borderRadius={12} />
|
<Animated.View style={[styles.recommendationsSection, { paddingHorizontal: horizontalPadding }, castStyle]}>
|
||||||
<View style={styles.detailsGrid}>
|
<ShimmerSkeleton
|
||||||
<SkeletonElement width="48%" height={60} borderRadius={8} />
|
width={isTV ? 140 : 110}
|
||||||
<SkeletonElement width="48%" height={60} borderRadius={8} />
|
height={isTV ? 24 : 20}
|
||||||
</View>
|
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>
|
||||||
</View>
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|
@ -258,7 +557,6 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
heroSection: {
|
heroSection: {
|
||||||
height: height * 0.6,
|
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
},
|
},
|
||||||
heroOverlay: {
|
heroOverlay: {
|
||||||
|
|
@ -266,54 +564,52 @@ const styles = StyleSheet.create({
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
},
|
},
|
||||||
heroBottomContent: {
|
heroBottomContent: {
|
||||||
position: 'absolute',
|
paddingBottom: 20,
|
||||||
bottom: 20,
|
|
||||||
left: 20,
|
|
||||||
right: 20,
|
|
||||||
},
|
},
|
||||||
genresRow: {
|
metaRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginBottom: 16,
|
alignItems: 'center',
|
||||||
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
buttonsRow: {
|
buttonsRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginBottom: 8,
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
contentSection: {
|
contentSection: {
|
||||||
padding: 20,
|
paddingTop: 16,
|
||||||
},
|
},
|
||||||
synopsisSection: {
|
descriptionSection: {
|
||||||
marginBottom: 32,
|
marginBottom: 24,
|
||||||
},
|
},
|
||||||
castSection: {
|
castSection: {
|
||||||
marginBottom: 32,
|
marginBottom: 24,
|
||||||
},
|
},
|
||||||
castRow: {
|
castRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginTop: 16,
|
|
||||||
},
|
},
|
||||||
castItem: {
|
castItem: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginRight: 16,
|
marginRight: 16,
|
||||||
},
|
},
|
||||||
episodesSection: {
|
episodesSection: {
|
||||||
marginBottom: 32,
|
marginBottom: 24,
|
||||||
},
|
},
|
||||||
episodeItem: {
|
episodeList: {
|
||||||
|
gap: 16,
|
||||||
|
},
|
||||||
|
episodeCard: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginBottom: 16,
|
gap: 12,
|
||||||
alignItems: 'center',
|
|
||||||
},
|
},
|
||||||
episodeInfo: {
|
episodeInfo: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
detailsSection: {
|
recommendationsSection: {
|
||||||
marginBottom: 32,
|
marginBottom: 24,
|
||||||
},
|
},
|
||||||
detailsGrid: {
|
posterRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
|
||||||
marginTop: 16,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue