diff --git a/src/components/player/modals/AudioTrackModal.tsx b/src/components/player/modals/AudioTrackModal.tsx
index c2363563..77e3bbba 100644
--- a/src/components/player/modals/AudioTrackModal.tsx
+++ b/src/components/player/modals/AudioTrackModal.tsx
@@ -1,24 +1,13 @@
import React from 'react';
import { View, Text, TouchableOpacity, ScrollView, Dimensions } from 'react-native';
-import { Ionicons, MaterialIcons } from '@expo/vector-icons';
+import { MaterialIcons } from '@expo/vector-icons';
import { BlurView } from 'expo-blur';
import Animated, {
FadeIn,
- FadeOut,
- SlideInDown,
- SlideOutDown,
- FadeInDown,
- FadeInUp,
- Layout,
- withSpring,
- withTiming,
+ FadeOut,
useAnimatedStyle,
useSharedValue,
- interpolate,
- Easing,
- withDelay,
- withSequence,
- runOnJS,
+ withTiming,
} from 'react-native-reanimated';
import { LinearGradient } from 'expo-linear-gradient';
import { styles } from '../utils/playerStyles';
@@ -34,7 +23,6 @@ interface AudioTrackModalProps {
const { width, height } = Dimensions.get('window');
-// Fixed dimensions for the modal
const MODAL_WIDTH = Math.min(width - 32, 520);
const MODAL_MAX_HEIGHT = height * 0.85;
@@ -42,17 +30,14 @@ const AudioBadge = ({
text,
color,
bgColor,
- icon,
- delay = 0
+ icon
}: {
text: string;
color: string;
bgColor: string;
icon?: string;
- delay?: number;
}) => (
-
{text}
-
+
);
export const AudioTrackModal: React.FC = ({
@@ -90,30 +75,19 @@ export const AudioTrackModal: React.FC = ({
selectedAudioTrack,
selectAudioTrack,
}) => {
- const modalScale = useSharedValue(0.9);
const modalOpacity = useSharedValue(0);
React.useEffect(() => {
if (showAudioModal) {
- modalScale.value = withSpring(1, {
- damping: 20,
- stiffness: 300,
- mass: 0.8,
- });
- modalOpacity.value = withTiming(1, {
- duration: 200,
- easing: Easing.out(Easing.quad),
- });
+ modalOpacity.value = withTiming(1, { duration: 200 });
}
}, [showAudioModal]);
const modalStyle = useAnimatedStyle(() => ({
- transform: [{ scale: modalScale.value }],
opacity: modalOpacity.value,
}));
const handleClose = () => {
- modalScale.value = withTiming(0.9, { duration: 150 });
modalOpacity.value = withTiming(0, { duration: 150 });
setTimeout(() => setShowAudioModal(false), 150);
};
@@ -122,8 +96,8 @@ export const AudioTrackModal: React.FC = ({
return (
= ({
padding: 16,
}}
>
- {/* Backdrop */}
= ({
activeOpacity={1}
/>
- {/* Modal Content */}
= ({
modalStyle,
]}
>
- {/* Glassmorphism Background */}
= ({
height: '100%',
}}
>
- {/* Header */}
= ({
width: '100%',
}}
>
-
+
= ({
}}>
Choose from {vlcAudioTracks.length} available track{vlcAudioTracks.length !== 1 ? 's' : ''}
-
+
-
-
-
-
-
+
+
+
- {/* Content */}
= ({
bounces={false}
>
- {vlcAudioTracks.length > 0 ? vlcAudioTracks.map((track, index) => (
- 0 ? vlcAudioTracks.map((track) => (
+ = ({
{selectedAudioTrack === track.id && (
- = ({
}}>
ACTIVE
-
+
)}
@@ -365,7 +326,6 @@ export const AudioTrackModal: React.FC = ({
color="#6B7280"
bgColor="rgba(107, 114, 128, 0.15)"
icon="language"
- delay={50}
/>
)}
@@ -385,20 +345,17 @@ export const AudioTrackModal: React.FC = ({
? 'rgba(249, 115, 22, 0.3)'
: 'rgba(255, 255, 255, 0.1)',
}}>
- {selectedAudioTrack === track.id ? (
-
-
-
- ) : (
-
- )}
+
-
+
)) : (
- = ({
}}>
No audio tracks are available for this content.{'\n'}Try a different source or check your connection.
-
+
)}
diff --git a/src/components/player/modals/SourcesModal.tsx b/src/components/player/modals/SourcesModal.tsx
index ed2ad5ea..cd11cb4a 100644
--- a/src/components/player/modals/SourcesModal.tsx
+++ b/src/components/player/modals/SourcesModal.tsx
@@ -1,23 +1,13 @@
import React from 'react';
import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Dimensions } from 'react-native';
-import { Ionicons, MaterialIcons } from '@expo/vector-icons';
+import { MaterialIcons } from '@expo/vector-icons';
import { BlurView } from 'expo-blur';
import Animated, {
FadeIn,
- FadeOut,
- SlideInDown,
- SlideOutDown,
- FadeInDown,
- FadeInUp,
- Layout,
- withSpring,
- withTiming,
+ FadeOut,
useAnimatedStyle,
useSharedValue,
- interpolate,
- Easing,
- withDelay,
- withSequence,
+ withTiming,
runOnJS,
} from 'react-native-reanimated';
import { LinearGradient } from 'expo-linear-gradient';
@@ -36,7 +26,6 @@ interface SourcesModalProps {
const { width, height } = Dimensions.get('window');
-// Fixed dimensions for the modal
const MODAL_WIDTH = Math.min(width - 32, 520);
const MODAL_MAX_HEIGHT = height * 0.85;
@@ -59,8 +48,7 @@ const QualityIndicator = ({ quality }: { quality: string | null }) => {
}
return (
- {
}}>
{label}
-
+
);
};
@@ -95,17 +83,14 @@ const StreamMetaBadge = ({
text,
color,
bgColor,
- icon,
- delay = 0
+ icon
}: {
text: string;
color: string;
bgColor: string;
icon?: string;
- delay?: number;
}) => (
-
{text}
-
+
);
const SourcesModal: React.FC = ({
@@ -144,32 +129,33 @@ const SourcesModal: React.FC = ({
onSelectStream,
isChangingSource,
}) => {
- const modalScale = useSharedValue(0.9);
const modalOpacity = useSharedValue(0);
React.useEffect(() => {
if (showSourcesModal) {
- modalScale.value = withSpring(1, {
- damping: 20,
- stiffness: 300,
- mass: 0.8,
- });
- modalOpacity.value = withTiming(1, {
- duration: 200,
- easing: Easing.out(Easing.quad),
- });
+ modalOpacity.value = withTiming(1, { duration: 200 });
+ } else {
+ modalOpacity.value = withTiming(0, { duration: 150 });
}
+
+ return () => {
+ modalOpacity.value = 0;
+ };
}, [showSourcesModal]);
const modalStyle = useAnimatedStyle(() => ({
- transform: [{ scale: modalScale.value }],
opacity: modalOpacity.value,
}));
+ const handleClose = () => {
+ modalOpacity.value = withTiming(0, { duration: 150 }, () => {
+ runOnJS(setShowSourcesModal)(false);
+ });
+ };
+
if (!showSourcesModal) return null;
const sortedProviders = Object.entries(availableStreams).sort(([a], [b]) => {
- // Put HDRezka first
if (a === 'hdrezka') return -1;
if (b === 'hdrezka') return 1;
return 0;
@@ -191,16 +177,10 @@ const SourcesModal: React.FC = ({
return stream.url === currentStreamUrl;
};
- const handleClose = () => {
- modalScale.value = withTiming(0.9, { duration: 150 });
- modalOpacity.value = withTiming(0, { duration: 150 });
- setTimeout(() => setShowSourcesModal(false), 150);
- };
-
return (
= ({
padding: 16,
}}
>
- {/* Backdrop */}
= ({
activeOpacity={1}
/>
- {/* Modal Content */}
= ({
modalStyle,
]}
>
- {/* Glassmorphism Background */}
= ({
height: '100%',
}}
>
- {/* Header */}
= ({
width: '100%',
}}
>
-
+
= ({
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 2,
}}>
- Switch Source
+ Video Sources
= ({
fontWeight: '500',
letterSpacing: 0.2,
}}>
- Choose from {Object.values(availableStreams).reduce((acc, curr) => acc + curr.streams.length, 0)} available streams
+ Choose from {Object.values(availableStreams).reduce((acc, curr) => acc + curr.streams.length, 0)} available sources
-
+
-
-
-
-
-
+
+
+
- {/* Content */}
= ({
}}
bounces={false}
>
- {sortedProviders.map(([providerId, { streams, addonName }], providerIndex) => (
- 0 ? 32 : 0,
- width: '100%',
- }}
- >
- {/* Provider Header */}
+ {sortedProviders.map(([providerId, { streams, addonName }]) => (
+
-
-
-
- {addonName}
-
-
- Provider • {streams.length} stream{streams.length !== 1 ? 's' : ''}
-
-
-
+
+ {addonName}
+
{streams.length}
-
- {/* Streams Grid */}
-
- {streams.map((stream, index) => {
- const quality = getQualityFromTitle(stream.title);
- const isSelected = isStreamSelected(stream);
- const isHDR = stream.title?.toLowerCase().includes('hdr');
- const isDolby = stream.title?.toLowerCase().includes('dolby') || stream.title?.includes('DV');
- const size = stream.title?.match(/💾\s*([\d.]+\s*[GM]B)/)?.[1];
- const isDebrid = stream.behaviorHints?.cached;
- const isHDRezka = providerId === 'hdrezka';
- return (
- {
+ const isSelected = isStreamSelected(stream);
+ const quality = getQualityFromTitle(stream.title);
+
+ return (
+
+ handleStreamSelect(stream)}
+ activeOpacity={0.85}
+ disabled={isChangingSource}
>
- handleStreamSelect(stream)}
- disabled={isChangingSource || isSelected}
- activeOpacity={0.85}
- >
-
- {/* Stream Info */}
-
- {/* Title Row */}
-
+
+
+
-
- {isHDRezka ? `HDRezka ${stream.title}` : (stream.name || stream.title || 'Unnamed Stream')}
-
-
- {isSelected && (
-
-
-
- PLAYING
-
-
- )}
-
- {isChangingSource && isSelected && (
-
-
-
- Switching...
-
-
- )}
-
+ {stream.title || 'Untitled Stream'}
+
- {/* Subtitle */}
- {!isHDRezka && stream.title && stream.title !== stream.name && (
-
- {stream.title}
-
+ {isSelected && (
+
+
+
+ PLAYING
+
+
)}
-
- {/* Enhanced Meta Info */}
-
-
-
- {isDolby && (
-
- )}
-
- {isHDR && (
-
- )}
-
- {size && (
-
- )}
-
- {isDebrid && (
-
- )}
-
- {isHDRezka && (
-
- )}
-
- {/* Enhanced Action Icon */}
- {isSelected ? (
-
-
-
- ) : (
-
- )}
+ {quality && }
+
-
-
- );
- })}
-
-
+
+
+ {isChangingSource ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+ })}
+
))}
diff --git a/src/components/player/modals/SubtitleModals.tsx b/src/components/player/modals/SubtitleModals.tsx
index 264c0096..455905a7 100644
--- a/src/components/player/modals/SubtitleModals.tsx
+++ b/src/components/player/modals/SubtitleModals.tsx
@@ -1,23 +1,13 @@
import React from 'react';
import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Image, Dimensions } from 'react-native';
-import { Ionicons, MaterialIcons } from '@expo/vector-icons';
+import { MaterialIcons } from '@expo/vector-icons';
import { BlurView } from 'expo-blur';
import Animated, {
FadeIn,
- FadeOut,
- SlideInDown,
- SlideOutDown,
- FadeInDown,
- FadeInUp,
- Layout,
- withSpring,
- withTiming,
+ FadeOut,
useAnimatedStyle,
useSharedValue,
- interpolate,
- Easing,
- withDelay,
- withSequence,
+ withTiming,
runOnJS,
} from 'react-native-reanimated';
import { LinearGradient } from 'expo-linear-gradient';
@@ -47,7 +37,6 @@ interface SubtitleModalsProps {
const { width, height } = Dimensions.get('window');
-// Fixed dimensions for the modals
const MODAL_WIDTH = Math.min(width - 32, 520);
const MODAL_MAX_HEIGHT = height * 0.85;
@@ -55,17 +44,14 @@ const SubtitleBadge = ({
text,
color,
bgColor,
- icon,
- delay = 0
+ icon
}: {
text: string;
color: string;
bgColor: string;
icon?: string;
- delay?: number;
}) => (
-
{text}
-
+
);
export const SubtitleModals: React.FC = ({
@@ -115,59 +101,51 @@ export const SubtitleModals: React.FC = ({
increaseSubtitleSize,
decreaseSubtitleSize,
}) => {
- const modalScale = useSharedValue(0.9);
const modalOpacity = useSharedValue(0);
- const languageModalScale = useSharedValue(0.9);
const languageModalOpacity = useSharedValue(0);
React.useEffect(() => {
if (showSubtitleModal) {
- modalScale.value = withSpring(1, {
- damping: 20,
- stiffness: 300,
- mass: 0.8,
- });
- modalOpacity.value = withTiming(1, {
- duration: 200,
- easing: Easing.out(Easing.quad),
- });
+ modalOpacity.value = withTiming(1, { duration: 200 });
+ } else {
+ modalOpacity.value = withTiming(0, { duration: 150 });
}
+
+ return () => {
+ modalOpacity.value = 0;
+ };
}, [showSubtitleModal]);
React.useEffect(() => {
if (showSubtitleLanguageModal) {
- languageModalScale.value = withSpring(1, {
- damping: 20,
- stiffness: 300,
- mass: 0.8,
- });
- languageModalOpacity.value = withTiming(1, {
- duration: 200,
- easing: Easing.out(Easing.quad),
- });
+ languageModalOpacity.value = withTiming(1, { duration: 200 });
+ } else {
+ languageModalOpacity.value = withTiming(0, { duration: 150 });
}
+
+ return () => {
+ languageModalOpacity.value = 0;
+ };
}, [showSubtitleLanguageModal]);
const modalStyle = useAnimatedStyle(() => ({
- transform: [{ scale: modalScale.value }],
opacity: modalOpacity.value,
}));
const languageModalStyle = useAnimatedStyle(() => ({
- transform: [{ scale: languageModalScale.value }],
opacity: languageModalOpacity.value,
}));
const handleClose = () => {
- modalScale.value = withTiming(0.9, { duration: 150 });
- modalOpacity.value = withTiming(0, { duration: 150 });
- setTimeout(() => setShowSubtitleModal(false), 150);
+ modalOpacity.value = withTiming(0, { duration: 150 }, () => {
+ runOnJS(setShowSubtitleModal)(false);
+ });
};
const handleLanguageClose = () => {
- languageModalScale.value = withTiming(0.9, { duration: 150 });
- languageModalOpacity.value = withTiming(0, { duration: 150 });
- setTimeout(() => setShowSubtitleLanguageModal(false), 150);
+ languageModalOpacity.value = withTiming(0, { duration: 150 }, () => {
+ runOnJS(setShowSubtitleLanguageModal)(false);
+ });
};
// Render subtitle settings modal
@@ -176,8 +154,8 @@ export const SubtitleModals: React.FC = ({
return (
= ({
padding: 16,
}}
>
- {/* Backdrop */}
= ({
activeOpacity={1}
/>
- {/* Modal Content */}
= ({
modalStyle,
]}
>
- {/* Glassmorphism Background */}
= ({
height: '100%',
}}
>
- {/* Header */}
= ({
width: '100%',
}}
>
-
+
= ({
fontWeight: '500',
letterSpacing: 0.2,
}}>
- Configure subtitles and language options
+ Customize your subtitle experience
-
+
-
-
-
-
-
+
+
+
- {/* Content */}
= ({
bounces={false}
>
-
- {/* External Subtitles Section */}
-
+
+
+ Size Adjustment
+
+
-
-
+ onPress={decreaseSubtitleSize}
+ >
+
+
+
+
- External Subtitles
+ {subtitleSize}
- High quality with size control
+ Font Size
-
-
- {/* Custom subtitles option */}
- {customSubtitles.length > 0 && (
-
- {
- selectTextTrack(-999);
- setShowSubtitleModal(false);
- }}
- activeOpacity={0.85}
- >
-
-
-
-
- Custom Subtitles
-
-
- {useCustomSubtitles && (
-
-
-
- ACTIVE
-
-
- )}
-
-
-
-
-
-
-
-
-
- {useCustomSubtitles ? (
-
-
-
- ) : (
-
- )}
-
-
-
-
- )}
-
- {/* Search for external subtitles */}
-
+
{
- handleClose();
- fetchAvailableSubtitles();
- }}
- disabled={isLoadingSubtitleList}
- activeOpacity={0.85}
- >
-
- {isLoadingSubtitleList ? (
-
- ) : (
-
- )}
-
- {isLoadingSubtitleList ? 'Searching...' : 'Search Online Subtitles'}
-
-
+ alignItems: 'center',
+ borderWidth: 2,
+ borderColor: 'rgba(255, 255, 255, 0.1)',
+ }}
+ onPress={increaseSubtitleSize}
+ >
+
-
-
+
+
- {/* Subtitle Size Controls */}
- {useCustomSubtitles && (
-
+
+ Subtitle Source
+
+
+ setShowSubtitleLanguageModal(true)}
>
-
- Size Control
+ Change Language
-
- Adjust font size for better readability
-
-
-
-
-
-
-
-
-
-
-
- {subtitleSize}px
-
-
- Font Size
-
-
-
-
-
-
-
-
-
- )}
-
- {/* Available built-in subtitle tracks */}
- {vlcTextTracks.length > 0 ? vlcTextTracks.map((track, index) => (
-
- {
- selectTextTrack(track.id);
- handleClose();
- }}
- activeOpacity={0.85}
- >
-
-
-
-
- {getTrackDisplayName(track)}
-
-
- {(selectedTextTrack === track.id && !useCustomSubtitles) && (
-
-
-
- ACTIVE
-
-
- )}
-
-
-
+
+ {selectedTextTrack !== -1 && (
- t.id === selectedTextTrack)?.language?.toUpperCase() || 'UNKNOWN'}
color="#6B7280"
bgColor="rgba(107, 114, 128, 0.15)"
- icon="format-size"
- delay={50}
/>
-
-
-
-
- {(selectedTextTrack === track.id && !useCustomSubtitles) ? (
-
-
-
- ) : (
-
)}
-
-
- )) : (
-
-
-
- No built-in subtitles available
-
-
- Try searching for external subtitles
-
-
- )}
+
+
+
+
@@ -866,11 +432,11 @@ export const SubtitleModals: React.FC = ({
// Render subtitle language selection modal
const renderSubtitleLanguageModal = () => {
if (!showSubtitleLanguageModal) return null;
-
+
return (
= ({
padding: 16,
}}
>
- {/* Backdrop */}
= ({
activeOpacity={1}
/>
- {/* Modal Content */}
= ({
languageModalStyle,
]}
>
- {/* Glassmorphism Background */}
= ({
height: '100%',
}}
>
- {/* Header */}
= ({
width: '100%',
}}
>
-
+
= ({
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 2,
}}>
- Select Language
+ Subtitle Language
= ({
fontWeight: '500',
letterSpacing: 0.2,
}}>
- Choose from {availableSubtitles.length} available languages
+ Choose from {vlcTextTracks.length} available tracks
-
+
-
-
-
-
-
+
+
+
- {/* Content */}
= ({
}}
bounces={false}
>
- {availableSubtitles.length > 0 ? availableSubtitles.map((subtitle, index) => (
-
-
+ {vlcTextTracks.map((track) => (
+ loadWyzieSubtitle(subtitle)}
- disabled={isLoadingSubtitles}
- activeOpacity={0.85}
>
-
+ {
+ selectTextTrack(track.id);
+ handleLanguageClose();
+ }}
+ activeOpacity={0.85}
+ >
-
-
-
+
- {formatLanguage(subtitle.language)}
-
-
+ {getTrackDisplayName(track)}
+
+
+ {selectedTextTrack === track.id && (
+
+
+
+ ACTIVE
+
+
+ )}
+
+
+
- {subtitle.display}
-
+
+ {track.language && (
+
+ )}
+
+
+
+
+
-
-
- {isLoadingSubtitles ? (
-
- ) : (
-
- )}
-
-
-
-
- )) : (
-
-
-
- No subtitles found
-
-
- No subtitles are available for this content.{'\n'}Try searching again or check back later.
-
-
- )}
+
+
+ ))}
+