NuvioStreaming_backup_24-10-25/src/components/player/modals/AudioTrackModal.tsx
tapframe 046c9e3f97 Enhance modals with fixed dimensions and improved layout
This update introduces fixed dimensions for the AudioTrackModal, SourcesModal, and SubtitleModals, ensuring consistent sizing across different screen sizes. The layout has been refined to improve visual clarity and usability, including adjustments to scroll view heights and modal styles. Additionally, the integration of a new XPRIME source in the metadata handling enhances the overall streaming experience by prioritizing this source in the selection process.
2025-06-11 02:10:10 +05:30

444 lines
No EOL
15 KiB
TypeScript

import React from 'react';
import { View, Text, TouchableOpacity, ScrollView, 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 { getTrackDisplayName } from '../utils/playerUtils';
interface AudioTrackModalProps {
showAudioModal: boolean;
setShowAudioModal: (show: boolean) => void;
vlcAudioTracks: Array<{id: number, name: string, language?: string}>;
selectedAudioTrack: number | null;
selectAudioTrack: (trackId: number) => void;
}
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;
const AudioBadge = ({
text,
color,
bgColor,
icon,
delay = 0
}: {
text: string;
color: string;
bgColor: string;
icon?: string;
delay?: number;
}) => (
<Animated.View
entering={FadeInUp.duration(200).delay(delay)}
style={{
backgroundColor: bgColor,
borderColor: `${color}40`,
borderWidth: 1,
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
flexDirection: 'row',
alignItems: 'center',
elevation: 2,
shadowColor: color,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.3,
shadowRadius: 2,
}}
>
{icon && (
<MaterialIcons name={icon as any} size={12} color={color} style={{ marginRight: 4 }} />
)}
<Text style={{
color: color,
fontSize: 10,
fontWeight: '700',
letterSpacing: 0.3,
}}>
{text}
</Text>
</Animated.View>
);
export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
showAudioModal,
setShowAudioModal,
vlcAudioTracks,
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),
});
}
}, [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);
};
if (!showAudioModal) return null;
return (
<Animated.View
entering={FadeIn.duration(250)}
exiting={FadeOut.duration(200)}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.9)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 9999,
padding: 16,
}}
>
{/* Backdrop */}
<TouchableOpacity
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
onPress={handleClose}
activeOpacity={1}
/>
{/* Modal Content */}
<Animated.View
style={[
{
width: MODAL_WIDTH,
maxHeight: MODAL_MAX_HEIGHT,
minHeight: height * 0.3,
overflow: 'hidden',
elevation: 25,
shadowColor: '#000',
shadowOffset: { width: 0, height: 12 },
shadowOpacity: 0.4,
shadowRadius: 25,
alignSelf: 'center',
},
modalStyle,
]}
>
{/* Glassmorphism Background */}
<BlurView
intensity={100}
tint="dark"
style={{
borderRadius: 28,
overflow: 'hidden',
backgroundColor: 'rgba(26, 26, 26, 0.8)',
width: '100%',
height: '100%',
}}
>
{/* Header */}
<LinearGradient
colors={[
'rgba(249, 115, 22, 0.95)',
'rgba(234, 88, 12, 0.95)',
'rgba(194, 65, 12, 0.9)'
]}
locations={[0, 0.6, 1]}
style={{
paddingHorizontal: 28,
paddingVertical: 24,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderBottomWidth: 1,
borderBottomColor: 'rgba(255, 255, 255, 0.1)',
width: '100%',
}}
>
<Animated.View
entering={FadeInDown.duration(300).delay(100)}
style={{ flex: 1 }}
>
<Text style={{
color: '#fff',
fontSize: 24,
fontWeight: '800',
letterSpacing: -0.8,
textShadowColor: 'rgba(0, 0, 0, 0.3)',
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 2,
}}>
Audio Tracks
</Text>
<Text style={{
color: 'rgba(255, 255, 255, 0.85)',
fontSize: 14,
marginTop: 4,
fontWeight: '500',
letterSpacing: 0.2,
}}>
Choose from {vlcAudioTracks.length} available track{vlcAudioTracks.length !== 1 ? 's' : ''}
</Text>
</Animated.View>
<Animated.View entering={BounceIn.duration(400).delay(200)}>
<TouchableOpacity
style={{
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: 'rgba(255, 255, 255, 0.15)',
justifyContent: 'center',
alignItems: 'center',
marginLeft: 16,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.2)',
}}
onPress={handleClose}
activeOpacity={0.7}
>
<MaterialIcons name="close" size={20} color="#fff" />
</TouchableOpacity>
</Animated.View>
</LinearGradient>
{/* Content */}
<ScrollView
style={{
maxHeight: MODAL_MAX_HEIGHT - 100, // Account for header height
backgroundColor: 'transparent',
width: '100%',
}}
showsVerticalScrollIndicator={false}
contentContainerStyle={{
padding: 24,
paddingBottom: 32,
width: '100%',
}}
bounces={false}
>
<View style={styles.modernTrackListContainer}>
{vlcAudioTracks.length > 0 ? vlcAudioTracks.map((track, index) => (
<Animated.View
key={track.id}
entering={FadeInDown.duration(300).delay(150 + (index * 50))}
layout={Layout.springify()}
style={{
marginBottom: 16,
width: '100%',
}}
>
<TouchableOpacity
style={{
backgroundColor: selectedAudioTrack === track.id
? 'rgba(249, 115, 22, 0.08)'
: 'rgba(255, 255, 255, 0.03)',
borderRadius: 20,
padding: 20,
borderWidth: 2,
borderColor: selectedAudioTrack === track.id
? 'rgba(249, 115, 22, 0.4)'
: 'rgba(255, 255, 255, 0.08)',
elevation: selectedAudioTrack === track.id ? 8 : 3,
shadowColor: selectedAudioTrack === track.id ? '#F97316' : '#000',
shadowOffset: { width: 0, height: selectedAudioTrack === track.id ? 4 : 2 },
shadowOpacity: selectedAudioTrack === track.id ? 0.3 : 0.1,
shadowRadius: selectedAudioTrack === track.id ? 12 : 6,
width: '100%',
}}
onPress={() => {
selectAudioTrack(track.id);
handleClose();
}}
activeOpacity={0.85}
>
<View style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
}}>
<View style={{ flex: 1, marginRight: 16 }}>
<View style={{
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
gap: 12,
}}>
<Text style={{
color: selectedAudioTrack === track.id ? '#fff' : 'rgba(255, 255, 255, 0.95)',
fontSize: 16,
fontWeight: '700',
letterSpacing: -0.2,
flex: 1,
}}>
{getTrackDisplayName(track)}
</Text>
{selectedAudioTrack === track.id && (
<Animated.View
entering={BounceIn.duration(300)}
style={{
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(249, 115, 22, 0.25)',
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 14,
borderWidth: 1,
borderColor: 'rgba(249, 115, 22, 0.5)',
}}
>
<MaterialIcons name="volume-up" size={12} color="#F97316" />
<Text style={{
color: '#F97316',
fontSize: 10,
fontWeight: '800',
marginLeft: 3,
letterSpacing: 0.3,
}}>
ACTIVE
</Text>
</Animated.View>
)}
</View>
<View style={{
flexDirection: 'row',
flexWrap: 'wrap',
gap: 6,
alignItems: 'center',
}}>
<AudioBadge
text="AUDIO TRACK"
color="#F97316"
bgColor="rgba(249, 115, 22, 0.15)"
icon="audiotrack"
/>
{track.language && (
<AudioBadge
text={track.language.toUpperCase()}
color="#6B7280"
bgColor="rgba(107, 114, 128, 0.15)"
icon="language"
delay={50}
/>
)}
</View>
</View>
<View style={{
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: selectedAudioTrack === track.id
? 'rgba(249, 115, 22, 0.15)'
: 'rgba(255, 255, 255, 0.05)',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: selectedAudioTrack === track.id
? 'rgba(249, 115, 22, 0.3)'
: 'rgba(255, 255, 255, 0.1)',
}}>
{selectedAudioTrack === track.id ? (
<Animated.View entering={ZoomIn.duration(200)}>
<MaterialIcons name="check-circle" size={24} color="#F97316" />
</Animated.View>
) : (
<MaterialIcons name="volume-up" size={24} color="rgba(255,255,255,0.6)" />
)}
</View>
</View>
</TouchableOpacity>
</Animated.View>
)) : (
<Animated.View
entering={FadeInDown.duration(300).delay(150)}
style={{
backgroundColor: 'rgba(255, 255, 255, 0.02)',
borderRadius: 20,
padding: 40,
alignItems: 'center',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.05)',
width: '100%',
}}
>
<MaterialIcons name="volume-off" size={48} color="rgba(255, 255, 255, 0.3)" />
<Text style={{
color: 'rgba(255, 255, 255, 0.6)',
fontSize: 18,
fontWeight: '700',
marginTop: 16,
textAlign: 'center',
letterSpacing: -0.3,
}}>
No audio tracks found
</Text>
<Text style={{
color: 'rgba(255, 255, 255, 0.4)',
fontSize: 14,
marginTop: 8,
textAlign: 'center',
lineHeight: 20,
}}>
No audio tracks are available for this content.{'\n'}Try a different source or check your connection.
</Text>
</Animated.View>
)}
</View>
</ScrollView>
</BlurView>
</Animated.View>
</Animated.View>
);
};
export default AudioTrackModal;