mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-29 20:33:42 +00:00
Refactor modals to improve animation handling and simplify component structure
This update refines the AudioTrackModal, SourcesModal, and SubtitleModals components by removing unnecessary animated views and optimizing the animation durations for a smoother user experience. The modal structure has been simplified, enhancing readability and maintainability. Additionally, adjustments to the modal close handling improve responsiveness. These changes aim to create a more cohesive and efficient interface across the application.
This commit is contained in:
parent
3fbec2c096
commit
314ece1238
3 changed files with 522 additions and 1173 deletions
|
|
@ -1,24 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity, ScrollView, Dimensions } from 'react-native';
|
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 { BlurView } from 'expo-blur';
|
||||||
import Animated, {
|
import Animated, {
|
||||||
FadeIn,
|
FadeIn,
|
||||||
FadeOut,
|
FadeOut,
|
||||||
SlideInDown,
|
|
||||||
SlideOutDown,
|
|
||||||
FadeInDown,
|
|
||||||
FadeInUp,
|
|
||||||
Layout,
|
|
||||||
withSpring,
|
|
||||||
withTiming,
|
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
interpolate,
|
withTiming,
|
||||||
Easing,
|
|
||||||
withDelay,
|
|
||||||
withSequence,
|
|
||||||
runOnJS,
|
|
||||||
} 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';
|
||||||
|
|
@ -34,7 +23,6 @@ interface AudioTrackModalProps {
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
|
||||||
// Fixed dimensions for the modal
|
|
||||||
const MODAL_WIDTH = Math.min(width - 32, 520);
|
const MODAL_WIDTH = Math.min(width - 32, 520);
|
||||||
const MODAL_MAX_HEIGHT = height * 0.85;
|
const MODAL_MAX_HEIGHT = height * 0.85;
|
||||||
|
|
||||||
|
|
@ -42,17 +30,14 @@ const AudioBadge = ({
|
||||||
text,
|
text,
|
||||||
color,
|
color,
|
||||||
bgColor,
|
bgColor,
|
||||||
icon,
|
icon
|
||||||
delay = 0
|
|
||||||
}: {
|
}: {
|
||||||
text: string;
|
text: string;
|
||||||
color: string;
|
color: string;
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
delay?: number;
|
|
||||||
}) => (
|
}) => (
|
||||||
<Animated.View
|
<View
|
||||||
entering={FadeInUp.duration(200).delay(delay)}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: bgColor,
|
backgroundColor: bgColor,
|
||||||
borderColor: `${color}40`,
|
borderColor: `${color}40`,
|
||||||
|
|
@ -80,7 +65,7 @@ const AudioBadge = ({
|
||||||
}}>
|
}}>
|
||||||
{text}
|
{text}
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
|
|
@ -90,30 +75,19 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
selectedAudioTrack,
|
selectedAudioTrack,
|
||||||
selectAudioTrack,
|
selectAudioTrack,
|
||||||
}) => {
|
}) => {
|
||||||
const modalScale = useSharedValue(0.9);
|
|
||||||
const modalOpacity = useSharedValue(0);
|
const modalOpacity = useSharedValue(0);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (showAudioModal) {
|
if (showAudioModal) {
|
||||||
modalScale.value = withSpring(1, {
|
modalOpacity.value = withTiming(1, { duration: 200 });
|
||||||
damping: 20,
|
|
||||||
stiffness: 300,
|
|
||||||
mass: 0.8,
|
|
||||||
});
|
|
||||||
modalOpacity.value = withTiming(1, {
|
|
||||||
duration: 200,
|
|
||||||
easing: Easing.out(Easing.quad),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [showAudioModal]);
|
}, [showAudioModal]);
|
||||||
|
|
||||||
const modalStyle = useAnimatedStyle(() => ({
|
const modalStyle = useAnimatedStyle(() => ({
|
||||||
transform: [{ scale: modalScale.value }],
|
|
||||||
opacity: modalOpacity.value,
|
opacity: modalOpacity.value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
modalScale.value = withTiming(0.9, { duration: 150 });
|
|
||||||
modalOpacity.value = withTiming(0, { duration: 150 });
|
modalOpacity.value = withTiming(0, { duration: 150 });
|
||||||
setTimeout(() => setShowAudioModal(false), 150);
|
setTimeout(() => setShowAudioModal(false), 150);
|
||||||
};
|
};
|
||||||
|
|
@ -122,8 +96,8 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={FadeIn.duration(250)}
|
entering={FadeIn.duration(200)}
|
||||||
exiting={FadeOut.duration(200)}
|
exiting={FadeOut.duration(150)}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|
@ -137,7 +111,6 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
padding: 16,
|
padding: 16,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Backdrop */}
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
@ -150,7 +123,6 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
activeOpacity={1}
|
activeOpacity={1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal Content */}
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
|
|
@ -168,7 +140,6 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
modalStyle,
|
modalStyle,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{/* Glassmorphism Background */}
|
|
||||||
<BlurView
|
<BlurView
|
||||||
intensity={100}
|
intensity={100}
|
||||||
tint="dark"
|
tint="dark"
|
||||||
|
|
@ -180,7 +151,6 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[
|
colors={[
|
||||||
'rgba(249, 115, 22, 0.95)',
|
'rgba(249, 115, 22, 0.95)',
|
||||||
|
|
@ -199,10 +169,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Animated.View
|
<View style={{ flex: 1 }}>
|
||||||
entering={FadeInDown.duration(300).delay(100)}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
>
|
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
|
@ -223,33 +190,30 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
}}>
|
}}>
|
||||||
Choose from {vlcAudioTracks.length} available track{vlcAudioTracks.length !== 1 ? 's' : ''}
|
Choose from {vlcAudioTracks.length} available track{vlcAudioTracks.length !== 1 ? 's' : ''}
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</View>
|
||||||
|
|
||||||
<Animated.View entering={FadeIn.duration(300).delay(200)}>
|
<TouchableOpacity
|
||||||
<TouchableOpacity
|
style={{
|
||||||
style={{
|
width: 44,
|
||||||
width: 44,
|
height: 44,
|
||||||
height: 44,
|
borderRadius: 22,
|
||||||
borderRadius: 22,
|
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
justifyContent: 'center',
|
||||||
justifyContent: 'center',
|
alignItems: 'center',
|
||||||
alignItems: 'center',
|
marginLeft: 16,
|
||||||
marginLeft: 16,
|
borderWidth: 1,
|
||||||
borderWidth: 1,
|
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
}}
|
||||||
}}
|
onPress={handleClose}
|
||||||
onPress={handleClose}
|
activeOpacity={0.7}
|
||||||
activeOpacity={0.7}
|
>
|
||||||
>
|
<MaterialIcons name="close" size={20} color="#fff" />
|
||||||
<MaterialIcons name="close" size={20} color="#fff" />
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
|
||||||
</Animated.View>
|
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{
|
style={{
|
||||||
maxHeight: MODAL_MAX_HEIGHT - 100, // Account for header height
|
maxHeight: MODAL_MAX_HEIGHT - 100,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
|
|
@ -262,11 +226,9 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
bounces={false}
|
bounces={false}
|
||||||
>
|
>
|
||||||
<View style={styles.modernTrackListContainer}>
|
<View style={styles.modernTrackListContainer}>
|
||||||
{vlcAudioTracks.length > 0 ? vlcAudioTracks.map((track, index) => (
|
{vlcAudioTracks.length > 0 ? vlcAudioTracks.map((track) => (
|
||||||
<Animated.View
|
<View
|
||||||
key={track.id}
|
key={track.id}
|
||||||
entering={FadeIn.duration(200).delay(50 + index * 30)}
|
|
||||||
exiting={FadeOut.duration(150)}
|
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|
@ -320,8 +282,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{selectedAudioTrack === track.id && (
|
{selectedAudioTrack === track.id && (
|
||||||
<Animated.View
|
<View
|
||||||
entering={FadeIn.duration(300)}
|
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
@ -343,7 +304,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
}}>
|
}}>
|
||||||
ACTIVE
|
ACTIVE
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
@ -365,7 +326,6 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
color="#6B7280"
|
color="#6B7280"
|
||||||
bgColor="rgba(107, 114, 128, 0.15)"
|
bgColor="rgba(107, 114, 128, 0.15)"
|
||||||
icon="language"
|
icon="language"
|
||||||
delay={50}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -385,20 +345,17 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
? 'rgba(249, 115, 22, 0.3)'
|
? 'rgba(249, 115, 22, 0.3)'
|
||||||
: 'rgba(255, 255, 255, 0.1)',
|
: 'rgba(255, 255, 255, 0.1)',
|
||||||
}}>
|
}}>
|
||||||
{selectedAudioTrack === track.id ? (
|
<MaterialIcons
|
||||||
<Animated.View entering={FadeIn.duration(200)}>
|
name={selectedAudioTrack === track.id ? "check-circle" : "volume-up"}
|
||||||
<MaterialIcons name="check-circle" size={24} color="#F97316" />
|
size={24}
|
||||||
</Animated.View>
|
color={selectedAudioTrack === track.id ? "#F97316" : "rgba(255,255,255,0.6)"}
|
||||||
) : (
|
/>
|
||||||
<MaterialIcons name="volume-up" size={24} color="rgba(255,255,255,0.6)" />
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View>
|
</View>
|
||||||
)) : (
|
)) : (
|
||||||
<Animated.View
|
<View
|
||||||
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,
|
||||||
|
|
@ -429,7 +386,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
}}>
|
}}>
|
||||||
No audio tracks are available for this content.{'\n'}Try a different source or check your connection.
|
No audio tracks are available for this content.{'\n'}Try a different source or check your connection.
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Dimensions } from 'react-native';
|
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 { BlurView } from 'expo-blur';
|
||||||
import Animated, {
|
import Animated, {
|
||||||
FadeIn,
|
FadeIn,
|
||||||
FadeOut,
|
FadeOut,
|
||||||
SlideInDown,
|
|
||||||
SlideOutDown,
|
|
||||||
FadeInDown,
|
|
||||||
FadeInUp,
|
|
||||||
Layout,
|
|
||||||
withSpring,
|
|
||||||
withTiming,
|
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
interpolate,
|
withTiming,
|
||||||
Easing,
|
|
||||||
withDelay,
|
|
||||||
withSequence,
|
|
||||||
runOnJS,
|
runOnJS,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
|
@ -36,7 +26,6 @@ interface SourcesModalProps {
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
|
||||||
// Fixed dimensions for the modal
|
|
||||||
const MODAL_WIDTH = Math.min(width - 32, 520);
|
const MODAL_WIDTH = Math.min(width - 32, 520);
|
||||||
const MODAL_MAX_HEIGHT = height * 0.85;
|
const MODAL_MAX_HEIGHT = height * 0.85;
|
||||||
|
|
||||||
|
|
@ -59,8 +48,7 @@ const QualityIndicator = ({ quality }: { quality: string | null }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<View
|
||||||
entering={FadeIn.duration(200).delay(100)}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `${color}20`,
|
backgroundColor: `${color}20`,
|
||||||
borderColor: `${color}60`,
|
borderColor: `${color}60`,
|
||||||
|
|
@ -87,7 +75,7 @@ const QualityIndicator = ({ quality }: { quality: string | null }) => {
|
||||||
}}>
|
}}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -95,17 +83,14 @@ const StreamMetaBadge = ({
|
||||||
text,
|
text,
|
||||||
color,
|
color,
|
||||||
bgColor,
|
bgColor,
|
||||||
icon,
|
icon
|
||||||
delay = 0
|
|
||||||
}: {
|
}: {
|
||||||
text: string;
|
text: string;
|
||||||
color: string;
|
color: string;
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
delay?: number;
|
|
||||||
}) => (
|
}) => (
|
||||||
<Animated.View
|
<View
|
||||||
entering={FadeIn.duration(200).delay(delay)}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: bgColor,
|
backgroundColor: bgColor,
|
||||||
borderColor: `${color}40`,
|
borderColor: `${color}40`,
|
||||||
|
|
@ -133,7 +118,7 @@ const StreamMetaBadge = ({
|
||||||
}}>
|
}}>
|
||||||
{text}
|
{text}
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const SourcesModal: React.FC<SourcesModalProps> = ({
|
const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
|
|
@ -144,32 +129,33 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
onSelectStream,
|
onSelectStream,
|
||||||
isChangingSource,
|
isChangingSource,
|
||||||
}) => {
|
}) => {
|
||||||
const modalScale = useSharedValue(0.9);
|
|
||||||
const modalOpacity = useSharedValue(0);
|
const modalOpacity = useSharedValue(0);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (showSourcesModal) {
|
if (showSourcesModal) {
|
||||||
modalScale.value = withSpring(1, {
|
modalOpacity.value = withTiming(1, { duration: 200 });
|
||||||
damping: 20,
|
} else {
|
||||||
stiffness: 300,
|
modalOpacity.value = withTiming(0, { duration: 150 });
|
||||||
mass: 0.8,
|
|
||||||
});
|
|
||||||
modalOpacity.value = withTiming(1, {
|
|
||||||
duration: 200,
|
|
||||||
easing: Easing.out(Easing.quad),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
modalOpacity.value = 0;
|
||||||
|
};
|
||||||
}, [showSourcesModal]);
|
}, [showSourcesModal]);
|
||||||
|
|
||||||
const modalStyle = useAnimatedStyle(() => ({
|
const modalStyle = useAnimatedStyle(() => ({
|
||||||
transform: [{ scale: modalScale.value }],
|
|
||||||
opacity: modalOpacity.value,
|
opacity: modalOpacity.value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
modalOpacity.value = withTiming(0, { duration: 150 }, () => {
|
||||||
|
runOnJS(setShowSourcesModal)(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (!showSourcesModal) return null;
|
if (!showSourcesModal) return null;
|
||||||
|
|
||||||
const sortedProviders = Object.entries(availableStreams).sort(([a], [b]) => {
|
const sortedProviders = Object.entries(availableStreams).sort(([a], [b]) => {
|
||||||
// Put HDRezka first
|
|
||||||
if (a === 'hdrezka') return -1;
|
if (a === 'hdrezka') return -1;
|
||||||
if (b === 'hdrezka') return 1;
|
if (b === 'hdrezka') return 1;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -191,16 +177,10 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
return stream.url === currentStreamUrl;
|
return stream.url === currentStreamUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
modalScale.value = withTiming(0.9, { duration: 150 });
|
|
||||||
modalOpacity.value = withTiming(0, { duration: 150 });
|
|
||||||
setTimeout(() => setShowSourcesModal(false), 150);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={FadeIn.duration(250)}
|
entering={FadeIn.duration(200)}
|
||||||
exiting={FadeOut.duration(200)}
|
exiting={FadeOut.duration(150)}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|
@ -214,7 +194,6 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
padding: 16,
|
padding: 16,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Backdrop */}
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
@ -227,7 +206,6 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
activeOpacity={1}
|
activeOpacity={1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal Content */}
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
|
|
@ -245,7 +223,6 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
modalStyle,
|
modalStyle,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{/* Glassmorphism Background */}
|
|
||||||
<BlurView
|
<BlurView
|
||||||
intensity={100}
|
intensity={100}
|
||||||
tint="dark"
|
tint="dark"
|
||||||
|
|
@ -257,12 +234,11 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[
|
colors={[
|
||||||
'rgba(229, 9, 20, 0.95)',
|
'rgba(249, 115, 22, 0.95)',
|
||||||
'rgba(176, 6, 16, 0.95)',
|
'rgba(234, 88, 12, 0.95)',
|
||||||
'rgba(139, 5, 12, 0.9)'
|
'rgba(194, 65, 12, 0.9)'
|
||||||
]}
|
]}
|
||||||
locations={[0, 0.6, 1]}
|
locations={[0, 0.6, 1]}
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -276,10 +252,7 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Animated.View
|
<View style={{ flex: 1 }}>
|
||||||
entering={FadeIn.duration(300).delay(100)}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
>
|
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
|
@ -289,7 +262,7 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
textShadowOffset: { width: 0, height: 1 },
|
textShadowOffset: { width: 0, height: 1 },
|
||||||
textShadowRadius: 2,
|
textShadowRadius: 2,
|
||||||
}}>
|
}}>
|
||||||
Switch Source
|
Video Sources
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: 'rgba(255, 255, 255, 0.85)',
|
color: 'rgba(255, 255, 255, 0.85)',
|
||||||
|
|
@ -298,35 +271,32 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
letterSpacing: 0.2,
|
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
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</View>
|
||||||
|
|
||||||
<Animated.View entering={FadeIn.duration(300).delay(200)}>
|
<TouchableOpacity
|
||||||
<TouchableOpacity
|
style={{
|
||||||
style={{
|
width: 44,
|
||||||
width: 44,
|
height: 44,
|
||||||
height: 44,
|
borderRadius: 22,
|
||||||
borderRadius: 22,
|
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
justifyContent: 'center',
|
||||||
justifyContent: 'center',
|
alignItems: 'center',
|
||||||
alignItems: 'center',
|
marginLeft: 16,
|
||||||
marginLeft: 16,
|
borderWidth: 1,
|
||||||
borderWidth: 1,
|
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
}}
|
||||||
}}
|
onPress={handleClose}
|
||||||
onPress={handleClose}
|
activeOpacity={0.7}
|
||||||
activeOpacity={0.7}
|
>
|
||||||
>
|
<MaterialIcons name="close" size={20} color="#fff" />
|
||||||
<MaterialIcons name="close" size={20} color="#fff" />
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
|
||||||
</Animated.View>
|
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{
|
style={{
|
||||||
maxHeight: MODAL_MAX_HEIGHT - 100, // Account for header height
|
maxHeight: MODAL_MAX_HEIGHT - 100,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
|
|
@ -338,312 +308,169 @@ const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
}}
|
}}
|
||||||
bounces={false}
|
bounces={false}
|
||||||
>
|
>
|
||||||
{sortedProviders.map(([providerId, { streams, addonName }], providerIndex) => (
|
{sortedProviders.map(([providerId, { streams, addonName }]) => (
|
||||||
<Animated.View
|
<View key={providerId} style={{ marginBottom: 24 }}>
|
||||||
key={providerId}
|
|
||||||
entering={FadeIn.duration(200).delay(50 + providerIndex * 30)}
|
|
||||||
exiting={FadeOut.duration(150)}
|
|
||||||
style={{
|
|
||||||
marginBottom: streams.length > 0 ? 32 : 0,
|
|
||||||
width: '100%',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Provider Header */}
|
|
||||||
<View style={{
|
<View style={{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: 20,
|
marginBottom: 12,
|
||||||
paddingBottom: 12,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'rgba(255, 255, 255, 0.08)',
|
|
||||||
width: '100%',
|
|
||||||
}}>
|
}}>
|
||||||
<LinearGradient
|
<Text style={{
|
||||||
colors={providerId === 'hdrezka' ? ['#00d4aa', '#00a085'] : ['#E50914', '#B00610']}
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
style={{
|
fontSize: 14,
|
||||||
width: 12,
|
fontWeight: '600',
|
||||||
height: 12,
|
letterSpacing: 0.3,
|
||||||
borderRadius: 6,
|
textTransform: 'uppercase',
|
||||||
marginRight: 16,
|
}}>
|
||||||
elevation: 3,
|
{addonName}
|
||||||
shadowColor: providerId === 'hdrezka' ? '#00d4aa' : '#E50914',
|
</Text>
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowOpacity: 0.4,
|
|
||||||
shadowRadius: 4,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<Text style={{
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: '700',
|
|
||||||
letterSpacing: -0.3,
|
|
||||||
}}>
|
|
||||||
{addonName}
|
|
||||||
</Text>
|
|
||||||
<Text style={{
|
|
||||||
color: 'rgba(255, 255, 255, 0.6)',
|
|
||||||
fontSize: 12,
|
|
||||||
marginTop: 1,
|
|
||||||
fontWeight: '500',
|
|
||||||
}}>
|
|
||||||
Provider • {streams.length} stream{streams.length !== 1 ? 's' : ''}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={{
|
<View style={{
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 6,
|
paddingVertical: 2,
|
||||||
borderRadius: 16,
|
borderRadius: 12,
|
||||||
borderWidth: 1,
|
marginLeft: 8,
|
||||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
}}>
|
}}>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: 'rgba(255, 255, 255, 0.7)',
|
color: 'rgba(255, 255, 255, 0.5)',
|
||||||
fontSize: 11,
|
fontSize: 12,
|
||||||
fontWeight: '700',
|
fontWeight: '600',
|
||||||
letterSpacing: 0.5,
|
|
||||||
}}>
|
}}>
|
||||||
{streams.length}
|
{streams.length}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Streams Grid */}
|
{streams.map((stream, index) => {
|
||||||
<View style={{ gap: 16, width: '100%' }}>
|
const isSelected = isStreamSelected(stream);
|
||||||
{streams.map((stream, index) => {
|
const quality = getQualityFromTitle(stream.title);
|
||||||
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 (
|
return (
|
||||||
<Animated.View
|
<View
|
||||||
key={`${stream.url}-${index}`}
|
key={`${stream.url}-${index}`}
|
||||||
entering={FadeIn.duration(200).delay(100 + index * 50)}
|
style={{
|
||||||
exiting={FadeOut.duration(150)}
|
marginBottom: 12,
|
||||||
style={{ width: '100%' }}
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={{
|
||||||
|
backgroundColor: isSelected
|
||||||
|
? 'rgba(249, 115, 22, 0.08)'
|
||||||
|
: 'rgba(255, 255, 255, 0.03)',
|
||||||
|
borderRadius: 20,
|
||||||
|
padding: 20,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: isSelected
|
||||||
|
? 'rgba(249, 115, 22, 0.4)'
|
||||||
|
: 'rgba(255, 255, 255, 0.08)',
|
||||||
|
elevation: isSelected ? 8 : 3,
|
||||||
|
shadowColor: isSelected ? '#F97316' : '#000',
|
||||||
|
shadowOffset: { width: 0, height: isSelected ? 4 : 2 },
|
||||||
|
shadowOpacity: isSelected ? 0.3 : 0.1,
|
||||||
|
shadowRadius: isSelected ? 12 : 6,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
onPress={() => handleStreamSelect(stream)}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
disabled={isChangingSource}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<View style={{
|
||||||
style={{
|
flexDirection: 'row',
|
||||||
backgroundColor: isSelected
|
alignItems: 'center',
|
||||||
? 'rgba(229, 9, 20, 0.08)'
|
justifyContent: 'space-between',
|
||||||
: 'rgba(255, 255, 255, 0.03)',
|
width: '100%',
|
||||||
borderRadius: 20,
|
}}>
|
||||||
padding: 20,
|
<View style={{ flex: 1, marginRight: 16 }}>
|
||||||
borderWidth: 2,
|
<View style={{
|
||||||
borderColor: isSelected
|
flexDirection: 'row',
|
||||||
? 'rgba(229, 9, 20, 0.4)'
|
alignItems: 'center',
|
||||||
: 'rgba(255, 255, 255, 0.08)',
|
marginBottom: 8,
|
||||||
elevation: isSelected ? 8 : 3,
|
gap: 12,
|
||||||
shadowColor: isSelected ? '#E50914' : '#000',
|
}}>
|
||||||
shadowOffset: { width: 0, height: isSelected ? 4 : 2 },
|
<Text style={{
|
||||||
shadowOpacity: isSelected ? 0.3 : 0.1,
|
color: isSelected ? '#fff' : 'rgba(255, 255, 255, 0.95)',
|
||||||
shadowRadius: isSelected ? 12 : 6,
|
fontSize: 16,
|
||||||
transform: [{ scale: isSelected ? 1.02 : 1 }],
|
fontWeight: '700',
|
||||||
width: '100%',
|
letterSpacing: -0.2,
|
||||||
}}
|
flex: 1,
|
||||||
onPress={() => handleStreamSelect(stream)}
|
|
||||||
disabled={isChangingSource || isSelected}
|
|
||||||
activeOpacity={0.85}
|
|
||||||
>
|
|
||||||
<View style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: '100%',
|
|
||||||
}}>
|
|
||||||
{/* Stream Info */}
|
|
||||||
<View style={{ flex: 1, marginRight: 16 }}>
|
|
||||||
{/* Title Row */}
|
|
||||||
<View style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
marginBottom: 12,
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: 8,
|
|
||||||
}}>
|
}}>
|
||||||
<Text style={{
|
{stream.title || 'Untitled Stream'}
|
||||||
color: isSelected ? '#fff' : 'rgba(255, 255, 255, 0.95)',
|
</Text>
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '700',
|
|
||||||
letterSpacing: -0.2,
|
|
||||||
flex: 1,
|
|
||||||
lineHeight: 22,
|
|
||||||
}}>
|
|
||||||
{isHDRezka ? `HDRezka ${stream.title}` : (stream.name || stream.title || 'Unnamed Stream')}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<Animated.View
|
<View
|
||||||
entering={FadeIn.duration(300)}
|
style={{
|
||||||
style={{
|
flexDirection: 'row',
|
||||||
flexDirection: 'row',
|
alignItems: 'center',
|
||||||
alignItems: 'center',
|
backgroundColor: 'rgba(249, 115, 22, 0.25)',
|
||||||
backgroundColor: 'rgba(229, 9, 20, 0.25)',
|
paddingHorizontal: 10,
|
||||||
paddingHorizontal: 10,
|
paddingVertical: 5,
|
||||||
paddingVertical: 5,
|
borderRadius: 14,
|
||||||
borderRadius: 14,
|
borderWidth: 1,
|
||||||
borderWidth: 1,
|
borderColor: 'rgba(249, 115, 22, 0.5)',
|
||||||
borderColor: 'rgba(229, 9, 20, 0.5)',
|
}}
|
||||||
elevation: 4,
|
>
|
||||||
shadowColor: '#E50914',
|
<MaterialIcons name="play-arrow" size={12} color="#F97316" />
|
||||||
shadowOffset: { width: 0, height: 2 },
|
<Text style={{
|
||||||
shadowOpacity: 0.3,
|
color: '#F97316',
|
||||||
shadowRadius: 4,
|
fontSize: 10,
|
||||||
}}
|
fontWeight: '800',
|
||||||
>
|
marginLeft: 3,
|
||||||
<MaterialIcons name="play-circle-filled" size={12} color="#E50914" />
|
letterSpacing: 0.3,
|
||||||
<Text style={{
|
}}>
|
||||||
color: '#E50914',
|
PLAYING
|
||||||
fontSize: 10,
|
</Text>
|
||||||
fontWeight: '800',
|
</View>
|
||||||
marginLeft: 3,
|
|
||||||
letterSpacing: 0.3,
|
|
||||||
}}>
|
|
||||||
PLAYING
|
|
||||||
</Text>
|
|
||||||
</Animated.View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isChangingSource && isSelected && (
|
|
||||||
<Animated.View
|
|
||||||
entering={FadeIn.duration(200)}
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'rgba(229, 9, 20, 0.2)',
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
paddingVertical: 4,
|
|
||||||
borderRadius: 12,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ActivityIndicator size="small" color="#E50914" />
|
|
||||||
<Text style={{
|
|
||||||
color: '#E50914',
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: '600',
|
|
||||||
marginLeft: 4,
|
|
||||||
}}>
|
|
||||||
Switching...
|
|
||||||
</Text>
|
|
||||||
</Animated.View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Subtitle */}
|
|
||||||
{!isHDRezka && stream.title && stream.title !== stream.name && (
|
|
||||||
<Text style={{
|
|
||||||
color: 'rgba(255, 255, 255, 0.65)',
|
|
||||||
fontSize: 13,
|
|
||||||
marginBottom: 12,
|
|
||||||
lineHeight: 18,
|
|
||||||
fontWeight: '400',
|
|
||||||
}}>
|
|
||||||
{stream.title}
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Enhanced Meta Info */}
|
|
||||||
<View style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: 6,
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<QualityIndicator quality={quality} />
|
|
||||||
|
|
||||||
{isDolby && (
|
|
||||||
<StreamMetaBadge
|
|
||||||
text="DOLBY"
|
|
||||||
color="#8B5CF6"
|
|
||||||
bgColor="rgba(139, 92, 246, 0.15)"
|
|
||||||
icon="hd"
|
|
||||||
delay={100}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isHDR && (
|
|
||||||
<StreamMetaBadge
|
|
||||||
text="HDR"
|
|
||||||
color="#F59E0B"
|
|
||||||
bgColor="rgba(245, 158, 11, 0.15)"
|
|
||||||
icon="brightness-high"
|
|
||||||
delay={120}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{size && (
|
|
||||||
<StreamMetaBadge
|
|
||||||
text={size}
|
|
||||||
color="#6B7280"
|
|
||||||
bgColor="rgba(107, 114, 128, 0.15)"
|
|
||||||
icon="storage"
|
|
||||||
delay={140}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDebrid && (
|
|
||||||
<StreamMetaBadge
|
|
||||||
text="DEBRID"
|
|
||||||
color="#00d4aa"
|
|
||||||
bgColor="rgba(0, 212, 170, 0.15)"
|
|
||||||
icon="flash-on"
|
|
||||||
delay={160}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isHDRezka && (
|
|
||||||
<StreamMetaBadge
|
|
||||||
text="HDREZKA"
|
|
||||||
color="#00d4aa"
|
|
||||||
bgColor="rgba(0, 212, 170, 0.15)"
|
|
||||||
icon="verified"
|
|
||||||
delay={180}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Enhanced Action Icon */}
|
|
||||||
<View style={{
|
<View style={{
|
||||||
width: 48,
|
flexDirection: 'row',
|
||||||
height: 48,
|
flexWrap: 'wrap',
|
||||||
borderRadius: 24,
|
gap: 6,
|
||||||
backgroundColor: isSelected
|
|
||||||
? 'rgba(229, 9, 20, 0.15)'
|
|
||||||
: 'rgba(255, 255, 255, 0.05)',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: isSelected
|
|
||||||
? 'rgba(229, 9, 20, 0.3)'
|
|
||||||
: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
elevation: 4,
|
|
||||||
shadowColor: isSelected ? '#E50914' : '#fff',
|
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowOpacity: isSelected ? 0.2 : 0.05,
|
|
||||||
shadowRadius: 4,
|
|
||||||
}}>
|
}}>
|
||||||
{isSelected ? (
|
{quality && <QualityIndicator quality={quality} />}
|
||||||
<Animated.View entering={FadeIn.duration(200)}>
|
<StreamMetaBadge
|
||||||
<MaterialIcons name="check-circle" size={24} color="#E50914" />
|
text={providerId.toUpperCase()}
|
||||||
</Animated.View>
|
color="#6B7280"
|
||||||
) : (
|
bgColor="rgba(107, 114, 128, 0.15)"
|
||||||
<MaterialIcons name="play-arrow" size={24} color="rgba(255,255,255,0.6)" />
|
icon="source"
|
||||||
)}
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
|
||||||
</Animated.View>
|
<View style={{
|
||||||
);
|
width: 48,
|
||||||
})}
|
height: 48,
|
||||||
</View>
|
borderRadius: 24,
|
||||||
</Animated.View>
|
backgroundColor: isSelected
|
||||||
|
? 'rgba(249, 115, 22, 0.15)'
|
||||||
|
: 'rgba(255, 255, 255, 0.05)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: isSelected
|
||||||
|
? 'rgba(249, 115, 22, 0.3)'
|
||||||
|
: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
}}>
|
||||||
|
{isChangingSource ? (
|
||||||
|
<ActivityIndicator size="small" color="#F97316" />
|
||||||
|
) : (
|
||||||
|
<MaterialIcons
|
||||||
|
name={isSelected ? "check-circle" : "play-circle-outline"}
|
||||||
|
size={24}
|
||||||
|
color={isSelected ? "#F97316" : "rgba(255,255,255,0.6)"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</BlurView>
|
</BlurView>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue