mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-13 13:10:56 +00:00
Refactor animation durations across multiple components for improved performance and consistency
This update modifies the animation durations in various components, including CatalogSection, ContinueWatchingSection, FeaturedContent, and ThisWeekSection, reducing the fade-in durations to enhance the user experience. Additionally, adjustments were made to the CastSection, MetadataDetails, and SeriesContent components to streamline animations. The changes aim to create a more cohesive and responsive interface throughout the application.
This commit is contained in:
parent
e020c2f4d9
commit
4a6f349cdb
15 changed files with 153 additions and 115 deletions
|
|
@ -76,7 +76,7 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
|
|||
return (
|
||||
<Animated.View
|
||||
style={styles.catalogContainer}
|
||||
entering={FadeIn.duration(400).delay(50)}
|
||||
entering={FadeIn.duration(300).delay(50)}
|
||||
>
|
||||
<View style={styles.catalogHeader}>
|
||||
<View style={styles.titleContainer}>
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
}
|
||||
|
||||
return (
|
||||
<Animated.View entering={FadeIn.duration(400).delay(250)} style={styles.container}>
|
||||
<Animated.View entering={FadeIn.duration(300).delay(150)} style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={[styles.title, { color: currentTheme.colors.text }]}>Continue Watching</Text>
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
|
|||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(800).easing(Easing.out(Easing.cubic))}
|
||||
entering={FadeIn.duration(400).easing(Easing.out(Easing.cubic))}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.95}
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export const ThisWeekSection = () => {
|
|||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={FadeInRight.delay(index * 150).duration(600)}
|
||||
entering={FadeInRight.delay(index * 50).duration(300)}
|
||||
style={styles.episodeItemContainer}
|
||||
>
|
||||
<TouchableOpacity
|
||||
|
|
@ -261,7 +261,7 @@ export const ThisWeekSection = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Animated.View entering={FadeIn.duration(400)} style={styles.container}>
|
||||
<Animated.View entering={FadeIn.duration(300)} style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={[styles.title, { color: currentTheme.colors.text }]}>This Week</Text>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export const CastSection: React.FC<CastSectionProps> = ({
|
|||
return (
|
||||
<Animated.View
|
||||
style={styles.castSection}
|
||||
entering={FadeIn.duration(500).delay(300)}
|
||||
entering={FadeIn.duration(300).delay(150)}
|
||||
layout={Layout}
|
||||
>
|
||||
<View style={styles.sectionHeader}>
|
||||
|
|
@ -56,7 +56,7 @@ export const CastSection: React.FC<CastSectionProps> = ({
|
|||
keyExtractor={(item) => item.id.toString()}
|
||||
renderItem={({ item, index }) => (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(500).delay(100 + index * 50)}
|
||||
entering={FadeIn.duration(300).delay(50 + index * 30)}
|
||||
layout={Layout}
|
||||
>
|
||||
<TouchableOpacity
|
||||
|
|
@ -75,7 +75,7 @@ export const CastSection: React.FC<CastSectionProps> = ({
|
|||
transition={200}
|
||||
/>
|
||||
) : (
|
||||
<View style={[styles.castImagePlaceholder, { backgroundColor: currentTheme.colors.cardBackground }]}>
|
||||
<View style={[styles.castImagePlaceholder, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<Text style={[styles.placeholderText, { color: currentTheme.colors.textMuted }]}>
|
||||
{item.name.split(' ').reduce((prev: string, current: string) => prev + current[0], '').substring(0, 2)}
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
|
|||
|
||||
{/* Creator/Director Info */}
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(500).delay(200)}
|
||||
entering={FadeIn.duration(300).delay(100)}
|
||||
style={styles.creatorContainer}
|
||||
>
|
||||
{metadata.directors && metadata.directors.length > 0 && (
|
||||
|
|
@ -81,7 +81,7 @@ const MetadataDetails: React.FC<MetadataDetailsProps> = ({
|
|||
{metadata.description && (
|
||||
<Animated.View
|
||||
style={styles.descriptionContainer}
|
||||
layout={Layout.duration(300).easing(Easing.inOut(Easing.ease))}
|
||||
entering={FadeIn.duration(300)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => setIsFullDescriptionOpen(!isFullDescriptionOpen)}
|
||||
|
|
|
|||
|
|
@ -535,13 +535,13 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
return (
|
||||
<View style={styles.container}>
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(500).delay(100)}
|
||||
entering={FadeIn.duration(300).delay(50)}
|
||||
>
|
||||
{renderSeasonSelector()}
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(500).delay(200)}
|
||||
entering={FadeIn.duration(300).delay(100)}
|
||||
>
|
||||
<Text style={[styles.sectionTitle, { color: currentTheme.colors.highEmphasis }]}>
|
||||
{episodes.length} {episodes.length === 1 ? 'Episode' : 'Episodes'}
|
||||
|
|
@ -562,7 +562,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
{currentSeasonEpisodes.map((episode, index) => (
|
||||
<Animated.View
|
||||
key={episode.id}
|
||||
entering={FadeIn.duration(400).delay(300 + index * 50)}
|
||||
entering={FadeIn.duration(300).delay(100 + index * 30)}
|
||||
style={[
|
||||
styles.episodeCardWrapperHorizontal,
|
||||
isTablet && styles.episodeCardWrapperHorizontalTablet
|
||||
|
|
@ -586,7 +586,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
{currentSeasonEpisodes.map((episode, index) => (
|
||||
<Animated.View
|
||||
key={episode.id}
|
||||
entering={FadeIn.duration(400).delay(300 + index * 50)}
|
||||
entering={FadeIn.duration(300).delay(100 + index * 30)}
|
||||
>
|
||||
{renderVerticalEpisodeCard(episode)}
|
||||
</Animated.View>
|
||||
|
|
@ -596,7 +596,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
currentSeasonEpisodes.map((episode, index) => (
|
||||
<Animated.View
|
||||
key={episode.id}
|
||||
entering={FadeIn.duration(400).delay(300 + index * 50)}
|
||||
entering={FadeIn.duration(300).delay(100 + index * 30)}
|
||||
>
|
||||
{renderVerticalEpisodeCard(episode)}
|
||||
</Animated.View>
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import Animated, {
|
|||
withDelay,
|
||||
withSequence,
|
||||
runOnJS,
|
||||
BounceIn,
|
||||
ZoomIn
|
||||
} from 'react-native-reanimated';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { styles } from '../utils/playerStyles';
|
||||
|
|
@ -227,7 +225,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
|||
</Text>
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View entering={BounceIn.duration(400).delay(200)}>
|
||||
<Animated.View entering={FadeIn.duration(300).delay(200)}>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
width: 44,
|
||||
|
|
@ -267,8 +265,8 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
|||
{vlcAudioTracks.length > 0 ? vlcAudioTracks.map((track, index) => (
|
||||
<Animated.View
|
||||
key={track.id}
|
||||
entering={FadeInDown.duration(300).delay(150 + (index * 50))}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(50 + index * 30)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
width: '100%',
|
||||
|
|
@ -323,7 +321,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
|||
|
||||
{selectedAudioTrack === track.id && (
|
||||
<Animated.View
|
||||
entering={BounceIn.duration(300)}
|
||||
entering={FadeIn.duration(300)}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
|
@ -388,7 +386,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
|||
: 'rgba(255, 255, 255, 0.1)',
|
||||
}}>
|
||||
{selectedAudioTrack === track.id ? (
|
||||
<Animated.View entering={ZoomIn.duration(200)}>
|
||||
<Animated.View entering={FadeIn.duration(200)}>
|
||||
<MaterialIcons name="check-circle" size={24} color="#F97316" />
|
||||
</Animated.View>
|
||||
) : (
|
||||
|
|
@ -400,7 +398,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
|||
</Animated.View>
|
||||
)) : (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(300).delay(150)}
|
||||
entering={FadeIn.duration(300).delay(150)}
|
||||
style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
||||
borderRadius: 20,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import Animated, {
|
|||
withDelay,
|
||||
withSequence,
|
||||
runOnJS,
|
||||
BounceIn,
|
||||
ZoomIn
|
||||
} from 'react-native-reanimated';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { styles } from '../utils/playerStyles';
|
||||
|
|
@ -62,7 +60,7 @@ const QualityIndicator = ({ quality }: { quality: string | null }) => {
|
|||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={ZoomIn.duration(200).delay(100)}
|
||||
entering={FadeIn.duration(200).delay(100)}
|
||||
style={{
|
||||
backgroundColor: `${color}20`,
|
||||
borderColor: `${color}60`,
|
||||
|
|
@ -107,7 +105,7 @@ const StreamMetaBadge = ({
|
|||
delay?: number;
|
||||
}) => (
|
||||
<Animated.View
|
||||
entering={FadeInUp.duration(200).delay(delay)}
|
||||
entering={FadeIn.duration(200).delay(delay)}
|
||||
style={{
|
||||
backgroundColor: bgColor,
|
||||
borderColor: `${color}40`,
|
||||
|
|
@ -279,7 +277,7 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
}}
|
||||
>
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(300).delay(100)}
|
||||
entering={FadeIn.duration(300).delay(100)}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<Text style={{
|
||||
|
|
@ -304,7 +302,7 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
</Text>
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View entering={BounceIn.duration(400).delay(200)}>
|
||||
<Animated.View entering={FadeIn.duration(300).delay(200)}>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
width: 44,
|
||||
|
|
@ -343,8 +341,8 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
{sortedProviders.map(([providerId, { streams, addonName }], providerIndex) => (
|
||||
<Animated.View
|
||||
key={providerId}
|
||||
entering={FadeInDown.duration(400).delay(150 + (providerIndex * 80))}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(50 + providerIndex * 30)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{
|
||||
marginBottom: streams.length > 0 ? 32 : 0,
|
||||
width: '100%',
|
||||
|
|
@ -426,8 +424,8 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
return (
|
||||
<Animated.View
|
||||
key={`${stream.url}-${index}`}
|
||||
entering={FadeInDown.duration(300).delay((providerIndex * 80) + (index * 40))}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(100 + index * 50)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
|
|
@ -482,7 +480,7 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
|
||||
{isSelected && (
|
||||
<Animated.View
|
||||
entering={BounceIn.duration(300)}
|
||||
entering={FadeIn.duration(300)}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
|
@ -632,7 +630,7 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
shadowRadius: 4,
|
||||
}}>
|
||||
{isSelected ? (
|
||||
<Animated.View entering={ZoomIn.duration(200)}>
|
||||
<Animated.View entering={FadeIn.duration(200)}>
|
||||
<MaterialIcons name="check-circle" size={24} color="#E50914" />
|
||||
</Animated.View>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import Animated, {
|
|||
withDelay,
|
||||
withSequence,
|
||||
runOnJS,
|
||||
BounceIn,
|
||||
ZoomIn
|
||||
} from 'react-native-reanimated';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { styles } from '../utils/playerStyles';
|
||||
|
|
@ -281,7 +279,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</Text>
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View entering={BounceIn.duration(400).delay(200)}>
|
||||
<Animated.View entering={FadeIn.duration(300).delay(200)}>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
width: 44,
|
||||
|
|
@ -321,8 +319,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
|
||||
{/* External Subtitles Section */}
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(400).delay(150)}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(150)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{
|
||||
marginBottom: 32,
|
||||
}}
|
||||
|
|
@ -372,8 +370,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{/* Custom subtitles option */}
|
||||
{customSubtitles.length > 0 && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(300).delay(200)}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(200)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
|
|
@ -423,7 +421,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
|
||||
{useCustomSubtitles && (
|
||||
<Animated.View
|
||||
entering={BounceIn.duration(300)}
|
||||
entering={FadeIn.duration(300)}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
|
@ -486,7 +484,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
: 'rgba(255, 255, 255, 0.1)',
|
||||
}}>
|
||||
{useCustomSubtitles ? (
|
||||
<Animated.View entering={ZoomIn.duration(200)}>
|
||||
<Animated.View entering={FadeIn.duration(300)}>
|
||||
<MaterialIcons name="check-circle" size={24} color="#4CAF50" />
|
||||
</Animated.View>
|
||||
) : (
|
||||
|
|
@ -500,8 +498,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
|
||||
{/* Search for external subtitles */}
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(300).delay(250)}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(250)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
|
|
@ -549,8 +547,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{/* Subtitle Size Controls */}
|
||||
{useCustomSubtitles && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(400).delay(200)}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(200)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{
|
||||
marginBottom: 32,
|
||||
}}
|
||||
|
|
@ -598,7 +596,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</View>
|
||||
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(300).delay(300)}
|
||||
entering={FadeIn.duration(200).delay(300)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.03)',
|
||||
borderRadius: 20,
|
||||
|
|
@ -698,8 +697,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{vlcTextTracks.length > 0 ? vlcTextTracks.map((track, index) => (
|
||||
<Animated.View
|
||||
key={track.id}
|
||||
entering={FadeInDown.duration(300).delay(400 + (index * 50))}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(50 + index * 30)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
|
|
@ -749,7 +748,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
|
||||
{(selectedTextTrack === track.id && !useCustomSubtitles) && (
|
||||
<Animated.View
|
||||
entering={BounceIn.duration(300)}
|
||||
entering={FadeIn.duration(300)}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
|
@ -812,7 +811,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
: 'rgba(255, 255, 255, 0.1)',
|
||||
}}>
|
||||
{(selectedTextTrack === track.id && !useCustomSubtitles) ? (
|
||||
<Animated.View entering={ZoomIn.duration(200)}>
|
||||
<Animated.View entering={FadeIn.duration(300)}>
|
||||
<MaterialIcons name="check-circle" size={24} color="#FF9800" />
|
||||
</Animated.View>
|
||||
) : (
|
||||
|
|
@ -824,7 +823,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</Animated.View>
|
||||
)) : (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(300).delay(400)}
|
||||
entering={FadeIn.duration(200).delay(400)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
||||
borderRadius: 16,
|
||||
|
|
@ -972,7 +972,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</Text>
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View entering={BounceIn.duration(400).delay(200)}>
|
||||
<Animated.View entering={FadeIn.duration(300).delay(200)}>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
width: 44,
|
||||
|
|
@ -1011,8 +1011,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{availableSubtitles.length > 0 ? availableSubtitles.map((subtitle, index) => (
|
||||
<Animated.View
|
||||
key={subtitle.id}
|
||||
entering={FadeInDown.duration(300).delay(150 + (index * 50))}
|
||||
layout={Layout.springify()}
|
||||
entering={FadeIn.duration(200).delay(100 + index * 50)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
|
|
@ -1098,7 +1098,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</Animated.View>
|
||||
)) : (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(300).delay(150)}
|
||||
entering={FadeIn.duration(200).delay(150)}
|
||||
style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
||||
borderRadius: 20,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
Easing,
|
||||
useAnimatedScrollHandler,
|
||||
runOnUI,
|
||||
cancelAnimation,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
|
|
@ -57,73 +58,115 @@ export const useMetadataAnimations = (safeAreaTop: number, watchProgress: any) =
|
|||
|
||||
// Ultra-fast entrance sequence - batch animations for better performance
|
||||
useEffect(() => {
|
||||
// Batch all entrance animations to run simultaneously
|
||||
// Batch all entrance animations to run simultaneously with safety
|
||||
const enterAnimations = () => {
|
||||
'worklet';
|
||||
|
||||
// Start with slightly reduced values and animate to full visibility
|
||||
screenOpacity.value = withTiming(1, {
|
||||
duration: 250,
|
||||
easing: easings.fast
|
||||
});
|
||||
|
||||
heroOpacity.value = withTiming(1, {
|
||||
duration: 300,
|
||||
easing: easings.fast
|
||||
});
|
||||
|
||||
heroScale.value = withSpring(1, ultraFastSpring);
|
||||
|
||||
uiElementsOpacity.value = withTiming(1, {
|
||||
duration: 400,
|
||||
easing: easings.natural
|
||||
});
|
||||
|
||||
uiElementsTranslateY.value = withSpring(0, fastSpring);
|
||||
|
||||
contentOpacity.value = withTiming(1, {
|
||||
duration: 350,
|
||||
easing: easings.fast
|
||||
});
|
||||
try {
|
||||
// Start with slightly reduced values and animate to full visibility
|
||||
screenOpacity.value = withTiming(1, {
|
||||
duration: 250,
|
||||
easing: easings.fast
|
||||
});
|
||||
|
||||
heroOpacity.value = withTiming(1, {
|
||||
duration: 300,
|
||||
easing: easings.fast
|
||||
});
|
||||
|
||||
heroScale.value = withSpring(1, ultraFastSpring);
|
||||
|
||||
uiElementsOpacity.value = withTiming(1, {
|
||||
duration: 400,
|
||||
easing: easings.natural
|
||||
});
|
||||
|
||||
uiElementsTranslateY.value = withSpring(0, fastSpring);
|
||||
|
||||
contentOpacity.value = withTiming(1, {
|
||||
duration: 350,
|
||||
easing: easings.fast
|
||||
});
|
||||
} catch (error) {
|
||||
// Silently handle any animation errors
|
||||
console.warn('Animation error in enterAnimations:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Use runOnUI for better performance
|
||||
runOnUI(enterAnimations)();
|
||||
// Use runOnUI for better performance with error handling
|
||||
try {
|
||||
runOnUI(enterAnimations)();
|
||||
} catch (error) {
|
||||
console.warn('Failed to run enter animations:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Optimized watch progress animation
|
||||
// Optimized watch progress animation with safety
|
||||
useEffect(() => {
|
||||
const hasProgress = watchProgress && watchProgress.duration > 0;
|
||||
|
||||
const updateProgress = () => {
|
||||
'worklet';
|
||||
progressOpacity.value = withTiming(hasProgress ? 1 : 0, {
|
||||
duration: hasProgress ? 200 : 150,
|
||||
easing: easings.fast
|
||||
});
|
||||
|
||||
try {
|
||||
progressOpacity.value = withTiming(hasProgress ? 1 : 0, {
|
||||
duration: hasProgress ? 200 : 150,
|
||||
easing: easings.fast
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Animation error in updateProgress:', error);
|
||||
}
|
||||
};
|
||||
|
||||
runOnUI(updateProgress)();
|
||||
try {
|
||||
runOnUI(updateProgress)();
|
||||
} catch (error) {
|
||||
console.warn('Failed to run progress animation:', error);
|
||||
}
|
||||
}, [watchProgress]);
|
||||
|
||||
// Ultra-optimized scroll handler with minimal calculations
|
||||
// Cleanup function to cancel animations
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
try {
|
||||
cancelAnimation(screenOpacity);
|
||||
cancelAnimation(contentOpacity);
|
||||
cancelAnimation(heroOpacity);
|
||||
cancelAnimation(heroScale);
|
||||
cancelAnimation(uiElementsOpacity);
|
||||
cancelAnimation(uiElementsTranslateY);
|
||||
cancelAnimation(progressOpacity);
|
||||
cancelAnimation(scrollY);
|
||||
cancelAnimation(headerProgress);
|
||||
cancelAnimation(staticHeaderElementsY);
|
||||
} catch (error) {
|
||||
console.warn('Error canceling animations:', error);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Ultra-optimized scroll handler with minimal calculations and safety
|
||||
const scrollHandler = useAnimatedScrollHandler({
|
||||
onScroll: (event) => {
|
||||
'worklet';
|
||||
|
||||
const rawScrollY = event.contentOffset.y;
|
||||
scrollY.value = rawScrollY;
|
||||
try {
|
||||
const rawScrollY = event.contentOffset.y;
|
||||
scrollY.value = rawScrollY;
|
||||
|
||||
// Single calculation for header threshold
|
||||
const threshold = height * 0.4 - safeAreaTop;
|
||||
const progress = rawScrollY > threshold ? 1 : 0;
|
||||
|
||||
// Use single progress value for all header animations
|
||||
if (headerProgress.value !== progress) {
|
||||
headerProgress.value = withTiming(progress, {
|
||||
duration: progress ? 200 : 150,
|
||||
easing: easings.ultraFast
|
||||
});
|
||||
// Single calculation for header threshold
|
||||
const threshold = height * 0.4 - safeAreaTop;
|
||||
const progress = rawScrollY > threshold ? 1 : 0;
|
||||
|
||||
// Use single progress value for all header animations
|
||||
if (headerProgress.value !== progress) {
|
||||
headerProgress.value = withTiming(progress, {
|
||||
duration: progress ? 200 : 150,
|
||||
easing: easings.ultraFast
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Animation error in scroll handler:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@ const HomeScreen = () => {
|
|||
/>
|
||||
);
|
||||
case 'thisWeek':
|
||||
return <Animated.View entering={FadeIn.duration(400).delay(150)}><ThisWeekSection /></Animated.View>;
|
||||
return <Animated.View entering={FadeIn.duration(300).delay(100)}><ThisWeekSection /></Animated.View>;
|
||||
case 'continueWatching':
|
||||
return <ContinueWatchingSection ref={continueWatchingRef} />;
|
||||
case 'catalog':
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import Animated, {
|
|||
interpolate,
|
||||
withSpring,
|
||||
withDelay,
|
||||
ZoomIn
|
||||
} from 'react-native-reanimated';
|
||||
import { RootStackParamList } from '../navigation/AppNavigator';
|
||||
import { logger } from '../utils/logger';
|
||||
|
|
@ -445,7 +444,7 @@ const SearchScreen = () => {
|
|||
onPress={() => {
|
||||
navigation.navigate('Metadata', { id: item.id, type: item.type });
|
||||
}}
|
||||
entering={FadeIn.duration(500).delay(index * 100)}
|
||||
entering={FadeIn.duration(300).delay(index * 50)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={[styles.horizontalItemPosterContainer, {
|
||||
|
|
@ -650,7 +649,7 @@ const SearchScreen = () => {
|
|||
{seriesResults.length > 0 && (
|
||||
<Animated.View
|
||||
style={styles.carouselContainer}
|
||||
entering={FadeIn.duration(300).delay(100)}
|
||||
entering={FadeIn.duration(300).delay(50)}
|
||||
>
|
||||
<Text style={[styles.carouselTitle, { color: currentTheme.colors.white }]}>
|
||||
TV Shows ({seriesResults.length})
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@ const ShowRatingsScreen = ({ route }: Props) => {
|
|||
<Animated.View
|
||||
key={`s${season.season_number}`}
|
||||
style={styles.ratingColumn}
|
||||
entering={SlideInRight.delay(season.season_number * 50).duration(200)}
|
||||
entering={FadeIn.delay(season.season_number * 20).duration(200)}
|
||||
>
|
||||
<Text style={[styles.headerText, { color: colors.white }]}>S{season.season_number}</Text>
|
||||
</Animated.View>
|
||||
|
|
@ -507,7 +507,7 @@ const ShowRatingsScreen = ({ route }: Props) => {
|
|||
<Animated.View
|
||||
key={`s${season.season_number}e${episodeIndex + 1}`}
|
||||
style={styles.ratingColumn}
|
||||
entering={SlideInRight.delay((season.season_number + episodeIndex) * 10).duration(200)}
|
||||
entering={FadeIn.delay((season.season_number + episodeIndex) * 5).duration(200)}
|
||||
>
|
||||
{season.episodes[episodeIndex] &&
|
||||
<RatingCell
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ const StreamCard = ({ stream, onPress, index, isLoading, statusMessage, theme, i
|
|||
return (
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(200).delay(enterDelay)}
|
||||
layout={Layout.duration(200)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
|
|
@ -268,7 +268,7 @@ const ProviderFilter = memo(({
|
|||
const renderItem = useCallback(({ item, index }: { item: { id: string; name: string }; index: number }) => (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(300).delay(100 + index * 40)}
|
||||
layout={Layout.springify()}
|
||||
exiting={FadeOut.duration(150)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
key={item.id}
|
||||
|
|
@ -1076,7 +1076,7 @@ export const StreamsScreen = () => {
|
|||
return (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(400)}
|
||||
layout={Layout.springify()}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={styles.sectionHeaderContainer}
|
||||
>
|
||||
<View style={styles.sectionHeaderContent}>
|
||||
|
|
@ -1196,11 +1196,11 @@ export const StreamsScreen = () => {
|
|||
{type === 'series' && currentEpisode && (
|
||||
<Animated.View style={[styles.streamsHeroContainer, heroStyle]}>
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(600).springify()}
|
||||
entering={FadeIn.duration(300)}
|
||||
style={StyleSheet.absoluteFill}
|
||||
>
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(800).delay(100).springify().withInitialValues({
|
||||
entering={FadeIn.duration(400).delay(100).withInitialValues({
|
||||
transform: [{ scale: 1.05 }]
|
||||
})}
|
||||
style={StyleSheet.absoluteFill}
|
||||
|
|
|
|||
Loading…
Reference in a new issue