some animations and fixes

This commit is contained in:
Nayif Noushad 2025-04-15 14:48:03 +05:30
parent ccef0d0d40
commit 7f55bba2aa
3 changed files with 136 additions and 123 deletions

View file

@ -7,27 +7,35 @@ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/AppNavigator'; import type { RootStackParamList } from '../navigation/AppNavigator';
import { BlurView as ExpoBlurView } from 'expo-blur'; import { BlurView as ExpoBlurView } from 'expo-blur';
import { BlurView as CommunityBlurView } from '@react-native-community/blur'; import { BlurView as CommunityBlurView } from '@react-native-community/blur';
import Constants, { ExecutionEnvironment } from 'expo-constants';
type NavigationProp = NativeStackNavigationProp<RootStackParamList>; type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
export const NuvioHeader = () => { export const NuvioHeader = () => {
const navigation = useNavigation<NavigationProp>(); const navigation = useNavigation<NavigationProp>();
// Determine if running in Expo Go
const isExpoGo = Constants.executionEnvironment === ExecutionEnvironment.StoreClient;
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.headerContainer}> <View style={styles.headerContainer}>
{Platform.OS === 'ios' ? ( {Platform.OS === 'ios' ? (
<ExpoBlurView intensity={60} style={styles.blurOverlay} tint="dark" /> <ExpoBlurView intensity={60} style={styles.blurOverlay} tint="dark" />
) : ( ) : (
<View style={styles.androidBlurContainer}> isExpoGo ? (
<CommunityBlurView <View style={[styles.androidBlurContainer, styles.androidFallbackBlur]} />
style={styles.androidBlur} ) : (
blurType="dark" <View style={styles.androidBlurContainer}>
blurAmount={8} <CommunityBlurView
overlayColor="rgba(0,0,0,0.4)" style={styles.androidBlur}
reducedTransparencyFallbackColor="black" blurType="dark"
/> blurAmount={8}
</View> overlayColor="rgba(0,0,0,0.4)"
reducedTransparencyFallbackColor="black"
/>
</View>
)
)} )}
<View style={styles.contentContainer}> <View style={styles.contentContainer}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
@ -83,6 +91,9 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
backgroundColor: 'transparent', backgroundColor: 'transparent',
}, },
androidFallbackBlur: {
backgroundColor: 'rgba(0,0,0,0.6)',
},
contentContainer: { contentContainer: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',

View file

@ -654,50 +654,46 @@ const HomeScreen = () => {
}, [featuredContent, navigation]); }, [featuredContent, navigation]);
const renderFeaturedContent = () => { const renderFeaturedContent = () => {
if (!featuredContent) return null; if (!featuredContent) {
return <SkeletonFeatured />;
}
return ( return (
<View style={styles.featuredContainer}> <TouchableOpacity
activeOpacity={0.8}
onPress={handleSaveToLibrary}
style={styles.featuredContainer}
>
<ImageBackground <ImageBackground
source={{ uri: featuredContent.banner || featuredContent.poster }} source={{ uri: featuredContent.banner || featuredContent.poster }}
style={styles.featuredBanner} style={styles.featuredImage}
resizeMode="cover" resizeMode="cover"
> >
<LinearGradient <LinearGradient
colors={[ colors={[
'transparent', 'transparent',
'rgba(0,0,0,0.2)', 'rgba(0,0,0,0.2)',
'rgba(0,0,0,0.6)',
'rgba(0,0,0,0.8)', 'rgba(0,0,0,0.8)',
colors.darkBackground colors.darkBackground,
]} ]}
locations={[0, 0.3, 0.6, 0.8, 1]} locations={[0, 0.4, 0.7, 1]}
style={styles.featuredGradient} style={styles.featuredGradient}
> >
<View style={{ flex: 1 }} /> <Animated.View style={styles.featuredContentContainer} entering={FadeIn.duration(500)}>
<View style={styles.featuredContent}>
{featuredContent.logo ? ( {featuredContent.logo ? (
<ExpoImage <ExpoImage
source={{ uri: featuredContent.logo }} source={{ uri: featuredContent.logo }}
style={styles.featuredLogo} style={styles.featuredLogo}
contentFit="contain" contentFit="contain"
transition={200}
/> />
) : ( ) : (
<Text style={styles.featuredTitle}>{featuredContent.name}</Text> <Text style={styles.featuredTitleText}>{featuredContent.name}</Text>
)} )}
<View style={styles.genreContainer}> <View style={styles.genreContainer}>
{featuredContent.genres?.slice(0, 3).map((genre, index, array) => ( {featuredContent.genres?.slice(0, 3).map((genre, index) => (
<React.Fragment key={index}> <Text key={index} style={styles.genreText}>{genre}</Text>
<Text style={styles.genreText}>{genre}</Text>
{index < array.length - 1 && (
<Text style={styles.genreDot}></Text>
)}
</React.Fragment>
))} ))}
</View> </View>
<View style={styles.featuredButtons}> <View style={styles.featuredButtons}>
<TouchableOpacity <TouchableOpacity
style={styles.myListButton} style={styles.myListButton}
@ -748,10 +744,10 @@ const HomeScreen = () => {
<Text style={styles.infoButtonText}>Info</Text> <Text style={styles.infoButtonText}>Info</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </Animated.View>
</LinearGradient> </LinearGradient>
</ImageBackground> </ImageBackground>
</View> </TouchableOpacity>
); );
}; };
@ -1253,6 +1249,25 @@ const styles = StyleSheet.create<any>({
alignItems: 'center', alignItems: 'center',
borderRadius: 12, borderRadius: 12,
}, },
featuredImage: {
width: '100%',
height: '100%',
},
featuredContentContainer: {
flex: 1,
justifyContent: 'flex-end',
},
featuredTitleText: {
color: colors.highEmphasis,
fontSize: 28,
fontWeight: '900',
marginBottom: 8,
textShadowColor: 'rgba(0,0,0,0.6)',
textShadowOffset: { width: 0, height: 2 },
textShadowRadius: 4,
textAlign: 'center',
paddingHorizontal: 16,
},
}); });
export default HomeScreen; export default HomeScreen;

View file

@ -52,7 +52,7 @@ const { width, height } = Dimensions.get('window');
// Animation configs // Animation configs
const springConfig = { const springConfig = {
damping: 15, damping: 20,
mass: 1, mass: 1,
stiffness: 100 stiffness: 100
}; };
@ -87,7 +87,6 @@ const MetadataScreen = () => {
const contentRef = useRef<ScrollView>(null); const contentRef = useRef<ScrollView>(null);
const [lastScrollTop, setLastScrollTop] = useState(0); const [lastScrollTop, setLastScrollTop] = useState(0);
const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false); const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false);
const fullDescriptionAnimation = useSharedValue(0);
// Animation values // Animation values
const screenScale = useSharedValue(0.8); const screenScale = useSharedValue(0.8);
@ -103,12 +102,15 @@ const MetadataScreen = () => {
episodeId?: string; episodeId?: string;
} | null>(null); } | null>(null);
// Add new animated value for creator height
const creatorHeight = useSharedValue(0);
// Add new animated value for watch progress // Add new animated value for watch progress
const watchProgressHeight = useSharedValue(0);
const watchProgressOpacity = useSharedValue(0); const watchProgressOpacity = useSharedValue(0);
const watchProgressScaleY = useSharedValue(0);
// Add new animated value for logo scale
const logoScale = useSharedValue(0);
// Add new animated value for creator fade-in
const creatorOpacity = useSharedValue(0);
// Debug log for route params // Debug log for route params
// logger.log('[MetadataScreen] Component mounted with route params:', { id, type, episodeId }); // logger.log('[MetadataScreen] Component mounted with route params:', { id, type, episodeId });
@ -304,64 +306,85 @@ const MetadataScreen = () => {
// Add effect to animate watch progress when it changes // Add effect to animate watch progress when it changes
useEffect(() => { useEffect(() => {
if (watchProgress && watchProgress.duration > 0) { if (watchProgress && watchProgress.duration > 0) {
watchProgressHeight.value = withSpring(48, {
mass: 0.3,
stiffness: 120,
damping: 15,
velocity: 0.5
});
watchProgressOpacity.value = withSpring(1, { watchProgressOpacity.value = withSpring(1, {
mass: 0.2, mass: 0.2,
stiffness: 100, stiffness: 100,
damping: 12 damping: 14
}); });
} else { watchProgressScaleY.value = withSpring(1, {
watchProgressHeight.value = withSpring(0, {
mass: 0.3, mass: 0.3,
stiffness: 120, stiffness: 120,
damping: 15 damping: 18
}); });
} else {
watchProgressOpacity.value = withSpring(0, { watchProgressOpacity.value = withSpring(0, {
mass: 0.2, mass: 0.2,
stiffness: 100, stiffness: 100,
damping: 12 damping: 14
});
watchProgressScaleY.value = withSpring(0, {
mass: 0.3,
stiffness: 120,
damping: 18
}); });
} }
}, [watchProgress]); }, [watchProgress]);
// Add animated style for watch progress // Add animated style for watch progress
const watchProgressAnimatedStyle = useAnimatedStyle(() => { const watchProgressAnimatedStyle = useAnimatedStyle(() => {
const progress = interpolate( const translateY = interpolate(
watchProgressHeight.value, watchProgressScaleY.value,
[0, 48],
[0, 1], [0, 1],
[-8, 0],
Extrapolate.CLAMP Extrapolate.CLAMP
); );
return { return {
height: watchProgressHeight.value,
opacity: watchProgressOpacity.value, opacity: watchProgressOpacity.value,
transform: [ transform: [
{ { translateY: translateY },
translateY: interpolate( { scaleY: watchProgressScaleY.value }
progress,
[0, 1],
[-8, 0],
Extrapolate.CLAMP
)
},
{
scale: interpolate(
progress,
[0, 1],
[0.95, 1],
Extrapolate.CLAMP
)
}
] ]
}; };
}); });
// Add animated style for logo
const logoAnimatedStyle = useAnimatedStyle(() => {
return {
transform: [{ scale: logoScale.value }],
};
});
// Effect to animate logo scale when logo URI is available
useEffect(() => {
if (metadata?.logo) {
logoScale.value = withSpring(1, {
damping: 18,
stiffness: 120,
mass: 0.5
});
} else {
// Optional: Reset scale if logo disappears?
// logoScale.value = withTiming(0, { duration: 100 });
}
}, [metadata?.logo]);
// Add animated style for creator fade-in
const creatorFadeInStyle = useAnimatedStyle(() => {
return {
opacity: creatorOpacity.value,
};
});
// Effect to fade in creator section when data is available
useEffect(() => {
const hasCreators = metadata?.directors?.length || metadata?.creators?.length;
creatorOpacity.value = withTiming(hasCreators ? 1 : 0, {
duration: 300, // Adjust duration as needed
easing: Easing.out(Easing.quad), // Use an easing function
});
}, [metadata?.directors, metadata?.creators]);
// Update the watch progress render function // Update the watch progress render function
const renderWatchProgress = () => { const renderWatchProgress = () => {
if (!watchProgress || watchProgress.duration === 0) { if (!watchProgress || watchProgress.duration === 0) {
@ -513,46 +536,6 @@ const MetadataScreen = () => {
) )
})); }));
// Add animated style for creator container
const creatorAnimatedStyle = useAnimatedStyle(() => ({
maxHeight: creatorHeight.value,
opacity: interpolate(
creatorHeight.value,
[0, 24],
[0, 1],
Extrapolate.CLAMP
),
transform: [
{
translateY: interpolate(
creatorHeight.value,
[0, 24],
[-8, 0],
Extrapolate.CLAMP
)
}
]
}));
// Add effect to animate height when metadata changes
useEffect(() => {
if (metadata?.directors?.length || metadata?.creators?.length) {
creatorHeight.value = withSpring(24, {
mass: 0.5,
stiffness: 100,
damping: 12,
velocity: 0.4
});
} else {
creatorHeight.value = withSpring(0, {
mass: 0.5,
stiffness: 100,
damping: 12,
velocity: 0.4
});
}
}, [metadata?.directors, metadata?.creators]);
// Debug logs for director/creator data // Debug logs for director/creator data
React.useEffect(() => { React.useEffect(() => {
if (metadata && metadata.id) { if (metadata && metadata.id) {
@ -615,8 +598,11 @@ const MetadataScreen = () => {
React.useEffect(() => { React.useEffect(() => {
screenScale.value = withSpring(1, springConfig); screenScale.value = withSpring(1, springConfig);
screenOpacity.value = withSpring(1, springConfig); screenOpacity.value = withSpring(1, springConfig);
heroHeight.value = withSpring(height * 0.5, springConfig); contentTranslateY.value = withSpring(0, {
contentTranslateY.value = withSpring(0, springConfig); damping: 25,
mass: 1,
stiffness: 100
});
}, []); }, []);
const handleBack = useCallback(() => { const handleBack = useCallback(() => {
@ -716,7 +702,7 @@ const MetadataScreen = () => {
style={styles.scrollView} style={styles.scrollView}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
onScroll={(e) => { onScroll={(e) => {
setLastScrollTop(e.nativeEvent.contentOffset.y); // setLastScrollTop(e.nativeEvent.contentOffset.y); // Remove unused onScroll handler logic
}} }}
scrollEventThrottle={16} scrollEventThrottle={16}
> >
@ -740,14 +726,16 @@ const MetadataScreen = () => {
locations={[0, 0.4, 0.65, 0.8, 0.9, 1]} locations={[0, 0.4, 0.65, 0.8, 0.9, 1]}
style={styles.heroGradient} style={styles.heroGradient}
> >
<Animated.View entering={FadeInDown.delay(200).springify()} style={styles.heroContent}> <Animated.View entering={FadeInDown.delay(100).springify()} style={styles.heroContent}>
{/* Title */} {/* Title */}
{metadata.logo ? ( {metadata.logo ? (
<Image <Animated.View style={logoAnimatedStyle}>
source={{ uri: metadata.logo }} <Image
style={styles.titleLogo} source={{ uri: metadata.logo }}
contentFit="contain" style={styles.titleLogo}
/> contentFit="contain"
/>
</Animated.View>
) : ( ) : (
<Text style={styles.titleText}>{metadata.name}</Text> <Text style={styles.titleText}>{metadata.name}</Text>
)} )}
@ -802,11 +790,10 @@ const MetadataScreen = () => {
</View> </View>
{/* Creator/Director Info */} {/* Creator/Director Info */}
<Animated.View <Animated.View
style={[ style={[
styles.creatorContainer, styles.creatorContainer,
creatorAnimatedStyle, creatorFadeInStyle,
{ minHeight: (metadata?.directors?.length || metadata?.creators?.length) ? 'auto' : 0 }
]} ]}
> >
{metadata.directors && metadata.directors.length > 0 && ( {metadata.directors && metadata.directors.length > 0 && (
@ -1131,7 +1118,6 @@ const styles = StyleSheet.create({
creatorContainer: { creatorContainer: {
marginBottom: 2, marginBottom: 2,
paddingHorizontal: 16, paddingHorizontal: 16,
overflow: 'hidden'
}, },
creatorSection: { creatorSection: {
flexDirection: 'row', flexDirection: 'row',
@ -1157,7 +1143,8 @@ const styles = StyleSheet.create({
marginBottom: 8, marginBottom: 8,
width: '100%', width: '100%',
alignItems: 'center', alignItems: 'center',
overflow: 'hidden' overflow: 'hidden',
height: 48,
}, },
watchProgressBar: { watchProgressBar: {
width: '75%', width: '75%',