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