mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-11 20:40:42 +00:00
some animations and fixes
This commit is contained in:
parent
ccef0d0d40
commit
7f55bba2aa
3 changed files with 136 additions and 123 deletions
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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%',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue