mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-03 16:59:08 +00:00
consistent theming across player modals
This commit is contained in:
parent
a8dfe30546
commit
79b0cfc990
6 changed files with 545 additions and 790 deletions
|
|
@ -1,14 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity, ScrollView, useWindowDimensions, StyleSheet, Platform } from 'react-native';
|
import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Platform, useWindowDimensions } from 'react-native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import Animated, {
|
import Animated, {
|
||||||
FadeIn,
|
FadeIn,
|
||||||
FadeOut,
|
FadeOut,
|
||||||
SlideInDown,
|
SlideInRight,
|
||||||
SlideOutDown,
|
SlideOutRight,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { getTrackDisplayName, DEBUG_MODE } from '../utils/playerUtils';
|
import { getTrackDisplayName } from '../utils/playerUtils';
|
||||||
import { logger } from '../../../utils/logger';
|
|
||||||
|
|
||||||
interface AudioTrackModalProps {
|
interface AudioTrackModalProps {
|
||||||
showAudioModal: boolean;
|
showAudioModal: boolean;
|
||||||
|
|
@ -25,53 +24,42 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
selectedAudioTrack,
|
selectedAudioTrack,
|
||||||
selectAudioTrack,
|
selectAudioTrack,
|
||||||
}) => {
|
}) => {
|
||||||
const { width, height } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
|
const MENU_WIDTH = Math.min(width * 0.85, 400);
|
||||||
const menuWidth = Math.min(width * 0.9, 420);
|
|
||||||
const menuMaxHeight = height * 0.9;
|
|
||||||
|
|
||||||
const handleClose = () => setShowAudioModal(false);
|
const handleClose = () => setShowAudioModal(false);
|
||||||
|
|
||||||
if (!showAudioModal) return null;
|
if (!showAudioModal) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={StyleSheet.absoluteFill} zIndex={9999}>
|
<View style={[StyleSheet.absoluteFill, { zIndex: 9999 }]}>
|
||||||
{/* Backdrop matching SubtitleModal */}
|
<TouchableOpacity style={StyleSheet.absoluteFill} activeOpacity={1} onPress={handleClose}>
|
||||||
<TouchableOpacity
|
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)} style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)' }} />
|
||||||
style={StyleSheet.absoluteFill}
|
|
||||||
activeOpacity={1}
|
|
||||||
onPress={handleClose}
|
|
||||||
>
|
|
||||||
<Animated.View
|
|
||||||
entering={FadeIn.duration(200)}
|
|
||||||
exiting={FadeOut.duration(150)}
|
|
||||||
style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.4)' }}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{/* Center Alignment Container */}
|
|
||||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }} pointerEvents="box-none">
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={SlideInDown.duration(300)}
|
entering={SlideInRight.duration(300)}
|
||||||
exiting={SlideOutDown.duration(250)}
|
exiting={SlideOutRight.duration(250)}
|
||||||
style={{
|
style={{
|
||||||
width: menuWidth,
|
position: 'absolute',
|
||||||
maxHeight: menuMaxHeight,
|
top: 0,
|
||||||
backgroundColor: 'rgba(15, 15, 15, 0.98)', // Matches SubtitleModal
|
right: 0,
|
||||||
borderRadius: 24,
|
bottom: 0,
|
||||||
borderWidth: 1,
|
width: MENU_WIDTH,
|
||||||
|
backgroundColor: '#0f0f0f',
|
||||||
|
borderLeftWidth: 1,
|
||||||
borderColor: 'rgba(255,255,255,0.1)',
|
borderColor: 'rgba(255,255,255,0.1)',
|
||||||
overflow: 'hidden'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header with shared aesthetics */}
|
<View style={{ paddingTop: Platform.OS === 'ios' ? 60 : 15, paddingHorizontal: 20 }}>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', padding: 20, position: 'relative' }}>
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||||||
<Text style={{ color: 'white', fontSize: 18, fontWeight: '700' }}>Audio Tracks</Text>
|
<Text style={{ color: 'white', fontSize: 22, fontWeight: '700' }}>Audio Tracks</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={{ paddingHorizontal: 20, paddingBottom: 20 }}
|
contentContainerStyle={{ padding: 15, paddingBottom: 40 }}
|
||||||
>
|
>
|
||||||
<View style={{ gap: 8 }}>
|
<View style={{ gap: 8 }}>
|
||||||
{ksAudioTracks.map((track) => {
|
{ksAudioTracks.map((track) => {
|
||||||
|
|
@ -85,9 +73,12 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
setTimeout(handleClose, 200);
|
setTimeout(handleClose, 200);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
padding: 10,
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
backgroundColor: isSelected ? 'white' : 'rgba(255,255,255,0.05)', // Matches SubtitleModal item colors
|
backgroundColor: isSelected ? 'white' : 'rgba(255,255,255,0.06)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: isSelected ? 'white' : 'rgba(255,255,255,0.1)',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
|
|
@ -96,7 +87,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: isSelected ? 'black' : 'white',
|
color: isSelected ? 'black' : 'white',
|
||||||
fontWeight: isSelected ? '700' : '400',
|
fontWeight: isSelected ? '700' : '500',
|
||||||
fontSize: 15
|
fontSize: 15
|
||||||
}}>
|
}}>
|
||||||
{getTrackDisplayName(track)}
|
{getTrackDisplayName(track)}
|
||||||
|
|
@ -117,6 +108,5 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Dimensions } from 'react-native';
|
import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, StyleSheet, Platform, useWindowDimensions } from 'react-native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import Animated, {
|
import Animated, {
|
||||||
FadeIn,
|
FadeIn,
|
||||||
|
|
@ -20,9 +20,6 @@ interface EpisodeStreamsModalProps {
|
||||||
metadata?: { id?: string; name?: string };
|
metadata?: { id?: string; name?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
|
||||||
const MENU_WIDTH = Math.min(width * 0.85, 400);
|
|
||||||
|
|
||||||
const QualityBadge = ({ quality }: { quality: string | null }) => {
|
const QualityBadge = ({ quality }: { quality: string | null }) => {
|
||||||
if (!quality) return null;
|
if (!quality) return null;
|
||||||
|
|
||||||
|
|
@ -73,6 +70,9 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
onSelectStream,
|
onSelectStream,
|
||||||
metadata,
|
metadata,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { width } = useWindowDimensions();
|
||||||
|
const MENU_WIDTH = Math.min(width * 0.85, 400);
|
||||||
|
|
||||||
const [availableStreams, setAvailableStreams] = useState<{ [providerId: string]: { streams: Stream[]; addonName: string } }>({});
|
const [availableStreams, setAvailableStreams] = useState<{ [providerId: string]: { streams: Stream[]; addonName: string } }>({});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [hasErrors, setHasErrors] = useState<string[]>([]);
|
const [hasErrors, setHasErrors] = useState<string[]>([]);
|
||||||
|
|
@ -117,7 +117,6 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
logger.warn(`[EpisodeStreamsModal] Error from ${addonName || addonId}:`, error);
|
logger.warn(`[EpisodeStreamsModal] Error from ${addonName || addonId}:`, error);
|
||||||
setHasErrors(prev => [...prev, `${addonName || addonId}: ${error.message || 'Unknown error'}`]);
|
setHasErrors(prev => [...prev, `${addonName || addonId}: ${error.message || 'Unknown error'}`]);
|
||||||
} else if (streams && streams.length > 0) {
|
} else if (streams && streams.length > 0) {
|
||||||
// Update state incrementally for each provider
|
|
||||||
setAvailableStreams(prev => ({
|
setAvailableStreams(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[addonId]: {
|
[addonId]: {
|
||||||
|
|
@ -158,38 +157,16 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!visible) return null;
|
if (!visible) return null;
|
||||||
|
|
||||||
const sortedProviders = Object.entries(availableStreams);
|
const sortedProviders = Object.entries(availableStreams);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View style={[StyleSheet.absoluteFill, { zIndex: 9999 }]}>
|
||||||
{/* Backdrop */}
|
<TouchableOpacity style={StyleSheet.absoluteFill} activeOpacity={1} onPress={onClose}>
|
||||||
<Animated.View
|
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)} style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)' }} />
|
||||||
entering={FadeIn.duration(200)}
|
</TouchableOpacity>
|
||||||
exiting={FadeOut.duration(150)}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
||||||
zIndex: 9998,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
onPress={handleClose}
|
|
||||||
activeOpacity={1}
|
|
||||||
/>
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
{/* Side Menu */}
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={SlideInRight.duration(300)}
|
entering={SlideInRight.duration(300)}
|
||||||
exiting={SlideOutRight.duration(250)}
|
exiting={SlideOutRight.duration(250)}
|
||||||
|
|
@ -199,66 +176,29 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
width: MENU_WIDTH,
|
width: MENU_WIDTH,
|
||||||
backgroundColor: '#1A1A1A',
|
backgroundColor: '#0f0f0f',
|
||||||
zIndex: 9999,
|
borderLeftWidth: 1,
|
||||||
elevation: 20,
|
borderColor: 'rgba(255,255,255,0.1)',
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOffset: { width: -5, height: 0 },
|
|
||||||
shadowOpacity: 0.3,
|
|
||||||
shadowRadius: 10,
|
|
||||||
borderTopLeftRadius: 20,
|
|
||||||
borderBottomLeftRadius: 20,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
<View style={{ paddingTop: Platform.OS === 'ios' ? 60 : 15, paddingHorizontal: 20 }}>
|
||||||
<View style={{
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingTop: 60,
|
|
||||||
paddingBottom: 20,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'rgba(255, 255, 255, 0.08)',
|
|
||||||
}}>
|
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Text style={{
|
<Text style={{ color: 'white', fontSize: 22, fontWeight: '700' }} numberOfLines={1}>
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: '700',
|
|
||||||
}}>
|
|
||||||
{episode?.name || 'Select Stream'}
|
{episode?.name || 'Select Stream'}
|
||||||
</Text>
|
</Text>
|
||||||
{episode && (
|
{episode && (
|
||||||
<Text style={{
|
<Text style={{ color: 'rgba(255,255,255,0.6)', fontSize: 13, marginTop: 4 }}>
|
||||||
color: 'rgba(255, 255, 255, 0.6)',
|
|
||||||
fontSize: 12,
|
|
||||||
marginTop: 4,
|
|
||||||
}}>
|
|
||||||
S{episode.season_number}E{episode.episode_number}
|
S{episode.season_number}E{episode.episode_number}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
</View>
|
||||||
style={{
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
borderRadius: 18,
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
onPress={handleClose}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="close" size={20} color="#FFFFFF" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ flex: 1 }}
|
|
||||||
contentContainerStyle={{ padding: 20, paddingBottom: 40 }}
|
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{ padding: 15, paddingBottom: 40 }}
|
||||||
>
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<View style={{
|
<View style={{
|
||||||
|
|
@ -301,9 +241,9 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={`${providerId}-${index}`}
|
key={`${providerId}-${index}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
backgroundColor: 'rgba(255,255,255,0.06)',
|
||||||
borderRadius: 16,
|
borderRadius: 12,
|
||||||
padding: 16,
|
padding: 12,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(255,255,255,0.1)',
|
borderColor: 'rgba(255,255,255,0.1)',
|
||||||
}}
|
}}
|
||||||
|
|
@ -319,7 +259,7 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
gap: 8,
|
gap: 8,
|
||||||
}}>
|
}}>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: '#FFFFFF',
|
color: 'white',
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -333,9 +273,9 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
|
||||||
{stream.size && (
|
{stream.size && (
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<MaterialIcons name="storage" size={14} color="rgba(107, 114, 128, 0.8)" />
|
<MaterialIcons name="storage" size={14} color="rgba(255,255,255,0.5)" />
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: 'rgba(107, 114, 128, 0.8)',
|
color: 'rgba(255,255,255,0.5)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginLeft: 4,
|
marginLeft: 4,
|
||||||
|
|
@ -361,10 +301,7 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{
|
<View style={{ marginLeft: 12, alignItems: 'center' }}>
|
||||||
marginLeft: 12,
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<MaterialIcons name="play-arrow" size={20} color="rgba(255,255,255,0.4)" />
|
<MaterialIcons name="play-arrow" size={20} color="rgba(255,255,255,0.4)" />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -434,7 +371,6 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
||||||
)}
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,21 @@ export const EpisodesModal: React.FC<EpisodesModalProps> = ({
|
||||||
if (showEpisodesModal && metadata?.id) {
|
if (showEpisodesModal && metadata?.id) {
|
||||||
setIsLoadingProgress(true);
|
setIsLoadingProgress(true);
|
||||||
try {
|
try {
|
||||||
const progress = await storageService.getShowProgress(metadata.id);
|
const allProgress = await storageService.getAllWatchProgress();
|
||||||
setEpisodeProgress(progress || {});
|
const progress: { [key: string]: any } = {};
|
||||||
|
|
||||||
|
// Filter progress for current show's episodes
|
||||||
|
Object.entries(allProgress).forEach(([key, value]) => {
|
||||||
|
if (key.includes(metadata.id!)) {
|
||||||
|
progress[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setEpisodeProgress(progress);
|
||||||
|
|
||||||
// Trakt sync logic preserved
|
// Trakt sync logic preserved
|
||||||
if (await TraktService.isAuthenticated()) {
|
const traktService = TraktService.getInstance();
|
||||||
|
if (await traktService.isAuthenticated()) {
|
||||||
// Optional: background sync logic
|
// Optional: background sync logic
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -84,7 +94,7 @@ export const EpisodesModal: React.FC<EpisodesModalProps> = ({
|
||||||
const currentSeasonEpisodes = groupedEpisodes[selectedSeason] || [];
|
const currentSeasonEpisodes = groupedEpisodes[selectedSeason] || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={StyleSheet.absoluteFill} zIndex={9999}>
|
<View style={[StyleSheet.absoluteFill, { zIndex: 9999 }]}>
|
||||||
<TouchableOpacity style={StyleSheet.absoluteFill} activeOpacity={1} onPress={() => setShowEpisodesModal(false)}>
|
<TouchableOpacity style={StyleSheet.absoluteFill} activeOpacity={1} onPress={() => setShowEpisodesModal(false)}>
|
||||||
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)} style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)' }} />
|
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)} style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)' }} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Dimensions } from 'react-native';
|
import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, StyleSheet, Platform, useWindowDimensions } from 'react-native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import Animated, {
|
import Animated, {
|
||||||
FadeIn,
|
FadeIn,
|
||||||
|
|
@ -18,9 +18,6 @@ interface SourcesModalProps {
|
||||||
isChangingSource?: boolean;
|
isChangingSource?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
|
||||||
const MENU_WIDTH = Math.min(width * 0.85, 400);
|
|
||||||
|
|
||||||
const QualityBadge = ({ quality }: { quality: string | null }) => {
|
const QualityBadge = ({ quality }: { quality: string | null }) => {
|
||||||
if (!quality) return null;
|
if (!quality) return null;
|
||||||
|
|
||||||
|
|
@ -72,6 +69,9 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
onSelectStream,
|
onSelectStream,
|
||||||
isChangingSource = false,
|
isChangingSource = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { width } = useWindowDimensions();
|
||||||
|
const MENU_WIDTH = Math.min(width * 0.85, 400);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setShowSourcesModal(false);
|
setShowSourcesModal(false);
|
||||||
};
|
};
|
||||||
|
|
@ -97,29 +97,11 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View style={[StyleSheet.absoluteFill, { zIndex: 9999 }]}>
|
||||||
{/* Backdrop */}
|
<TouchableOpacity style={StyleSheet.absoluteFill} activeOpacity={1} onPress={handleClose}>
|
||||||
<Animated.View
|
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)} style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)' }} />
|
||||||
entering={FadeIn.duration(200)}
|
</TouchableOpacity>
|
||||||
exiting={FadeOut.duration(150)}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
||||||
zIndex: 9998,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
onPress={handleClose}
|
|
||||||
activeOpacity={1}
|
|
||||||
/>
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
{/* Side Menu */}
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={SlideInRight.duration(300)}
|
entering={SlideInRight.duration(300)}
|
||||||
exiting={SlideOutRight.duration(250)}
|
exiting={SlideOutRight.duration(250)}
|
||||||
|
|
@ -129,55 +111,20 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
width: MENU_WIDTH,
|
width: MENU_WIDTH,
|
||||||
backgroundColor: '#1A1A1A',
|
backgroundColor: '#0f0f0f',
|
||||||
zIndex: 9999,
|
borderLeftWidth: 1,
|
||||||
elevation: 20,
|
borderColor: 'rgba(255,255,255,0.1)',
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOffset: { width: -5, height: 0 },
|
|
||||||
shadowOpacity: 0.3,
|
|
||||||
shadowRadius: 10,
|
|
||||||
borderTopLeftRadius: 20,
|
|
||||||
borderBottomLeftRadius: 20,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
<View style={{ paddingTop: Platform.OS === 'ios' ? 60 : 15, paddingHorizontal: 20 }}>
|
||||||
<View style={{
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||||||
flexDirection: 'row',
|
<Text style={{ color: 'white', fontSize: 22, fontWeight: '700' }}>Change Source</Text>
|
||||||
alignItems: 'center',
|
</View>
|
||||||
justifyContent: 'space-between',
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingTop: 60,
|
|
||||||
paddingBottom: 20,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'rgba(255, 255, 255, 0.08)',
|
|
||||||
}}>
|
|
||||||
<Text style={{
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: '700',
|
|
||||||
}}>
|
|
||||||
Change Source
|
|
||||||
</Text>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
borderRadius: 18,
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
onPress={handleClose}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="close" size={20} color="#FFFFFF" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ flex: 1 }}
|
|
||||||
contentContainerStyle={{ padding: 20, paddingBottom: 40 }}
|
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{ padding: 15, paddingBottom: 40 }}
|
||||||
>
|
>
|
||||||
{isChangingSource && (
|
{isChangingSource && (
|
||||||
<View style={{
|
<View style={{
|
||||||
|
|
@ -223,11 +170,11 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={`${providerId}-${index}`}
|
key={`${providerId}-${index}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: isSelected ? 'rgba(59, 130, 246, 0.15)' : 'rgba(255, 255, 255, 0.05)',
|
backgroundColor: isSelected ? 'white' : 'rgba(255,255,255,0.06)',
|
||||||
borderRadius: 16,
|
borderRadius: 12,
|
||||||
padding: 16,
|
padding: 12,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: isSelected ? 'rgba(59, 130, 246, 0.3)' : 'rgba(255, 255, 255, 0.1)',
|
borderColor: isSelected ? 'white' : 'rgba(255,255,255,0.1)',
|
||||||
opacity: (isChangingSource && !isSelected) ? 0.6 : 1,
|
opacity: (isChangingSource && !isSelected) ? 0.6 : 1,
|
||||||
}}
|
}}
|
||||||
onPress={() => handleStreamSelect(stream)}
|
onPress={() => handleStreamSelect(stream)}
|
||||||
|
|
@ -243,9 +190,9 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
gap: 8,
|
gap: 8,
|
||||||
}}>
|
}}>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: '#FFFFFF',
|
color: isSelected ? 'black' : 'white',
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: '500',
|
fontWeight: isSelected ? '700' : '500',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}>
|
}}>
|
||||||
{stream.title || stream.name || `Stream ${index + 1}`}
|
{stream.title || stream.name || `Stream ${index + 1}`}
|
||||||
|
|
@ -257,9 +204,9 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
|
||||||
{stream.size && (
|
{stream.size && (
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<MaterialIcons name="storage" size={14} color="rgba(107, 114, 128, 0.8)" />
|
<MaterialIcons name="storage" size={14} color={isSelected ? 'rgba(0,0,0,0.6)' : 'rgba(255,255,255,0.5)'} />
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: 'rgba(107, 114, 128, 0.8)',
|
color: isSelected ? 'rgba(0,0,0,0.6)' : 'rgba(255,255,255,0.5)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginLeft: 4,
|
marginLeft: 4,
|
||||||
|
|
@ -270,9 +217,9 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
)}
|
)}
|
||||||
{stream.lang && (
|
{stream.lang && (
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<MaterialIcons name="language" size={14} color="rgba(59, 130, 246, 0.8)" />
|
<MaterialIcons name="language" size={14} color={isSelected ? 'rgba(0,0,0,0.6)' : 'rgba(59,130,246,0.8)'} />
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: 'rgba(59, 130, 246, 0.8)',
|
color: isSelected ? 'rgba(0,0,0,0.6)' : 'rgba(59,130,246,0.8)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginLeft: 4,
|
marginLeft: 4,
|
||||||
|
|
@ -285,12 +232,9 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{
|
<View style={{ marginLeft: 12, alignItems: 'center' }}>
|
||||||
marginLeft: 12,
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
{isSelected ? (
|
{isSelected ? (
|
||||||
<MaterialIcons name="check" size={20} color="#3B82F6" />
|
<MaterialIcons name="check" size={18} color="black" />
|
||||||
) : (
|
) : (
|
||||||
<MaterialIcons name="play-arrow" size={20} color="rgba(255,255,255,0.4)" />
|
<MaterialIcons name="play-arrow" size={20} color="rgba(255,255,255,0.4)" />
|
||||||
)}
|
)}
|
||||||
|
|
@ -330,6 +274,6 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
||||||
)}
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity, Platform, useWindowDimensions, ScrollView } from 'react-native';
|
import { View, Text, TouchableOpacity, Platform, useWindowDimensions, ScrollView, StyleSheet } from 'react-native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import Animated, {
|
import Animated, {
|
||||||
FadeIn,
|
FadeIn,
|
||||||
|
|
@ -7,7 +7,6 @@ import Animated, {
|
||||||
SlideInRight,
|
SlideInRight,
|
||||||
SlideOutRight,
|
SlideOutRight,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
||||||
|
|
||||||
interface SpeedModalProps {
|
interface SpeedModalProps {
|
||||||
showSpeedModal: boolean;
|
showSpeedModal: boolean;
|
||||||
|
|
@ -30,20 +29,8 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
holdToSpeedValue,
|
holdToSpeedValue,
|
||||||
setHoldToSpeedValue,
|
setHoldToSpeedValue,
|
||||||
}) => {
|
}) => {
|
||||||
const insets = useSafeAreaInsets();
|
const { width } = useWindowDimensions();
|
||||||
const { width, height } = useWindowDimensions();
|
const MENU_WIDTH = Math.min(width * 0.85, 400);
|
||||||
const isIos = Platform.OS === 'ios';
|
|
||||||
const isLandscape = width > height;
|
|
||||||
|
|
||||||
// Responsive tuning - more aggressive compacting
|
|
||||||
const isCompact = width < 360 || height < 640;
|
|
||||||
const sectionPad = isCompact ? 6 : 8;
|
|
||||||
const chipPadH = isCompact ? 4 : 6;
|
|
||||||
const chipPadV = isCompact ? 3 : 4;
|
|
||||||
const menuWidth = Math.min(
|
|
||||||
width * (isIos ? (isLandscape ? 0.55 : 0.8) : 0.85),
|
|
||||||
isIos ? 380 : 360
|
|
||||||
);
|
|
||||||
|
|
||||||
const speedPresets = [0.5, 1.0, 1.5, 2.0, 2.5];
|
const speedPresets = [0.5, 1.0, 1.5, 2.0, 2.5];
|
||||||
const holdSpeedOptions = [1.5, 2.0];
|
const holdSpeedOptions = [1.5, 2.0];
|
||||||
|
|
@ -60,33 +47,14 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
setHoldToSpeedValue(speed);
|
setHoldToSpeedValue(speed);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSpeedModal = () => {
|
|
||||||
if (!showSpeedModal) return null;
|
if (!showSpeedModal) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View style={[StyleSheet.absoluteFill, { zIndex: 9999 }]}>
|
||||||
{/* Backdrop */}
|
<TouchableOpacity style={StyleSheet.absoluteFill} activeOpacity={1} onPress={handleClose}>
|
||||||
<Animated.View
|
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)} style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)' }} />
|
||||||
entering={FadeIn.duration(200)}
|
</TouchableOpacity>
|
||||||
exiting={FadeOut.duration(150)}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
||||||
zIndex: 9998,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
onPress={handleClose}
|
|
||||||
activeOpacity={1}
|
|
||||||
/>
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
{/* Side Menu */}
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={SlideInRight.duration(300)}
|
entering={SlideInRight.duration(300)}
|
||||||
exiting={SlideOutRight.duration(250)}
|
exiting={SlideOutRight.duration(250)}
|
||||||
|
|
@ -95,65 +63,28 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
width: menuWidth,
|
width: MENU_WIDTH,
|
||||||
backgroundColor: '#1A1A1A',
|
backgroundColor: '#0f0f0f',
|
||||||
zIndex: 9999,
|
borderLeftWidth: 1,
|
||||||
elevation: 20,
|
borderColor: 'rgba(255,255,255,0.1)',
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOffset: { width: -5, height: 0 },
|
|
||||||
shadowOpacity: 0.3,
|
|
||||||
shadowRadius: 10,
|
|
||||||
borderTopLeftRadius: 20,
|
|
||||||
borderBottomLeftRadius: 20,
|
|
||||||
paddingRight: 0,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
<View style={{ paddingTop: Platform.OS === 'ios' ? 60 : 15, paddingHorizontal: 20 }}>
|
||||||
<View style={{
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||||||
flexDirection: 'row',
|
<Text style={{ color: 'white', fontSize: 22, fontWeight: '700' }}>Playback Speed</Text>
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingTop: insets.top + (isCompact ? 4 : 6),
|
|
||||||
paddingBottom: 6,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'rgba(255, 255, 255, 0.08)',
|
|
||||||
}}>
|
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
|
||||||
<MaterialIcons name="speed" size={20} color="#FFFFFF" />
|
|
||||||
<Text style={{ color: '#FFFFFF', fontSize: 18, fontWeight: '700' }}>Playback Speed</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
|
||||||
style={{
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
borderRadius: 18,
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
onPress={handleClose}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="close" size={20} color="#FFFFFF" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ flex: 1 }}
|
|
||||||
contentContainerStyle={{
|
|
||||||
padding: 12,
|
|
||||||
paddingBottom: (isCompact ? 16 : 20) + (isIos ? insets.bottom : 0)
|
|
||||||
}}
|
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{ padding: 15, paddingBottom: 40 }}
|
||||||
>
|
>
|
||||||
|
|
||||||
{/* Current Speed Display */}
|
{/* Current Speed Display */}
|
||||||
<View style={{
|
<View style={{
|
||||||
backgroundColor: 'rgba(59, 130, 246, 0.15)',
|
backgroundColor: 'rgba(59, 130, 246, 0.15)',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
padding: sectionPad * 0.75,
|
padding: 12,
|
||||||
marginBottom: 12,
|
marginBottom: 20,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(59, 130, 246, 0.3)',
|
borderColor: 'rgba(59, 130, 246, 0.3)',
|
||||||
}}>
|
}}>
|
||||||
|
|
@ -161,7 +92,7 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
<MaterialIcons name="play-arrow" size={20} color="#3B82F6" />
|
<MaterialIcons name="play-arrow" size={20} color="#3B82F6" />
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: '#3B82F6',
|
color: '#3B82F6',
|
||||||
fontSize: isCompact ? 16 : 18,
|
fontSize: 18,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
marginLeft: 6
|
marginLeft: 6
|
||||||
}}>
|
}}>
|
||||||
|
|
@ -171,19 +102,19 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Speed Presets */}
|
{/* Speed Presets */}
|
||||||
<View style={{ marginBottom: 16 }}>
|
<View style={{ marginBottom: 20 }}>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: 'rgba(255, 255, 255, 0.7)',
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginBottom: 10,
|
marginBottom: 12,
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
}}>
|
}}>
|
||||||
Speed Presets
|
Speed Presets
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
|
<View style={{ gap: 8 }}>
|
||||||
{speedPresets.map((speed) => {
|
{speedPresets.map((speed) => {
|
||||||
const isSelected = currentSpeed === speed;
|
const isSelected = currentSpeed === speed;
|
||||||
return (
|
return (
|
||||||
|
|
@ -191,24 +122,26 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
key={speed}
|
key={speed}
|
||||||
onPress={() => handleSpeedSelect(speed)}
|
onPress={() => handleSpeedSelect(speed)}
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: chipPadH,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: chipPadV,
|
paddingVertical: 12,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
backgroundColor: isSelected ? 'rgba(59, 130, 246, 0.15)' : 'rgba(255, 255, 255, 0.05)',
|
backgroundColor: isSelected ? 'white' : 'rgba(255,255,255,0.06)',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: isSelected ? 'rgba(59, 130, 246, 0.3)' : 'rgba(255, 255, 255, 0.1)',
|
borderColor: isSelected ? 'white' : 'rgba(255,255,255,0.1)',
|
||||||
minWidth: 50,
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: isSelected ? '#3B82F6' : '#FFFFFF',
|
color: isSelected ? 'black' : 'white',
|
||||||
fontWeight: '600',
|
fontWeight: isSelected ? '700' : '500',
|
||||||
fontSize: isCompact ? 11 : 12
|
fontSize: 15
|
||||||
}}>
|
}}>
|
||||||
{speed}x
|
{speed}x
|
||||||
</Text>
|
</Text>
|
||||||
|
{isSelected && <MaterialIcons name="check" size={18} color="black" />}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -216,22 +149,21 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Hold-to-Speed Settings */}
|
{/* Hold-to-Speed Settings */}
|
||||||
<View style={{ gap: isCompact ? 8 : 12 }}>
|
<View style={{ backgroundColor: 'rgba(255,255,255,0.06)', borderRadius: 12, padding: 16, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)' }}>
|
||||||
<View style={{ backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 12, padding: sectionPad }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 16 }}>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 10 }}>
|
<MaterialIcons name="touch-app" size={18} color="rgba(255,255,255,0.7)" />
|
||||||
<MaterialIcons name="touch-app" size={14} color="rgba(255,255,255,0.7)" />
|
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 14, marginLeft: 6, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 11, marginLeft: 4, fontWeight: '600' }}>
|
|
||||||
Hold-to-Speed
|
Hold-to-Speed
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Enable Toggle */}
|
{/* Enable Toggle */}
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
<Text style={{ color: '#FFFFFF', fontWeight: '600', fontSize: 13 }}>Enable Hold Speed</Text>
|
<Text style={{ color: 'white', fontWeight: '600', fontSize: 15 }}>Enable Hold Speed</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={{
|
style={{
|
||||||
width: isCompact ? 48 : 54,
|
width: 54,
|
||||||
height: isCompact ? 28 : 30,
|
height: 30,
|
||||||
backgroundColor: holdToSpeedEnabled ? '#22C55E' : 'rgba(255,255,255,0.25)',
|
backgroundColor: holdToSpeedEnabled ? '#22C55E' : 'rgba(255,255,255,0.25)',
|
||||||
borderRadius: 15,
|
borderRadius: 15,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|
@ -246,9 +178,9 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
|
|
||||||
{/* Hold Speed Selector */}
|
{/* Hold Speed Selector */}
|
||||||
{holdToSpeedEnabled && (
|
{holdToSpeedEnabled && (
|
||||||
<View style={{ marginBottom: 8 }}>
|
<View style={{ marginBottom: 16 }}>
|
||||||
<Text style={{ color: '#FFFFFF', fontWeight: '600', marginBottom: 6, fontSize: 13 }}>Hold Speed</Text>
|
<Text style={{ color: 'white', fontWeight: '600', marginBottom: 10, fontSize: 15 }}>Hold Speed</Text>
|
||||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 8 }}>
|
||||||
{holdSpeedOptions.map((speed) => {
|
{holdSpeedOptions.map((speed) => {
|
||||||
const isSelected = holdToSpeedValue === speed;
|
const isSelected = holdToSpeedValue === speed;
|
||||||
return (
|
return (
|
||||||
|
|
@ -256,28 +188,26 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
key={speed}
|
key={speed}
|
||||||
onPress={() => handleHoldSpeedSelect(speed)}
|
onPress={() => handleHoldSpeedSelect(speed)}
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: chipPadH,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: chipPadV,
|
paddingVertical: 8,
|
||||||
borderRadius: 10,
|
borderRadius: 20,
|
||||||
backgroundColor: isSelected ? 'rgba(34, 197, 94, 0.15)' : 'rgba(255, 255, 255, 0.05)',
|
backgroundColor: isSelected ? 'white' : 'rgba(255,255,255,0.06)',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: isSelected ? 'rgba(34, 197, 94, 0.3)' : 'rgba(255, 255, 255, 0.1)',
|
borderColor: isSelected ? 'white' : 'rgba(255,255,255,0.1)',
|
||||||
minWidth: 45,
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
}}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: isSelected ? '#22C55E' : '#FFFFFF',
|
color: isSelected ? 'black' : 'white',
|
||||||
fontWeight: '600',
|
fontWeight: isSelected ? '700' : '500',
|
||||||
fontSize: isCompact ? 10 : 11
|
fontSize: 14
|
||||||
}}>
|
}}>
|
||||||
{speed}x
|
{speed}x
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</View>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -285,16 +215,16 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
<View style={{
|
<View style={{
|
||||||
backgroundColor: 'rgba(34, 197, 94, 0.1)',
|
backgroundColor: 'rgba(34, 197, 94, 0.1)',
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
padding: sectionPad * 0.75,
|
padding: 12,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(34, 197, 94, 0.3)',
|
borderColor: 'rgba(34, 197, 94, 0.3)',
|
||||||
}}>
|
}}>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 8 }}>
|
<View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 8 }}>
|
||||||
<MaterialIcons name="info" size={14} color="#22C55E" />
|
<MaterialIcons name="info" size={16} color="#22C55E" />
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: '#22C55E',
|
color: '#22C55E',
|
||||||
fontSize: isCompact ? 12 : 13,
|
fontSize: 13,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
}}>
|
}}>
|
||||||
|
|
@ -302,8 +232,8 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: 'rgba(255, 255, 255, 0.8)',
|
color: 'rgba(255, 255, 255, 0.8)',
|
||||||
fontSize: isCompact ? 11 : 12,
|
fontSize: 12,
|
||||||
lineHeight: isCompact ? 14 : 16,
|
lineHeight: 16,
|
||||||
}}>
|
}}>
|
||||||
Hold and press the left or right side of the video player to temporarily boost playback speed.
|
Hold and press the left or right side of the video player to temporarily boost playback speed.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
@ -311,17 +241,9 @@ export const SpeedModal: React.FC<SpeedModalProps> = ({
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</>
|
</View>
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{renderSpeedModal()}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import Animated, {
|
||||||
SlideInRight,
|
SlideInRight,
|
||||||
SlideOutRight,
|
SlideOutRight,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { styles } from '../utils/playerStyles';
|
import { StyleSheet } from 'react-native';
|
||||||
import { WyzieSubtitle, SubtitleCue } from '../utils/playerTypes';
|
import { WyzieSubtitle, SubtitleCue } from '../utils/playerTypes';
|
||||||
import { getTrackDisplayName, formatLanguage } from '../utils/playerUtils';
|
import { getTrackDisplayName, formatLanguage } from '../utils/playerUtils';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
@ -185,29 +185,11 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
||||||
if (!showSubtitleModal) return null;
|
if (!showSubtitleModal) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View style={[StyleSheet.absoluteFill, { zIndex: 9999 }]}>
|
||||||
{/* Backdrop */}
|
<TouchableOpacity style={StyleSheet.absoluteFill} activeOpacity={1} onPress={handleClose}>
|
||||||
<Animated.View
|
<Animated.View entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)} style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)' }} />
|
||||||
entering={FadeIn.duration(200)}
|
</TouchableOpacity>
|
||||||
exiting={FadeOut.duration(150)}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
||||||
zIndex: 9998,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
onPress={handleClose}
|
|
||||||
activeOpacity={1}
|
|
||||||
/>
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
{/* Side Menu */}
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={SlideInRight.duration(300)}
|
entering={SlideInRight.duration(300)}
|
||||||
exiting={SlideOutRight.duration(250)}
|
exiting={SlideOutRight.duration(250)}
|
||||||
|
|
@ -217,51 +199,22 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
width: menuWidth,
|
width: menuWidth,
|
||||||
backgroundColor: '#1A1A1A',
|
backgroundColor: '#0f0f0f',
|
||||||
zIndex: 9999,
|
borderLeftWidth: 1,
|
||||||
elevation: 20,
|
borderColor: 'rgba(255,255,255,0.1)',
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOffset: { width: -5, height: 0 },
|
|
||||||
shadowOpacity: 0.3,
|
|
||||||
shadowRadius: 10,
|
|
||||||
borderTopLeftRadius: 20,
|
|
||||||
borderBottomLeftRadius: 20,
|
|
||||||
paddingRight: 0,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
<View style={{ paddingTop: Platform.OS === 'ios' ? 60 : 15, paddingHorizontal: 20 }}>
|
||||||
<View style={{
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 }}>
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingTop: insets.top + (isCompact ? 8 : 12),
|
|
||||||
paddingBottom: 12,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'rgba(255, 255, 255, 0.08)',
|
|
||||||
}}>
|
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
||||||
<Text style={{ color: '#FFFFFF', fontSize: 22, fontWeight: '700' }}>Subtitles</Text>
|
<Text style={{ color: 'white', fontSize: 22, fontWeight: '700' }}>Subtitles</Text>
|
||||||
<View style={{ paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, backgroundColor: useCustomSubtitles ? 'rgba(34,197,94,0.2)' : 'rgba(59,130,246,0.2)' }}>
|
<View style={{ paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, backgroundColor: useCustomSubtitles ? 'rgba(34,197,94,0.2)' : 'rgba(59,130,246,0.2)' }}>
|
||||||
<Text style={{ color: useCustomSubtitles ? '#22C55E' : '#3B82F6', fontSize: 11, fontWeight: '700' }}>
|
<Text style={{ color: useCustomSubtitles ? '#22C55E' : '#3B82F6', fontSize: 11, fontWeight: '700' }}>
|
||||||
{useCustomSubtitles ? 'Addon in use' : 'Built‑in in use'}
|
{useCustomSubtitles ? 'Addon in use' : 'Built‑in in use'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
</View>
|
||||||
style={{
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
borderRadius: 18,
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
onPress={handleClose}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="close" size={20} color="#FFFFFF" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Segmented Tabs */}
|
{/* Segmented Tabs */}
|
||||||
|
|
@ -902,7 +855,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue