diff --git a/src/components/player/modals/SubtitleModals.tsx b/src/components/player/modals/SubtitleModals.tsx
index 582cf714..e5259633 100644
--- a/src/components/player/modals/SubtitleModals.tsx
+++ b/src/components/player/modals/SubtitleModals.tsx
@@ -1,6 +1,28 @@
import React from 'react';
-import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Image } from 'react-native';
-import { Ionicons } from '@expo/vector-icons';
+import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Image, Dimensions } from 'react-native';
+import { Ionicons, MaterialIcons } from '@expo/vector-icons';
+import { BlurView } from 'expo-blur';
+import Animated, {
+ FadeIn,
+ FadeOut,
+ SlideInDown,
+ SlideOutDown,
+ FadeInDown,
+ FadeInUp,
+ Layout,
+ withSpring,
+ withTiming,
+ useAnimatedStyle,
+ useSharedValue,
+ interpolate,
+ Easing,
+ withDelay,
+ withSequence,
+ runOnJS,
+ BounceIn,
+ ZoomIn
+} from 'react-native-reanimated';
+import { LinearGradient } from 'expo-linear-gradient';
import { styles } from '../utils/playerStyles';
import { WyzieSubtitle, SubtitleCue } from '../utils/playerTypes';
import { getTrackDisplayName, formatLanguage } from '../utils/playerUtils';
@@ -25,6 +47,53 @@ interface SubtitleModalsProps {
decreaseSubtitleSize: () => void;
}
+const { width, height } = Dimensions.get('window');
+
+const SubtitleBadge = ({
+ text,
+ color,
+ bgColor,
+ icon,
+ delay = 0
+}: {
+ text: string;
+ color: string;
+ bgColor: string;
+ icon?: string;
+ delay?: number;
+}) => (
+
+ {icon && (
+
+ )}
+
+ {text}
+
+
+);
+
export const SubtitleModals: React.FC = ({
showSubtitleModal,
setShowSubtitleModal,
@@ -44,169 +113,742 @@ 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),
+ });
+ }
+ }, [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),
+ });
+ }
+ }, [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);
+ };
+
+ const handleLanguageClose = () => {
+ languageModalScale.value = withTiming(0.9, { duration: 150 });
+ languageModalOpacity.value = withTiming(0, { duration: 150 });
+ setTimeout(() => setShowSubtitleLanguageModal(false), 150);
+ };
+
// Render subtitle settings modal
const renderSubtitleModal = () => {
if (!showSubtitleModal) return null;
return (
-
-
-
- Subtitle Settings
- setShowSubtitleModal(false)}
+
+ {/* Backdrop */}
+
+
+ {/* Modal Content */}
+
+ {/* Glassmorphism Background */}
+
+ {/* Header */}
+
-
-
-
-
-
-
+
+
+ Subtitle Settings
+
+
+ Configure subtitles and language options
+
+
- {/* External Subtitles Section - Priority */}
-
- External Subtitles
- High quality subtitles with size control
+
+
+
+
+
+
+
+ {/* Content */}
+
+
- {/* Custom subtitles option - show if loaded */}
- {customSubtitles.length > 0 ? (
- {
- selectTextTrack(-999);
- setShowSubtitleModal(false);
- }}
- >
-
-
-
-
- Custom Subtitles
-
- {customSubtitles.length} cues • Size adjustable
+ {/* External Subtitles Section */}
+
+
+
+
+
+ External Subtitles
+
+
+ High quality with size control
- {useCustomSubtitles && (
-
-
-
- )}
-
- ) : null}
-
- {/* Search for external subtitles */}
- {
- setShowSubtitleModal(false);
- fetchAvailableSubtitles();
- }}
- disabled={isLoadingSubtitleList}
- >
-
- {isLoadingSubtitleList ? (
-
- ) : (
-
- )}
-
- {isLoadingSubtitleList ? 'Searching...' : 'Search Online Subtitles'}
-
-
-
- {/* Subtitle Size Controls - Only for custom subtitles */}
- {useCustomSubtitles && (
-
- Size Control
-
- 0 && (
+
-
-
-
- {subtitleSize}px
- Font Size
-
-
-
-
-
-
- )}
-
- {/* Built-in Subtitles Section */}
-
- Built-in Subtitles
- System default sizing • No customization
-
- {/* Off option */}
- {
- selectTextTrack(-1);
- setShowSubtitleModal(false);
- }}
- >
-
-
-
-
- Disabled
- No subtitles
-
- {(selectedTextTrack === -1 && !useCustomSubtitles) && (
-
-
-
+ {
+ 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'}
+
+
+
+
+
+
+ {/* Subtitle Size Controls */}
+ {useCustomSubtitles && (
+
+
+
+
+
+ Size Control
+
+
+ Adjust font size for better readability
+
+
+
+
+
+
+
+
+
+
+
+
+ {subtitleSize}px
+
+
+ Font Size
+
+
+
+
+
+
+
+
+
+ )}
{/* Available built-in subtitle tracks */}
- {vlcTextTracks.length > 0 ? vlcTextTracks.map(track => (
- 0 ? vlcTextTracks.map((track, index) => (
+ {
- selectTextTrack(track.id);
- setShowSubtitleModal(false);
+ entering={FadeInDown.duration(300).delay(400 + (index * 50))}
+ layout={Layout.springify()}
+ style={{ marginBottom: 16 }}
+ >
+ {
+ selectTextTrack(track.id);
+ handleClose();
+ }}
+ activeOpacity={0.85}
+ >
+
+
+
+
+ {getTrackDisplayName(track)}
+
+
+ {(selectedTextTrack === track.id && !useCustomSubtitles) && (
+
+
+
+ ACTIVE
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ {(selectedTextTrack === track.id && !useCustomSubtitles) ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+ )) : (
+
-
-
-
-
-
- {getTrackDisplayName(track)}
-
-
- Built-in track • System font size
-
-
- {(selectedTextTrack === track.id && !useCustomSubtitles) && (
-
-
-
- )}
-
- )) : (
-
-
- No built-in subtitles available
-
+
+
+ No built-in subtitles available
+
+
+ Try searching for external subtitles
+
+
)}
-
-
-
-
+
+
+
+
);
};
@@ -215,58 +857,270 @@ export const SubtitleModals: React.FC = ({
if (!showSubtitleLanguageModal) return null;
return (
-
-
-
- Select Language
- setShowSubtitleLanguageModal(false)}
+
+ {/* Backdrop */}
+
+
+ {/* Modal Content */}
+
+ {/* Glassmorphism Background */}
+
+ {/* Header */}
+
-
-
-
-
-
-
- {availableSubtitles.length > 0 ? availableSubtitles.map(subtitle => (
- loadWyzieSubtitle(subtitle)}
- disabled={isLoadingSubtitles}
+
+
+ Select Language
+
+
+ Choose from {availableSubtitles.length} available languages
+
+
+
+
+
-
-
-
-
- {formatLanguage(subtitle.language)}
-
-
- {subtitle.display}
-
-
-
- {isLoadingSubtitles && (
-
- )}
+
+
+
+
+ {/* Content */}
+
+ {availableSubtitles.length > 0 ? availableSubtitles.map((subtitle, index) => (
+
+ loadWyzieSubtitle(subtitle)}
+ disabled={isLoadingSubtitles}
+ activeOpacity={0.85}
+ >
+
+
+
+
+
+ {formatLanguage(subtitle.language)}
+
+
+ {subtitle.display}
+
+
+
+
+
+ {isLoadingSubtitles ? (
+
+ ) : (
+
+ )}
+
+
+
+
)) : (
-
-
-
- No subtitles found for this content
+
+
+
+ No subtitles found
-
+
+ No subtitles are available for this content.{'\n'}Try searching again or check back later.
+
+
)}
-
-
-
-
+
+
+
+
);
};