mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
improved parental guide UI
This commit is contained in:
parent
eee6f81fca
commit
7e7804b6d4
5 changed files with 17 additions and 531 deletions
|
|
@ -1,228 +0,0 @@
|
|||
import React, { useState, useRef } from 'react';
|
||||
import { View, Text, TouchableOpacity, ScrollView, Animated, StyleSheet } from 'react-native';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
interface PauseOverlayProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
episodeTitle?: string;
|
||||
season?: number;
|
||||
episode?: number;
|
||||
year?: string | number;
|
||||
type: string;
|
||||
description: string;
|
||||
cast: any[];
|
||||
screenDimensions: { width: number, height: number };
|
||||
}
|
||||
|
||||
export const PauseOverlay: React.FC<PauseOverlayProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
title,
|
||||
episodeTitle,
|
||||
season,
|
||||
episode,
|
||||
year,
|
||||
type,
|
||||
description,
|
||||
cast,
|
||||
screenDimensions
|
||||
}) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
// Internal Animation State
|
||||
const pauseOverlayOpacity = useRef(new Animated.Value(visible ? 1 : 0)).current;
|
||||
const pauseOverlayTranslateY = useRef(new Animated.Value(12)).current;
|
||||
const metadataOpacity = useRef(new Animated.Value(1)).current;
|
||||
const metadataScale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
// Cast Details State
|
||||
const [selectedCastMember, setSelectedCastMember] = useState<any>(null);
|
||||
const [showCastDetails, setShowCastDetails] = useState(false);
|
||||
const castDetailsOpacity = useRef(new Animated.Value(0)).current;
|
||||
const castDetailsScale = useRef(new Animated.Value(0.95)).current;
|
||||
|
||||
React.useEffect(() => {
|
||||
Animated.timing(pauseOverlayOpacity, {
|
||||
toValue: visible ? 1 : 0,
|
||||
duration: 250,
|
||||
useNativeDriver: true
|
||||
}).start();
|
||||
}, [visible]);
|
||||
|
||||
if (!visible && !showCastDetails) return null;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={onClose}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 30,
|
||||
}}
|
||||
>
|
||||
<Animated.View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
opacity: pauseOverlayOpacity,
|
||||
}}
|
||||
>
|
||||
{/* Horizontal Fade */}
|
||||
<View style={{ position: 'absolute', top: 0, left: 0, bottom: 0, width: screenDimensions.width * 0.7 }}>
|
||||
<LinearGradient
|
||||
start={{ x: 0, y: 0.5 }}
|
||||
end={{ x: 1, y: 0.5 }}
|
||||
colors={['rgba(0,0,0,0.85)', 'rgba(0,0,0,0.0)']}
|
||||
locations={[0, 1]}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
</View>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
'rgba(0,0,0,0.6)',
|
||||
'rgba(0,0,0,0.4)',
|
||||
'rgba(0,0,0,0.2)',
|
||||
'rgba(0,0,0,0.0)'
|
||||
]}
|
||||
locations={[0, 0.3, 0.6, 1]}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
|
||||
<Animated.View style={{
|
||||
position: 'absolute',
|
||||
left: 24 + insets.left,
|
||||
right: 24 + insets.right,
|
||||
top: 24 + insets.top,
|
||||
bottom: 110 + insets.bottom,
|
||||
transform: [{ translateY: pauseOverlayTranslateY }]
|
||||
}}>
|
||||
{showCastDetails && selectedCastMember ? (
|
||||
<Animated.View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
opacity: castDetailsOpacity,
|
||||
transform: [{ scale: castDetailsScale }]
|
||||
}}
|
||||
>
|
||||
<View style={{ alignItems: 'flex-start', paddingBottom: screenDimensions.height * 0.1 }}>
|
||||
<TouchableOpacity
|
||||
style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 24, paddingVertical: 8, paddingHorizontal: 4 }}
|
||||
onPress={() => {
|
||||
Animated.parallel([
|
||||
Animated.timing(castDetailsOpacity, { toValue: 0, duration: 250, useNativeDriver: true }),
|
||||
Animated.timing(castDetailsScale, { toValue: 0.95, duration: 250, useNativeDriver: true })
|
||||
]).start(() => {
|
||||
setShowCastDetails(false);
|
||||
setSelectedCastMember(null);
|
||||
Animated.parallel([
|
||||
Animated.timing(metadataOpacity, { toValue: 1, duration: 400, useNativeDriver: true }),
|
||||
Animated.spring(metadataScale, { toValue: 1, tension: 80, friction: 8, useNativeDriver: true })
|
||||
]).start();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="arrow-back" size={20} color="#FFFFFF" style={{ marginRight: 8 }} />
|
||||
<Text style={{ color: '#B8B8B8', fontSize: Math.min(14, screenDimensions.width * 0.02) }}>Back to details</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ flexDirection: 'row', alignItems: 'flex-start', width: '100%' }}>
|
||||
{selectedCastMember.profile_path && (
|
||||
<View style={{ marginRight: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 5 }}>
|
||||
<FastImage
|
||||
source={{ uri: `https://image.tmdb.org/t/p/w300${selectedCastMember.profile_path}` }}
|
||||
style={{ width: Math.min(120, screenDimensions.width * 0.18), height: Math.min(180, screenDimensions.width * 0.27), borderRadius: 12, backgroundColor: 'rgba(255,255,255,0.1)' }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={{ flex: 1, paddingTop: 8 }}>
|
||||
<Text style={{ color: '#FFFFFF', fontSize: Math.min(32, screenDimensions.width * 0.045), fontWeight: '800', marginBottom: 8 }} numberOfLines={2}>
|
||||
{selectedCastMember.name}
|
||||
</Text>
|
||||
{selectedCastMember.character && (
|
||||
<Text style={{ color: '#CCCCCC', fontSize: Math.min(16, screenDimensions.width * 0.022), marginBottom: 8, fontWeight: '500', fontStyle: 'italic' }} numberOfLines={2}>
|
||||
as {selectedCastMember.character}
|
||||
</Text>
|
||||
)}
|
||||
{selectedCastMember.biography && (
|
||||
<Text style={{ color: '#D6D6D6', fontSize: Math.min(14, screenDimensions.width * 0.019), lineHeight: Math.min(20, screenDimensions.width * 0.026), marginTop: 16, opacity: 0.9 }} numberOfLines={4}>
|
||||
{selectedCastMember.biography}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<Animated.View style={{ flex: 1, justifyContent: 'space-between', opacity: metadataOpacity, transform: [{ scale: metadataScale }] }}>
|
||||
<View>
|
||||
<Text style={{ color: '#B8B8B8', fontSize: Math.min(18, screenDimensions.width * 0.025), marginBottom: 8 }}>You're watching</Text>
|
||||
<Text style={{ color: '#FFFFFF', fontSize: Math.min(48, screenDimensions.width * 0.06), fontWeight: '800', marginBottom: 10 }} numberOfLines={2}>
|
||||
{title}
|
||||
</Text>
|
||||
{!!year && (
|
||||
<Text style={{ color: '#CCCCCC', fontSize: Math.min(18, screenDimensions.width * 0.025), marginBottom: 8 }} numberOfLines={1}>
|
||||
{`${year}${type === 'series' && season && episode ? ` • S${season}E${episode}` : ''}`}
|
||||
</Text>
|
||||
)}
|
||||
{!!episodeTitle && (
|
||||
<Text style={{ color: '#FFFFFF', fontSize: Math.min(20, screenDimensions.width * 0.03), fontWeight: '600', marginBottom: 8 }} numberOfLines={2}>
|
||||
{episodeTitle}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text style={{ color: '#D6D6D6', fontSize: Math.min(18, screenDimensions.width * 0.025), lineHeight: Math.min(24, screenDimensions.width * 0.03) }} numberOfLines={3}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
{cast && cast.length > 0 && (
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<Text style={{ color: '#B8B8B8', fontSize: Math.min(16, screenDimensions.width * 0.022), marginBottom: 8 }}>Cast</Text>
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
|
||||
{cast.slice(0, 6).map((castMember: any, index: number) => (
|
||||
<TouchableOpacity
|
||||
key={castMember.id || index}
|
||||
style={{ backgroundColor: 'rgba(255,255,255,0.1)', borderRadius: 12, paddingHorizontal: Math.min(12, screenDimensions.width * 0.015), paddingVertical: Math.min(6, screenDimensions.height * 0.008), marginRight: 8, marginBottom: 8 }}
|
||||
onPress={() => {
|
||||
setSelectedCastMember(castMember);
|
||||
Animated.parallel([
|
||||
Animated.timing(metadataOpacity, { toValue: 0, duration: 250, useNativeDriver: true }),
|
||||
Animated.timing(metadataScale, { toValue: 0.95, duration: 250, useNativeDriver: true })
|
||||
]).start(() => {
|
||||
setShowCastDetails(true);
|
||||
Animated.parallel([
|
||||
Animated.timing(castDetailsOpacity, { toValue: 1, duration: 400, useNativeDriver: true }),
|
||||
Animated.spring(castDetailsScale, { toValue: 1, tension: 80, friction: 8, useNativeDriver: true })
|
||||
]).start();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: '#FFFFFF', fontSize: Math.min(14, screenDimensions.width * 0.018) }}>
|
||||
{castMember.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import React from 'react';
|
||||
import { View, Text, Animated, StyleSheet } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { styles } from '../../utils/playerStyles';
|
||||
|
||||
interface SpeedActivatedOverlayProps {
|
||||
visible: boolean;
|
||||
opacity: Animated.Value;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
export const SpeedActivatedOverlay: React.FC<SpeedActivatedOverlayProps> = ({
|
||||
visible,
|
||||
opacity,
|
||||
speed
|
||||
}) => {
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.speedActivatedOverlay,
|
||||
{ opacity: opacity }
|
||||
]}
|
||||
>
|
||||
<View style={styles.speedActivatedContainer}>
|
||||
<MaterialIcons name="fast-forward" size={32} color="#FFFFFF" />
|
||||
<Text style={styles.speedActivatedText}>{speed}x Speed</Text>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,228 +0,0 @@
|
|||
import React, { useState, useRef } from 'react';
|
||||
import { View, Text, TouchableOpacity, Animated, StyleSheet } from 'react-native';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
interface PauseOverlayProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
episodeTitle?: string;
|
||||
season?: number;
|
||||
episode?: number;
|
||||
year?: string | number;
|
||||
type: string;
|
||||
description: string;
|
||||
cast: any[];
|
||||
screenDimensions: { width: number, height: number };
|
||||
}
|
||||
|
||||
export const PauseOverlay: React.FC<PauseOverlayProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
title,
|
||||
episodeTitle,
|
||||
season,
|
||||
episode,
|
||||
year,
|
||||
type,
|
||||
description,
|
||||
cast,
|
||||
screenDimensions
|
||||
}) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
// Internal Animation State
|
||||
const pauseOverlayOpacity = useRef(new Animated.Value(visible ? 1 : 0)).current;
|
||||
const pauseOverlayTranslateY = useRef(new Animated.Value(12)).current;
|
||||
const metadataOpacity = useRef(new Animated.Value(1)).current;
|
||||
const metadataScale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
// Cast Details State
|
||||
const [selectedCastMember, setSelectedCastMember] = useState<any>(null);
|
||||
const [showCastDetails, setShowCastDetails] = useState(false);
|
||||
const castDetailsOpacity = useRef(new Animated.Value(0)).current;
|
||||
const castDetailsScale = useRef(new Animated.Value(0.95)).current;
|
||||
|
||||
React.useEffect(() => {
|
||||
Animated.timing(pauseOverlayOpacity, {
|
||||
toValue: visible ? 1 : 0,
|
||||
duration: 250,
|
||||
useNativeDriver: true
|
||||
}).start();
|
||||
}, [visible]);
|
||||
|
||||
if (!visible && !showCastDetails) return null;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={onClose}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 30,
|
||||
}}
|
||||
>
|
||||
<Animated.View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
opacity: pauseOverlayOpacity,
|
||||
}}
|
||||
>
|
||||
{/* Horizontal Fade */}
|
||||
<View style={{ position: 'absolute', top: 0, left: 0, bottom: 0, width: screenDimensions.width * 0.7 }}>
|
||||
<LinearGradient
|
||||
start={{ x: 0, y: 0.5 }}
|
||||
end={{ x: 1, y: 0.5 }}
|
||||
colors={['rgba(0,0,0,0.85)', 'rgba(0,0,0,0.0)']}
|
||||
locations={[0, 1]}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
</View>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
'rgba(0,0,0,0.6)',
|
||||
'rgba(0,0,0,0.4)',
|
||||
'rgba(0,0,0,0.2)',
|
||||
'rgba(0,0,0,0.0)'
|
||||
]}
|
||||
locations={[0, 0.3, 0.6, 1]}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
|
||||
<Animated.View style={{
|
||||
position: 'absolute',
|
||||
left: 24 + insets.left,
|
||||
right: 24 + insets.right,
|
||||
top: 24 + insets.top,
|
||||
bottom: 110 + insets.bottom,
|
||||
transform: [{ translateY: pauseOverlayTranslateY }]
|
||||
}}>
|
||||
{showCastDetails && selectedCastMember ? (
|
||||
<Animated.View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
opacity: castDetailsOpacity,
|
||||
transform: [{ scale: castDetailsScale }]
|
||||
}}
|
||||
>
|
||||
<View style={{ alignItems: 'flex-start', paddingBottom: screenDimensions.height * 0.1 }}>
|
||||
<TouchableOpacity
|
||||
style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 24, paddingVertical: 8, paddingHorizontal: 4 }}
|
||||
onPress={() => {
|
||||
Animated.parallel([
|
||||
Animated.timing(castDetailsOpacity, { toValue: 0, duration: 250, useNativeDriver: true }),
|
||||
Animated.timing(castDetailsScale, { toValue: 0.95, duration: 250, useNativeDriver: true })
|
||||
]).start(() => {
|
||||
setShowCastDetails(false);
|
||||
setSelectedCastMember(null);
|
||||
Animated.parallel([
|
||||
Animated.timing(metadataOpacity, { toValue: 1, duration: 400, useNativeDriver: true }),
|
||||
Animated.spring(metadataScale, { toValue: 1, tension: 80, friction: 8, useNativeDriver: true })
|
||||
]).start();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="arrow-back" size={20} color="#FFFFFF" style={{ marginRight: 8 }} />
|
||||
<Text style={{ color: '#B8B8B8', fontSize: Math.min(14, screenDimensions.width * 0.02) }}>Back to details</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ flexDirection: 'row', alignItems: 'flex-start', width: '100%' }}>
|
||||
{selectedCastMember.profile_path && (
|
||||
<View style={{ marginRight: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 5 }}>
|
||||
<FastImage
|
||||
source={{ uri: `https://image.tmdb.org/t/p/w300${selectedCastMember.profile_path}` }}
|
||||
style={{ width: Math.min(120, screenDimensions.width * 0.18), height: Math.min(180, screenDimensions.width * 0.27), borderRadius: 12, backgroundColor: 'rgba(255,255,255,0.1)' }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={{ flex: 1, paddingTop: 8 }}>
|
||||
<Text style={{ color: '#FFFFFF', fontSize: Math.min(32, screenDimensions.width * 0.045), fontWeight: '800', marginBottom: 8 }} numberOfLines={2}>
|
||||
{selectedCastMember.name}
|
||||
</Text>
|
||||
{selectedCastMember.character && (
|
||||
<Text style={{ color: '#CCCCCC', fontSize: Math.min(16, screenDimensions.width * 0.022), marginBottom: 8, fontWeight: '500', fontStyle: 'italic' }} numberOfLines={2}>
|
||||
as {selectedCastMember.character}
|
||||
</Text>
|
||||
)}
|
||||
{selectedCastMember.biography && (
|
||||
<Text style={{ color: '#D6D6D6', fontSize: Math.min(14, screenDimensions.width * 0.019), lineHeight: Math.min(20, screenDimensions.width * 0.026), marginTop: 16, opacity: 0.9 }} numberOfLines={4}>
|
||||
{selectedCastMember.biography}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<Animated.View style={{ flex: 1, justifyContent: 'space-between', opacity: metadataOpacity, transform: [{ scale: metadataScale }] }}>
|
||||
<View>
|
||||
<Text style={{ color: '#B8B8B8', fontSize: Math.min(18, screenDimensions.width * 0.025), marginBottom: 8 }}>You're watching</Text>
|
||||
<Text style={{ color: '#FFFFFF', fontSize: Math.min(48, screenDimensions.width * 0.06), fontWeight: '800', marginBottom: 10 }} numberOfLines={2}>
|
||||
{title}
|
||||
</Text>
|
||||
{!!year && (
|
||||
<Text style={{ color: '#CCCCCC', fontSize: Math.min(18, screenDimensions.width * 0.025), marginBottom: 8 }} numberOfLines={1}>
|
||||
{`${year}${type === 'series' && season && episode ? ` • S${season}E${episode}` : ''}`}
|
||||
</Text>
|
||||
)}
|
||||
{!!episodeTitle && (
|
||||
<Text style={{ color: '#FFFFFF', fontSize: Math.min(20, screenDimensions.width * 0.03), fontWeight: '600', marginBottom: 8 }} numberOfLines={2}>
|
||||
{episodeTitle}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text style={{ color: '#D6D6D6', fontSize: Math.min(18, screenDimensions.width * 0.025), lineHeight: Math.min(24, screenDimensions.width * 0.03) }} numberOfLines={3}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
{cast && cast.length > 0 && (
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<Text style={{ color: '#B8B8B8', fontSize: Math.min(16, screenDimensions.width * 0.022), marginBottom: 8 }}>Cast</Text>
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
|
||||
{cast.slice(0, 6).map((castMember: any, index: number) => (
|
||||
<TouchableOpacity
|
||||
key={castMember.id || index}
|
||||
style={{ backgroundColor: 'rgba(255,255,255,0.1)', borderRadius: 12, paddingHorizontal: Math.min(12, screenDimensions.width * 0.015), paddingVertical: Math.min(6, screenDimensions.height * 0.008), marginRight: 8, marginBottom: 8 }}
|
||||
onPress={() => {
|
||||
setSelectedCastMember(castMember);
|
||||
Animated.parallel([
|
||||
Animated.timing(metadataOpacity, { toValue: 0, duration: 250, useNativeDriver: true }),
|
||||
Animated.timing(metadataScale, { toValue: 0.95, duration: 250, useNativeDriver: true })
|
||||
]).start(() => {
|
||||
setShowCastDetails(true);
|
||||
Animated.parallel([
|
||||
Animated.timing(castDetailsOpacity, { toValue: 1, duration: 400, useNativeDriver: true }),
|
||||
Animated.spring(castDetailsScale, { toValue: 1, tension: 80, friction: 8, useNativeDriver: true })
|
||||
]).start();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: '#FFFFFF', fontSize: Math.min(14, screenDimensions.width * 0.018) }}>
|
||||
{castMember.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import React from 'react';
|
||||
import { View, Text, Animated } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { styles } from '../../utils/playerStyles';
|
||||
|
||||
interface SpeedActivatedOverlayProps {
|
||||
visible: boolean;
|
||||
opacity: Animated.Value;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
export const SpeedActivatedOverlay: React.FC<SpeedActivatedOverlayProps> = ({
|
||||
visible,
|
||||
opacity,
|
||||
speed
|
||||
}) => {
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.speedActivatedOverlay,
|
||||
{ opacity: opacity }
|
||||
]}
|
||||
>
|
||||
<View style={styles.speedActivatedContainer}>
|
||||
<MaterialIcons name="fast-forward" size={32} color="#FFFFFF" />
|
||||
<Text style={styles.speedActivatedText}>{speed}x Speed</Text>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { View, Text, StyleSheet, Dimensions } from 'react-native';
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
|
|
@ -10,6 +10,7 @@ import Animated, {
|
|||
} from 'react-native-reanimated';
|
||||
import { parentalGuideService } from '../../../services/parentalGuideService';
|
||||
import { logger } from '../../../utils/logger';
|
||||
import { useTheme } from '../../../contexts/ThemeContext';
|
||||
|
||||
interface ParentalGuideOverlayProps {
|
||||
imdbId: string | undefined;
|
||||
|
|
@ -42,16 +43,17 @@ const ROW_HEIGHT = 18;
|
|||
const WarningItemView: React.FC<{
|
||||
item: WarningItem;
|
||||
opacity: SharedValue<number>;
|
||||
}> = ({ item, opacity }) => {
|
||||
fontSize: number;
|
||||
}> = ({ item, opacity, fontSize }) => {
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: opacity.value,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.warningItem, animatedStyle]}>
|
||||
<Text style={styles.label}>{item.label}</Text>
|
||||
<Text style={styles.separator}>·</Text>
|
||||
<Text style={styles.severity}>{item.severity}</Text>
|
||||
<Text style={[styles.label, { fontSize }]}>{item.label}</Text>
|
||||
<Text style={[styles.separator, { fontSize }]}>·</Text>
|
||||
<Text style={[styles.severity, { fontSize }]}>{item.severity}</Text>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
|
@ -63,6 +65,8 @@ export const ParentalGuideOverlay: React.FC<ParentalGuideOverlayProps> = ({
|
|||
episode,
|
||||
shouldShow,
|
||||
}) => {
|
||||
const { currentTheme } = useTheme();
|
||||
const screenWidth = Dimensions.get('window').width;
|
||||
const [warnings, setWarnings] = useState<WarningItem[]>([]);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const hasShownRef = useRef(false);
|
||||
|
|
@ -222,10 +226,15 @@ export const ParentalGuideOverlay: React.FC<ParentalGuideOverlayProps> = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
// Responsive sizing
|
||||
const fontSize = Math.min(11, screenWidth * 0.014);
|
||||
const lineWidth = Math.min(3, screenWidth * 0.0038);
|
||||
const containerPadding = Math.min(20, screenWidth * 0.025);
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.container, containerStyle]} pointerEvents="none">
|
||||
<Animated.View style={[styles.container, { left: containerPadding, top: containerPadding + 30 }]} pointerEvents="none">
|
||||
{/* Vertical line - animates height */}
|
||||
<Animated.View style={[styles.line, lineStyle]} />
|
||||
<Animated.View style={[styles.line, lineStyle, { backgroundColor: currentTheme.colors.primary, width: lineWidth }]} />
|
||||
|
||||
{/* Warning items */}
|
||||
<View style={styles.itemsContainer}>
|
||||
|
|
@ -234,6 +243,7 @@ export const ParentalGuideOverlay: React.FC<ParentalGuideOverlayProps> = ({
|
|||
key={item.label}
|
||||
item={item}
|
||||
opacity={itemOpacities[index]}
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
|
@ -244,15 +254,11 @@ export const ParentalGuideOverlay: React.FC<ParentalGuideOverlayProps> = ({
|
|||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
top: 50,
|
||||
left: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
zIndex: 100,
|
||||
},
|
||||
line: {
|
||||
width: 2,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.5)',
|
||||
borderRadius: 1,
|
||||
marginRight: 10,
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue