mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
updated remaining pages for localization. metascreen and player components
This commit is contained in:
parent
611b37c847
commit
280536e93c
12 changed files with 213 additions and 76 deletions
|
|
@ -866,7 +866,6 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
styles.seasonTextButton,
|
||||
{
|
||||
marginRight: seasonButtonSpacing,
|
||||
width: isTV ? 150 : isLargeTablet ? 140 : isTablet ? 130 : 110,
|
||||
paddingVertical: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12,
|
||||
paddingHorizontal: isTV ? 20 : isLargeTablet ? 18 : isTablet ? 16 : 16,
|
||||
borderRadius: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12
|
||||
|
|
@ -885,7 +884,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
{ color: currentTheme.colors.highEmphasis }
|
||||
]
|
||||
]} numberOfLines={1}>
|
||||
{season === 0 ? 'Specials' : `Season ${season}`}
|
||||
{season === 0 ? t('metadata.specials') : t('metadata.season_number', { number: season })}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
@ -948,7 +947,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
]
|
||||
]}
|
||||
>
|
||||
{season === 0 ? 'Specials' : `Season ${season}`}
|
||||
{season === 0 ? t('metadata.specials') : t('metadata.season_number', { number: season })}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
@ -1559,7 +1558,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
paddingHorizontal: horizontalPadding
|
||||
}
|
||||
]}>
|
||||
{currentSeasonEpisodes.length} {currentSeasonEpisodes.length === 1 ? 'Episode' : 'Episodes'}
|
||||
{currentSeasonEpisodes.length === 1 ? t('metadata.episode_count', { count: currentSeasonEpisodes.length }) : t('metadata.episode_count_plural', { count: currentSeasonEpisodes.length })}
|
||||
</Text>
|
||||
|
||||
{/* Show message when no episodes are available for selected season */}
|
||||
|
|
@ -1567,10 +1566,10 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
<View style={styles.centeredContainer}>
|
||||
<MaterialIcons name="schedule" size={48} color={currentTheme.colors.textMuted} />
|
||||
<Text style={[styles.centeredText, { color: currentTheme.colors.text }]}>
|
||||
No episodes available for Season {selectedSeason}
|
||||
{t('metadata.no_episodes_for_season', { season: selectedSeason })}
|
||||
</Text>
|
||||
<Text style={[styles.centeredSubText, { color: currentTheme.colors.textMuted }]}>
|
||||
Episodes may not be released yet
|
||||
{t('metadata.episodes_not_released')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
|
@ -1750,7 +1749,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
fontSize: isTV ? 16 : 15,
|
||||
fontWeight: '500',
|
||||
}}>
|
||||
{markingAsWatched ? 'Removing...' : 'Mark as Unwatched'}
|
||||
{markingAsWatched ? t('metadata.removing') : t('metadata.mark_as_unwatched')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
|
|
@ -1777,7 +1776,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
fontSize: isTV ? 16 : 15,
|
||||
fontWeight: '600',
|
||||
}}>
|
||||
{markingAsWatched ? 'Marking...' : 'Mark as Watched'}
|
||||
{markingAsWatched ? t('metadata.marking') : t('metadata.mark_as_watched')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
|
|
@ -1809,7 +1808,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
fontWeight: '500',
|
||||
flex: 1, // Allow text to take up space
|
||||
}} numberOfLines={1}>
|
||||
{markingAsWatched ? 'Removing...' : `Unmark Season ${selectedSeason}`}
|
||||
{markingAsWatched ? t('metadata.removing') : t('metadata.unmark_season', { season: selectedSeason })}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
|
|
@ -1837,7 +1836,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
fontWeight: '500',
|
||||
flex: 1,
|
||||
}} numberOfLines={1}>
|
||||
{markingAsWatched ? 'Marking...' : `Mark Season ${selectedSeason}`}
|
||||
{markingAsWatched ? t('metadata.marking') : t('metadata.mark_season', { season: selectedSeason })}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
|
@ -1856,7 +1855,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
|||
fontSize: isTV ? 15 : 14,
|
||||
fontWeight: '500',
|
||||
}}>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||
import Feather from 'react-native-vector-icons/Feather';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import Slider from '@react-native-community/slider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { styles } from '../utils/playerStyles'; // Updated styles
|
||||
import { getTrackDisplayName } from '../utils/playerUtils';
|
||||
import { useTheme } from '../../../contexts/ThemeContext';
|
||||
|
|
@ -99,6 +100,7 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
useExoPlayer,
|
||||
}) => {
|
||||
const { currentTheme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
/* Responsive Spacing */
|
||||
|
|
@ -287,7 +289,7 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={duration || 1}
|
||||
|
||||
|
||||
value={previewTime}
|
||||
|
||||
onValueChange={(v) => setPreviewTime(v)}
|
||||
|
|
@ -338,7 +340,7 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
{/* Show year and provider (quality chip removed) */}
|
||||
<View style={styles.metadataRow}>
|
||||
{year && <Text style={styles.metadataText}>{year}</Text>}
|
||||
{streamName && <Text style={styles.providerText}>via {streamName}</Text>}
|
||||
{streamName && <Text style={styles.providerText}>{t('player_ui.via', { name: streamName })}</Text>}
|
||||
</View>
|
||||
{playerBackend && (
|
||||
<View style={styles.metadataRow}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity, ScrollView, useWindowDimensions, StyleSheet, Platform } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Animated, {
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
|
|
@ -25,6 +26,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
|||
selectedAudioTrack,
|
||||
selectAudioTrack,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { width, height } = useWindowDimensions();
|
||||
|
||||
// Size constants matching SubtitleModal aesthetics
|
||||
|
|
@ -67,7 +69,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
|||
>
|
||||
{/* Header with shared aesthetics */}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', padding: 20, position: 'relative' }}>
|
||||
<Text style={{ color: 'white', fontSize: 18, fontWeight: '700' }}>Audio Tracks</Text>
|
||||
<Text style={{ color: 'white', fontSize: 18, fontWeight: '700' }}>{t('player_ui.audio_tracks')}</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
|
|
@ -111,7 +113,7 @@ export const AudioTrackModal: React.FC<AudioTrackModalProps> = ({
|
|||
{ksAudioTracks.length === 0 && (
|
||||
<View style={{ padding: 40, alignItems: 'center', opacity: 0.5 }}>
|
||||
<MaterialIcons name="volume-off" size={32} color="white" />
|
||||
<Text style={{ color: 'white', marginTop: 10 }}>No audio tracks available</Text>
|
||||
<Text style={{ color: 'white', marginTop: 10 }}>{t('player_ui.no_audio_tracks')}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Animated, {
|
|||
SlideInRight,
|
||||
SlideOutRight,
|
||||
} from 'react-native-reanimated';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Episode } from '../../../types/metadata';
|
||||
import { Stream } from '../../../types/streams';
|
||||
import { stremioService } from '../../../services/stremioService';
|
||||
|
|
@ -58,6 +59,7 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
|||
onSelectStream,
|
||||
metadata,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { width } = useWindowDimensions();
|
||||
const MENU_WIDTH = Math.min(width * 0.85, 400);
|
||||
|
||||
|
|
@ -177,7 +179,7 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
|||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<View style={{ flex: 1, marginRight: 10 }}>
|
||||
<Text style={{ color: 'white', fontSize: 20, fontWeight: '700' }} numberOfLines={1}>
|
||||
{episode?.name || 'Sources'}
|
||||
{episode?.name || t('player_ui.sources')}
|
||||
</Text>
|
||||
{episode && (
|
||||
<Text style={{ color: 'rgba(255,255,255,0.5)', fontSize: 13, marginTop: 4 }}>
|
||||
|
|
@ -195,7 +197,7 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
|||
{isLoading && sortedProviders.length === 0 && (
|
||||
<View style={{ padding: 40, alignItems: 'center' }}>
|
||||
<ActivityIndicator color="white" />
|
||||
<Text style={{ color: 'white', marginTop: 15, opacity: 0.6 }}>Finding sources...</Text>
|
||||
<Text style={{ color: 'white', marginTop: 15, opacity: 0.6 }}>{t('player_ui.finding_sources')}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
|
@ -237,7 +239,7 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
|||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 4 }}>
|
||||
<Text style={{ color: 'white', fontWeight: '700', fontSize: 14, flex: 1 }} numberOfLines={1}>
|
||||
{stream.name || 'Unknown Source'}
|
||||
{stream.name || t('player_ui.unknown_source')}
|
||||
</Text>
|
||||
<QualityBadge quality={quality} />
|
||||
</View>
|
||||
|
|
@ -258,13 +260,13 @@ export const EpisodeStreamsModal: React.FC<EpisodeStreamsModalProps> = ({
|
|||
{!isLoading && sortedProviders.length === 0 && (
|
||||
<View style={{ padding: 40, alignItems: 'center', opacity: 0.5 }}>
|
||||
<MaterialIcons name="cloud-off" size={48} color="white" />
|
||||
<Text style={{ color: 'white', marginTop: 16, textAlign: 'center', fontWeight: '600' }}>No sources found</Text>
|
||||
<Text style={{ color: 'white', marginTop: 16, textAlign: 'center', fontWeight: '600' }}>{t('player_ui.no_sources_found')}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{hasErrors.length > 0 && (
|
||||
<View style={{ backgroundColor: 'rgba(239, 68, 68, 0.1)', borderRadius: 12, padding: 12, marginTop: 10 }}>
|
||||
<Text style={{ color: '#EF4444', fontSize: 11 }}>Sources might be limited due to provider errors.</Text>
|
||||
<Text style={{ color: '#EF4444', fontSize: 11 }}>{t('player_ui.sources_limited')}</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Animated, {
|
|||
SlideInRight,
|
||||
SlideOutRight,
|
||||
} from 'react-native-reanimated';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Episode } from '../../../types/metadata';
|
||||
import { EpisodeCard } from '../cards/EpisodeCard';
|
||||
import { storageService } from '../../../services/storageService';
|
||||
|
|
@ -32,6 +33,7 @@ export const EpisodesModal: React.FC<EpisodesModalProps> = ({
|
|||
onSelectEpisode,
|
||||
tmdbEpisodeOverrides
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { width } = useWindowDimensions();
|
||||
const [selectedSeason, setSelectedSeason] = useState<number>(currentEpisode?.season || 1);
|
||||
const [episodeProgress, setEpisodeProgress] = useState<{ [key: string]: any }>({});
|
||||
|
|
@ -117,7 +119,7 @@ export const EpisodesModal: React.FC<EpisodesModalProps> = ({
|
|||
>
|
||||
<View style={{ paddingTop: Platform.OS === 'ios' ? 60 : 20, paddingHorizontal: 20 }}>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
|
||||
<Text style={{ color: 'white', fontSize: 22, fontWeight: '700' }}>Episodes</Text>
|
||||
<Text style={{ color: 'white', fontSize: 22, fontWeight: '700' }}>{t('player_ui.episodes')}</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 15, gap: 8 }}>
|
||||
|
|
@ -143,7 +145,7 @@ export const EpisodesModal: React.FC<EpisodesModalProps> = ({
|
|||
color: selectedSeason === season ? 'black' : 'white',
|
||||
fontWeight: selectedSeason === season ? '700' : '500'
|
||||
}}>
|
||||
{season === 0 ? 'Specials' : `Season ${season}`}
|
||||
{season === 0 ? t('player_ui.specials') : t('player_ui.season', { season })}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import * as ExpoClipboard from 'expo-clipboard';
|
||||
import { View, Text, TouchableOpacity, StyleSheet, useWindowDimensions } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Animated, {
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
|
|
@ -22,6 +23,7 @@ export const ErrorModal: React.FC<ErrorModalProps> = ({
|
|||
errorDetails,
|
||||
onDismiss,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
const { width } = useWindowDimensions();
|
||||
const MODAL_WIDTH = Math.min(width * 0.8, 400);
|
||||
|
|
@ -79,7 +81,7 @@ export const ErrorModal: React.FC<ErrorModalProps> = ({
|
|||
marginBottom: 8,
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
Playback Error
|
||||
{t('player_ui.playback_error')}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
|
|
@ -93,7 +95,7 @@ export const ErrorModal: React.FC<ErrorModalProps> = ({
|
|||
lineHeight: 22
|
||||
}}
|
||||
>
|
||||
{errorDetails || 'An unknown error occurred during playback.'}
|
||||
{errorDetails || t('player_ui.unknown_error')}
|
||||
</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
|
|
@ -114,7 +116,7 @@ export const ErrorModal: React.FC<ErrorModalProps> = ({
|
|||
style={{ marginRight: 6 }}
|
||||
/>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.6)', fontSize: 13, fontWeight: '500' }}>
|
||||
{copied ? 'Copied to clipboard' : 'Copy error details'}
|
||||
{copied ? t('player_ui.copied_to_clipboard') : t('player_ui.copy_error')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
|
@ -135,7 +137,7 @@ export const ErrorModal: React.FC<ErrorModalProps> = ({
|
|||
fontSize: 16,
|
||||
fontWeight: '700'
|
||||
}}>
|
||||
Dismiss
|
||||
{t('player_ui.dismiss')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
|
|||
import { View, Text, TouchableOpacity } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { styles } from '../utils/playerStyles';
|
||||
import { formatTime } from '../utils/playerUtils';
|
||||
import { logger } from '../../../utils/logger';
|
||||
|
|
@ -27,6 +28,7 @@ export const ResumeOverlay: React.FC<ResumeOverlayProps> = ({
|
|||
handleResume,
|
||||
handleStartFromBeginning,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
// Removed excessive logging for props changes
|
||||
}, [showResumeOverlay, resumePosition, duration, title]);
|
||||
|
|
@ -35,9 +37,9 @@ export const ResumeOverlay: React.FC<ResumeOverlayProps> = ({
|
|||
// Removed excessive logging for overlay visibility
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Removed excessive logging for overlay rendering
|
||||
|
||||
|
||||
return (
|
||||
<View style={styles.resumeOverlay}>
|
||||
<LinearGradient
|
||||
|
|
@ -49,18 +51,18 @@ export const ResumeOverlay: React.FC<ResumeOverlayProps> = ({
|
|||
<Ionicons name="play-circle" size={40} color="#E50914" />
|
||||
</View>
|
||||
<View style={styles.resumeTextContainer}>
|
||||
<Text style={styles.resumeTitle}>Continue Watching</Text>
|
||||
<Text style={styles.resumeTitle}>{t('player_ui.continue_watching')}</Text>
|
||||
<Text style={styles.resumeInfo}>
|
||||
{title}
|
||||
{season && episode && ` • S${season}E${episode}`}
|
||||
</Text>
|
||||
<View style={styles.resumeProgressContainer}>
|
||||
<View style={styles.resumeProgressBar}>
|
||||
<View
|
||||
<View
|
||||
style={[
|
||||
styles.resumeProgressFill,
|
||||
styles.resumeProgressFill,
|
||||
{ width: `${duration > 0 ? (resumePosition / duration) * 100 : 0}%` }
|
||||
]}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.resumeTimeText}>
|
||||
|
|
@ -71,19 +73,19 @@ export const ResumeOverlay: React.FC<ResumeOverlayProps> = ({
|
|||
</View>
|
||||
|
||||
<View style={styles.resumeButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.resumeButton}
|
||||
<TouchableOpacity
|
||||
style={styles.resumeButton}
|
||||
onPress={handleStartFromBeginning}
|
||||
>
|
||||
<Ionicons name="refresh" size={16} color="white" style={styles.buttonIcon} />
|
||||
<Text style={styles.resumeButtonText}>Start Over</Text>
|
||||
<Text style={styles.resumeButtonText}>{t('player_ui.start_over')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.resumeButton, styles.resumeFromButton]}
|
||||
<TouchableOpacity
|
||||
style={[styles.resumeButton, styles.resumeFromButton]}
|
||||
onPress={handleResume}
|
||||
>
|
||||
<Ionicons name="play" size={16} color="white" style={styles.buttonIcon} />
|
||||
<Text style={styles.resumeButtonText}>Resume</Text>
|
||||
<Text style={styles.resumeButtonText}>{t('player_ui.resume')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Animated, {
|
|||
SlideInRight,
|
||||
SlideOutRight,
|
||||
} from 'react-native-reanimated';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Stream } from '../../../types/streams';
|
||||
|
||||
interface SourcesModalProps {
|
||||
|
|
@ -57,6 +58,7 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
onSelectStream,
|
||||
isChangingSource = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { width } = useWindowDimensions();
|
||||
const MENU_WIDTH = Math.min(width * 0.85, 400);
|
||||
|
||||
|
|
@ -123,7 +125,7 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
alignItems: 'center'
|
||||
}}>
|
||||
<Text style={{ color: 'white', fontSize: 20, fontWeight: '700' }}>
|
||||
Change Source
|
||||
{t('player_ui.change_source')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
|
@ -142,7 +144,7 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
}}>
|
||||
<ActivityIndicator size="small" color="#22C55E" />
|
||||
<Text style={{ color: '#22C55E', fontSize: 14, fontWeight: '600', marginLeft: 10 }}>
|
||||
Switching source...
|
||||
{t('player_ui.switching_source')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
|
@ -191,7 +193,7 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
fontSize: 14,
|
||||
flex: 1,
|
||||
}} numberOfLines={1}>
|
||||
{stream.title || stream.name || `Stream ${index + 1}`}
|
||||
{stream.title || stream.name || t('player_ui.stream', { number: index + 1 })}
|
||||
</Text>
|
||||
<QualityBadge quality={quality} />
|
||||
</View>
|
||||
|
|
@ -237,7 +239,7 @@ export const SourcesModal: React.FC<SourcesModalProps> = ({
|
|||
<View style={{ padding: 40, alignItems: 'center', opacity: 0.5 }}>
|
||||
<MaterialIcons name="cloud-off" size={48} color="white" />
|
||||
<Text style={{ color: 'white', marginTop: 16, textAlign: 'center', fontWeight: '600' }}>
|
||||
No sources found
|
||||
{t('player_ui.no_sources_found')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity, useWindowDimensions, StyleSheet } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Animated, {
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
|
|
@ -55,6 +56,7 @@ const SpeedModal: React.FC<SpeedModalProps> = ({
|
|||
holdToSpeedValue,
|
||||
setHoldToSpeedValue,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { width } = useWindowDimensions();
|
||||
const speedPresets = [0.5, 1.0, 1.25, 1.5, 2.0, 2.5];
|
||||
const holdSpeedOptions = [1.0, 2.0, 3.0];
|
||||
|
|
@ -85,7 +87,7 @@ const SpeedModal: React.FC<SpeedModalProps> = ({
|
|||
}}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20, alignItems: 'center' }}>
|
||||
<Text style={{ color: 'white', fontSize: 16, fontWeight: '600' }}>Playback Speed</Text>
|
||||
<Text style={{ color: 'white', fontSize: 16, fontWeight: '600' }}>{t('player_ui.playback_speed')}</Text>
|
||||
</View>
|
||||
|
||||
{/* Speed Selection Row */}
|
||||
|
|
@ -108,7 +110,7 @@ const SpeedModal: React.FC<SpeedModalProps> = ({
|
|||
onPress={() => setHoldToSpeedEnabled(!holdToSpeedEnabled)}
|
||||
style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: holdToSpeedEnabled ? 15 : 0 }}
|
||||
>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 14 }}>On Hold</Text>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 14 }}>{t('player_ui.on_hold')}</Text>
|
||||
<View style={{
|
||||
width: 34, height: 18, borderRadius: 10,
|
||||
backgroundColor: holdToSpeedEnabled ? 'white' : 'rgba(255,255,255,0.2)',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import Animated, {
|
|||
useAnimatedStyle,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { WyzieSubtitle, SubtitleCue } from '../utils/playerTypes';
|
||||
import { getTrackDisplayName, formatLanguage } from '../utils/playerUtils';
|
||||
|
||||
|
|
@ -96,6 +97,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
selectedExternalSubtitleId,
|
||||
onOpenSyncModal,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { width, height } = useWindowDimensions();
|
||||
const isIos = Platform.OS === 'ios';
|
||||
const isLandscape = width > height;
|
||||
|
|
@ -151,14 +153,14 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
>
|
||||
{/* Header */}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', padding: 20, position: 'relative' }}>
|
||||
<Text style={{ color: 'white', fontSize: 18, fontWeight: '700' }}>Subtitles</Text>
|
||||
<Text style={{ color: 'white', fontSize: 18, fontWeight: '700' }}>{t('player_ui.subtitles')}</Text>
|
||||
</View>
|
||||
|
||||
{/* Tab Bar */}
|
||||
<View style={{ flexDirection: 'row', gap: 15, paddingHorizontal: 70, marginBottom: 20 }}>
|
||||
<MorphingTab label="Built-in" isSelected={activeTab === 'built-in'} onPress={() => setActiveTab('built-in')} />
|
||||
<MorphingTab label="Addons" isSelected={activeTab === 'addon'} onPress={() => setActiveTab('addon')} />
|
||||
<MorphingTab label="Style" isSelected={activeTab === 'appearance'} onPress={() => setActiveTab('appearance')} />
|
||||
<MorphingTab label={t('player_ui.built_in')} isSelected={activeTab === 'built-in'} onPress={() => setActiveTab('built-in')} />
|
||||
<MorphingTab label={t('player_ui.addons')} isSelected={activeTab === 'addon'} onPress={() => setActiveTab('addon')} />
|
||||
<MorphingTab label={t('player_ui.style')} isSelected={activeTab === 'appearance'} onPress={() => setActiveTab('appearance')} />
|
||||
</View>
|
||||
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
|
|
@ -174,7 +176,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
}}
|
||||
style={{ padding: 10, borderRadius: 12, backgroundColor: selectedTextTrack === -1 ? 'white' : 'rgba(242, 184, 181)' }}
|
||||
>
|
||||
<Text style={{ color: selectedTextTrack === -1 ? 'black' : 'rgba(96, 20, 16)', fontWeight: '600' }}>None</Text>
|
||||
<Text style={{ color: selectedTextTrack === -1 ? 'black' : 'rgba(96, 20, 16)', fontWeight: '600' }}>{t('player_ui.none')}</Text>
|
||||
</TouchableOpacity>
|
||||
{ksTextTracks.map((track) => (
|
||||
<TouchableOpacity
|
||||
|
|
@ -199,7 +201,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{availableSubtitles.length === 0 ? (
|
||||
<TouchableOpacity onPress={fetchAvailableSubtitles} style={{ padding: 40, alignItems: 'center', opacity: 0.5 }}>
|
||||
<MaterialIcons name="cloud-download" size={32} color="white" />
|
||||
<Text style={{ color: 'white', marginTop: 10 }}>Search Online Subtitles</Text>
|
||||
<Text style={{ color: 'white', marginTop: 10 }}>{t('player_ui.search_online_subtitles')}</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
availableSubtitles.map((sub) => (
|
||||
|
|
@ -230,7 +232,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
<View style={{ backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 16, padding: sectionPad }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 8 }}>
|
||||
<MaterialIcons name="visibility" size={16} color="rgba(255,255,255,0.7)" />
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12, marginLeft: 6, fontWeight: '600' }}>Preview</Text>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12, marginLeft: 6, fontWeight: '600' }}>{t('player_ui.preview')}</Text>
|
||||
</View>
|
||||
<View style={{ height: previewHeight, justifyContent: 'flex-end' }}>
|
||||
<View style={{ alignItems: subtitleAlign === 'center' ? 'center' : subtitleAlign === 'left' ? 'flex-start' : 'flex-end', marginBottom: Math.min(80, subtitleBottomOffset) }}>
|
||||
|
|
@ -262,7 +264,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
<View style={{ backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 16, padding: sectionPad }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 10 }}>
|
||||
<MaterialIcons name="star" size={16} color="rgba(255,255,255,0.7)" />
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12, marginLeft: 6, fontWeight: '600' }}>Quick Presets</Text>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12, marginLeft: 6, fontWeight: '600' }}>{t('player_ui.quick_presets')}</Text>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
|
||||
<TouchableOpacity
|
||||
|
|
@ -274,7 +276,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
}}
|
||||
style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)' }}
|
||||
>
|
||||
<Text style={{ color: '#fff', fontWeight: '600', fontSize: isCompact ? 11 : 12 }}>Default</Text>
|
||||
<Text style={{ color: '#fff', fontWeight: '600', fontSize: isCompact ? 11 : 12 }}>{t('player_ui.default')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
|
|
@ -282,7 +284,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
}}
|
||||
style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(255,215,0,0.12)', borderWidth: 1, borderColor: 'rgba(255,215,0,0.35)' }}
|
||||
>
|
||||
<Text style={{ color: '#FFD700', fontWeight: '700', fontSize: isCompact ? 11 : 12 }}>Yellow</Text>
|
||||
<Text style={{ color: '#FFD700', fontWeight: '700', fontSize: isCompact ? 11 : 12 }}>{t('player_ui.yellow')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
|
|
@ -290,7 +292,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
}}
|
||||
style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(34,197,94,0.12)', borderWidth: 1, borderColor: 'rgba(34,197,94,0.35)' }}
|
||||
>
|
||||
<Text style={{ color: '#22C55E', fontWeight: '700', fontSize: isCompact ? 11 : 12 }}>High Contrast</Text>
|
||||
<Text style={{ color: '#22C55E', fontWeight: '700', fontSize: isCompact ? 11 : 12 }}>{t('player_ui.high_contrast')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
|
|
@ -298,7 +300,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
}}
|
||||
style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(59,130,246,0.12)', borderWidth: 1, borderColor: 'rgba(59,130,246,0.35)' }}
|
||||
>
|
||||
<Text style={{ color: '#3B82F6', fontWeight: '700', fontSize: isCompact ? 11 : 12 }}>Large</Text>
|
||||
<Text style={{ color: '#3B82F6', fontWeight: '700', fontSize: isCompact ? 11 : 12 }}>{t('player_ui.large')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -308,12 +310,12 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
<View style={{ backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 16, padding: sectionPad, gap: isCompact ? 10 : 14 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 2 }}>
|
||||
<MaterialIcons name="tune" size={16} color="rgba(255,255,255,0.7)" />
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12, marginLeft: 6, fontWeight: '600' }}>Core</Text>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12, marginLeft: 6, fontWeight: '600' }}>{t('player_ui.core')}</Text>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<MaterialIcons name="format-size" size={16} color="rgba(255,255,255,0.7)" />
|
||||
<Text style={{ color: '#fff', fontWeight: '600', marginLeft: 8 }}>Font Size</Text>
|
||||
<Text style={{ color: '#fff', fontWeight: '600', marginLeft: 8 }}>{t('player_ui.font_size')}</Text>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
<TouchableOpacity onPress={decreaseSubtitleSize} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', justifyContent: 'center', alignItems: 'center' }}>
|
||||
|
|
@ -332,7 +334,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<MaterialIcons name="layers" size={16} color="rgba(255,255,255,0.7)" />
|
||||
<Text style={{ color: '#fff', fontWeight: '600', marginLeft: 8 }}>Show Background</Text>
|
||||
<Text style={{ color: '#fff', fontWeight: '600', marginLeft: 8 }}>{t('player_ui.show_background')}</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={{ width: isCompact ? 48 : 54, height: isCompact ? 28 : 30, backgroundColor: subtitleBackground ? 'white' : 'rgba(255,255,255,0.25)', borderRadius: 15, justifyContent: 'center', alignItems: subtitleBackground ? 'flex-end' : 'flex-start', paddingHorizontal: 3 }}
|
||||
|
|
@ -348,14 +350,14 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
<View style={{ backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 16, padding: sectionPad, gap: isCompact ? 10 : 14 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<MaterialIcons name="build" size={16} color="rgba(255,255,255,0.7)" />
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12, marginLeft: 6, fontWeight: '600' }}>{isExoPlayerInternal ? 'Position' : 'Advanced'}</Text>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12, marginLeft: 6, fontWeight: '600' }}>{isExoPlayerInternal ? t('player_ui.position') : t('player_ui.advanced')}</Text>
|
||||
</View>
|
||||
{/* Text Color - Not supported on ExoPlayer internal subtitles */}
|
||||
{!isExoPlayerInternal && (
|
||||
<View style={{ marginTop: 8, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<MaterialIcons name="palette" size={16} color="rgba(255,255,255,0.7)" />
|
||||
<Text style={{ color: 'white', marginLeft: 8, fontWeight: '600' }}>Text Color</Text>
|
||||
<Text style={{ color: 'white', marginLeft: 8, fontWeight: '600' }}>{t('player_ui.text_color')}</Text>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, justifyContent: 'flex-end' }}>
|
||||
{['#FFFFFF', '#FFD700', '#00E5FF', '#FF5C5C', '#00FF88', '#9b59b6', '#f97316'].map(c => (
|
||||
|
|
@ -367,7 +369,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{/* Align - Not supported on ExoPlayer internal subtitles */}
|
||||
{!isExoPlayerInternal && (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Align</Text>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>{t('player_ui.align')}</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||
{([{ key: 'left', icon: 'format-align-left' }, { key: 'center', icon: 'format-align-center' }, { key: 'right', icon: 'format-align-right' }] as const).map(a => (
|
||||
<TouchableOpacity key={a.key} onPress={() => setSubtitleAlign(a.key)} style={{ paddingHorizontal: isCompact ? 8 : 10, paddingVertical: isCompact ? 4 : 6, borderRadius: 8, backgroundColor: subtitleAlign === a.key ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)' }}>
|
||||
|
|
@ -378,7 +380,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</View>
|
||||
)}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Bottom Offset</Text>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>{t('player_ui.bottom_offset')}</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleBottomOffset(Math.max(0, subtitleBottomOffset - 5))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="keyboard-arrow-down" color="#fff" size={20} />
|
||||
|
|
@ -394,7 +396,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{/* Background Opacity - Not supported on ExoPlayer internal subtitles */}
|
||||
{!isExoPlayerInternal && (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Background Opacity</Text>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>{t('player_ui.background_opacity')}</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleBgOpacity(Math.max(0, +(subtitleBgOpacity - 0.1).toFixed(1)))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
|
|
@ -410,16 +412,16 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
)}
|
||||
{!isUsingInternalSubtitle && (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Text Shadow</Text>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>{t('player_ui.text_shadow')}</Text>
|
||||
<TouchableOpacity onPress={() => setSubtitleTextShadow(!subtitleTextShadow)} style={{ paddingHorizontal: 10, paddingVertical: 8, borderRadius: 10, backgroundColor: subtitleTextShadow ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)', alignItems: 'center' }}>
|
||||
<Text style={{ color: '#fff', fontWeight: '700' }}>{subtitleTextShadow ? 'On' : 'Off'}</Text>
|
||||
<Text style={{ color: '#fff', fontWeight: '700' }}>{subtitleTextShadow ? t('player_ui.on') : t('player_ui.off')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
{!isUsingInternalSubtitle && (
|
||||
<>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white' }}>Outline Color</Text>
|
||||
<Text style={{ color: 'white' }}>{t('player_ui.outline_color')}</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||
{['#000000', '#FFFFFF', '#00E5FF', '#FF5C5C'].map(c => (
|
||||
<TouchableOpacity key={c} onPress={() => setSubtitleOutlineColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 2, borderColor: subtitleOutlineColor === c ? '#fff' : 'rgba(255,255,255,0.3)' }} />
|
||||
|
|
@ -427,7 +429,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</View>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white' }}>Outline Width</Text>
|
||||
<Text style={{ color: 'white' }}>{t('player_ui.outline_width')}</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleOutlineWidth(Math.max(0, subtitleOutlineWidth - 1))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
|
|
@ -445,7 +447,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{!isUsingInternalSubtitle && (
|
||||
<View style={{ flexDirection: isCompact ? 'column' : 'row', justifyContent: 'space-between', gap: 12 }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Letter Spacing</Text>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>{t('player_ui.letter_spacing')}</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleLetterSpacing(Math.max(0, +(subtitleLetterSpacing - 0.5).toFixed(1)))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
|
|
@ -459,7 +461,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</View>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Line Height</Text>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>{t('player_ui.line_height')}</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleLineHeightMultiplier(Math.max(1, +(subtitleLineHeightMultiplier - 0.1).toFixed(1)))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
|
|
@ -478,7 +480,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
{!isExoPlayerInternal && (
|
||||
<View style={{ marginTop: 4 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Timing Offset (s)</Text>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>{t('player_ui.timing_offset')}</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleOffsetSec(+(subtitleOffsetSec - 0.1).toFixed(1))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
|
|
@ -511,10 +513,10 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
}}
|
||||
>
|
||||
<MaterialIcons name="sync" color="#fff" size={18} style={{ marginRight: 8 }} />
|
||||
<Text style={{ color: '#fff', fontWeight: '600', fontSize: 14 }}>Visual Sync</Text>
|
||||
<Text style={{ color: '#fff', fontWeight: '600', fontSize: 14 }}>{t('player_ui.visual_sync')}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<Text style={{ color: 'rgba(255,255,255,0.6)', fontSize: 11, marginTop: 6 }}>Nudge subtitles earlier (-) or later (+) to sync if needed.</Text>
|
||||
<Text style={{ color: 'rgba(255,255,255,0.6)', fontSize: 11, marginTop: 6 }}>{t('player_ui.timing_hint')}</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={{ alignItems: 'flex-end', marginTop: 8 }}>
|
||||
|
|
@ -527,7 +529,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
}}
|
||||
style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 8, backgroundColor: 'rgba(255,255,255,0.1)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)' }}
|
||||
>
|
||||
<Text style={{ color: '#fff', fontWeight: '600', fontSize: isCompact ? 12 : 14 }}>Reset to defaults</Text>
|
||||
<Text style={{ color: '#fff', fontWeight: '600', fontSize: isCompact ? 12 : 14 }}>{t('player_ui.reset_defaults')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -255,7 +255,9 @@
|
|||
"added_to_collection_hero": "Added to Collection",
|
||||
"added_to_collection_desc_hero": "Added to your Trakt collection",
|
||||
"removed_from_collection_hero": "Removed from Collection",
|
||||
"removed_from_collection_desc_hero": "Removed from your Trakt collection"
|
||||
"removed_from_collection_desc_hero": "Removed from your Trakt collection",
|
||||
"mark_as_watched": "Mark as Watched",
|
||||
"mark_as_unwatched": "Mark as Unwatched"
|
||||
},
|
||||
"cast": {
|
||||
"biography": "Biography",
|
||||
|
|
@ -315,6 +317,64 @@
|
|||
"starting_best_stream": "Starting best stream...",
|
||||
"loading_more_sources": "Loading more sources..."
|
||||
},
|
||||
"player_ui": {
|
||||
"via": "via {{name}}",
|
||||
"audio_tracks": "Audio Tracks",
|
||||
"no_audio_tracks": "No audio tracks available",
|
||||
"playback_speed": "Playback Speed",
|
||||
"on_hold": "On Hold",
|
||||
"playback_error": "Playback Error",
|
||||
"unknown_error": "An unknown error occurred during playback.",
|
||||
"copy_error": "Copy error details",
|
||||
"copied_to_clipboard": "Copied to clipboard",
|
||||
"dismiss": "Dismiss",
|
||||
"continue_watching": "Continue Watching",
|
||||
"start_over": "Start Over",
|
||||
"resume": "Resume",
|
||||
"change_source": "Change Source",
|
||||
"switching_source": "Switching source...",
|
||||
"no_sources_found": "No sources found",
|
||||
"sources": "Sources",
|
||||
"finding_sources": "Finding sources...",
|
||||
"unknown_source": "Unknown Source",
|
||||
"sources_limited": "Sources might be limited due to provider errors.",
|
||||
"episodes": "Episodes",
|
||||
"specials": "Specials",
|
||||
"season": "Season {{season}}",
|
||||
"stream": "Stream {{number}}",
|
||||
"subtitles": "Subtitles",
|
||||
"built_in": "Built-in",
|
||||
"addons": "Addons",
|
||||
"style": "Style",
|
||||
"none": "None",
|
||||
"search_online_subtitles": "Search Online Subtitles",
|
||||
"preview": "Preview",
|
||||
"quick_presets": "Quick Presets",
|
||||
"default": "Default",
|
||||
"yellow": "Yellow",
|
||||
"high_contrast": "High Contrast",
|
||||
"large": "Large",
|
||||
"core": "Core",
|
||||
"font_size": "Font Size",
|
||||
"show_background": "Show Background",
|
||||
"advanced": "Advanced",
|
||||
"position": "Position",
|
||||
"text_color": "Text Color",
|
||||
"align": "Align",
|
||||
"bottom_offset": "Bottom Offset",
|
||||
"background_opacity": "Background Opacity",
|
||||
"text_shadow": "Text Shadow",
|
||||
"on": "On",
|
||||
"off": "Off",
|
||||
"outline_color": "Outline Color",
|
||||
"outline_width": "Outline Width",
|
||||
"letter_spacing": "Letter Spacing",
|
||||
"line_height": "Line Height",
|
||||
"timing_offset": "Timing Offset (s)",
|
||||
"visual_sync": "Visual Sync",
|
||||
"timing_hint": "Nudge subtitles earlier (-) or later (+) to sync if needed.",
|
||||
"reset_defaults": "Reset to defaults"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Downloads",
|
||||
"no_downloads": "No Downloads Yet",
|
||||
|
|
|
|||
|
|
@ -255,7 +255,9 @@
|
|||
"added_to_collection_hero": "Adicionado à Coleção",
|
||||
"added_to_collection_desc_hero": "Adicionado à sua coleção Trakt",
|
||||
"removed_from_collection_hero": "Removido da Coleção",
|
||||
"removed_from_collection_desc_hero": "Removido da sua coleção Trakt"
|
||||
"removed_from_collection_desc_hero": "Removido da sua coleção Trakt",
|
||||
"mark_as_watched": "Marcar como Assistido",
|
||||
"mark_as_unwatched": "Marcar como Não Assistido"
|
||||
},
|
||||
"cast": {
|
||||
"biography": "Biografia",
|
||||
|
|
@ -315,6 +317,64 @@
|
|||
"starting_best_stream": "Iniciando melhor stream...",
|
||||
"loading_more_sources": "Carregando mais fontes..."
|
||||
},
|
||||
"player_ui": {
|
||||
"via": "via {{name}}",
|
||||
"audio_tracks": "Faixas de Áudio",
|
||||
"no_audio_tracks": "Nenhuma faixa de áudio disponível",
|
||||
"playback_speed": "Velocidade de Reprodução",
|
||||
"on_hold": "Ao Segurar",
|
||||
"playback_error": "Erro de Reprodução",
|
||||
"unknown_error": "Ocorreu um erro desconhecido durante a reprodução.",
|
||||
"copy_error": "Copiar detalhes do erro",
|
||||
"copied_to_clipboard": "Copiado para a área de transferência",
|
||||
"dismiss": "Dispensar",
|
||||
"continue_watching": "Continuar Assistindo",
|
||||
"start_over": "Recomeçar",
|
||||
"resume": "Continuar",
|
||||
"change_source": "Mudar Fonte",
|
||||
"switching_source": "Trocando fonte...",
|
||||
"no_sources_found": "Nenhuma fonte encontrada",
|
||||
"sources": "Fontes",
|
||||
"finding_sources": "Procurando fontes...",
|
||||
"unknown_source": "Fonte Desconhecida",
|
||||
"sources_limited": "As fontes podem ser limitadas devido a erros do provedor.",
|
||||
"episodes": "Episódios",
|
||||
"specials": "Especiais",
|
||||
"season": "Temporada {{season}}",
|
||||
"stream": "Stream {{number}}",
|
||||
"subtitles": "Legendas",
|
||||
"built_in": "Integradas",
|
||||
"addons": "Addons",
|
||||
"style": "Estilo",
|
||||
"none": "Nenhuma",
|
||||
"search_online_subtitles": "Buscar Legendas Online",
|
||||
"preview": "Prévia",
|
||||
"quick_presets": "Predefinições Rápidas",
|
||||
"default": "Padrão",
|
||||
"yellow": "Amarelo",
|
||||
"high_contrast": "Alto Contraste",
|
||||
"large": "Grande",
|
||||
"core": "Principal",
|
||||
"font_size": "Tamanho da Fonte",
|
||||
"show_background": "Mostrar Fundo",
|
||||
"advanced": "Avançado",
|
||||
"position": "Posição",
|
||||
"text_color": "Cor do Texto",
|
||||
"align": "Alinhamento",
|
||||
"bottom_offset": "Deslocamento Inferior",
|
||||
"background_opacity": "Opacidade do Fundo",
|
||||
"text_shadow": "Sombra do Texto",
|
||||
"on": "Ligado",
|
||||
"off": "Desligado",
|
||||
"outline_color": "Cor do Contorno",
|
||||
"outline_width": "Largura do Contorno",
|
||||
"letter_spacing": "Espaçamento de Letras",
|
||||
"line_height": "Altura da Linha",
|
||||
"timing_offset": "Ajuste de Tempo (s)",
|
||||
"visual_sync": "Sincronização Visual",
|
||||
"timing_hint": "Ajuste as legendas para antes (-) ou depois (+) para sincronizar.",
|
||||
"reset_defaults": "Redefinir padrões"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Downloads",
|
||||
"no_downloads": "Nenhum Download Ainda",
|
||||
|
|
|
|||
Loading…
Reference in a new issue