diff --git a/src/components/player/modals/SubtitleModals.tsx b/src/components/player/modals/SubtitleModals.tsx
index b6713d4d..da5f5b5e 100644
--- a/src/components/player/modals/SubtitleModals.tsx
+++ b/src/components/player/modals/SubtitleModals.tsx
@@ -1,16 +1,16 @@
import React from 'react';
-import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Platform, useWindowDimensions } from 'react-native';
+import { View, Text, TouchableOpacity, ScrollView, Platform, useWindowDimensions, StyleSheet } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
-import Animated, {
- FadeIn,
+import Animated, {
+ FadeIn,
FadeOut,
- SlideInRight,
- SlideOutRight,
+ SlideInDown,
+ SlideOutDown,
+ useAnimatedStyle,
+ withTiming,
} from 'react-native-reanimated';
-import { styles } from '../utils/playerStyles';
import { WyzieSubtitle, SubtitleCue } from '../utils/playerTypes';
import { getTrackDisplayName, formatLanguage } from '../utils/playerUtils';
-import { useSafeAreaInsets } from 'react-native-safe-area-context';
interface SubtitleModalsProps {
showSubtitleModal: boolean;
@@ -24,7 +24,6 @@ interface SubtitleModalsProps {
ksTextTracks: Array<{id: number, name: string, language?: string}>;
selectedTextTrack: number;
useCustomSubtitles: boolean;
- // When true, KSPlayer is active (iOS MKV path). Use to gate iOS-only limitations.
isKsPlayerActive?: boolean;
subtitleSize: number;
subtitleBackground: boolean;
@@ -35,7 +34,6 @@ interface SubtitleModalsProps {
increaseSubtitleSize: () => void;
decreaseSubtitleSize: () => void;
toggleSubtitleBackground: () => void;
- // Customization props
subtitleTextColor: string;
setSubtitleTextColor: (c: string) => void;
subtitleBgOpacity: number;
@@ -60,857 +58,407 @@ interface SubtitleModalsProps {
setSubtitleOffsetSec: (n: number) => void;
}
-// Dynamic sizing handled inside component with useWindowDimensions
+const MorphingTab = ({ label, isSelected, onPress }: any) => {
+ const animatedStyle = useAnimatedStyle(() => ({
+ borderRadius: withTiming(isSelected ? 10 : 40, { duration: 250 }),
+ backgroundColor: withTiming(isSelected ? 'white' : 'rgba(255,255,255,0.06)', { duration: 250 }),
+ }));
+
+ return (
+
+
+
+ {label}
+
+
+
+ );
+};
export const SubtitleModals: React.FC = ({
- showSubtitleModal,
- setShowSubtitleModal,
- showSubtitleLanguageModal,
- setShowSubtitleLanguageModal,
- isLoadingSubtitleList,
- isLoadingSubtitles,
- customSubtitles,
- availableSubtitles,
- ksTextTracks,
- selectedTextTrack,
- useCustomSubtitles,
- isKsPlayerActive,
- subtitleSize,
- subtitleBackground,
- fetchAvailableSubtitles,
- loadWyzieSubtitle,
- selectTextTrack,
- disableCustomSubtitles,
- increaseSubtitleSize,
- decreaseSubtitleSize,
- toggleSubtitleBackground,
- subtitleTextColor,
- setSubtitleTextColor,
- subtitleBgOpacity,
- setSubtitleBgOpacity,
- subtitleTextShadow,
- setSubtitleTextShadow,
- subtitleOutline,
- setSubtitleOutline,
- subtitleOutlineColor,
- setSubtitleOutlineColor,
- subtitleOutlineWidth,
- setSubtitleOutlineWidth,
- subtitleAlign,
- setSubtitleAlign,
- subtitleBottomOffset,
- setSubtitleBottomOffset,
- subtitleLetterSpacing,
- setSubtitleLetterSpacing,
- subtitleLineHeightMultiplier,
- setSubtitleLineHeightMultiplier,
- subtitleOffsetSec,
- setSubtitleOffsetSec,
+ showSubtitleModal, setShowSubtitleModal, isLoadingSubtitleList, isLoadingSubtitles,
+ availableSubtitles, ksTextTracks, selectedTextTrack, useCustomSubtitles,
+ subtitleSize, subtitleBackground, fetchAvailableSubtitles,
+ loadWyzieSubtitle, selectTextTrack, increaseSubtitleSize,
+ decreaseSubtitleSize, toggleSubtitleBackground, subtitleTextColor, setSubtitleTextColor,
+ subtitleBgOpacity, setSubtitleBgOpacity, subtitleTextShadow, setSubtitleTextShadow,
+ subtitleOutline, setSubtitleOutline, subtitleOutlineColor, setSubtitleOutlineColor,
+ subtitleOutlineWidth, setSubtitleOutlineWidth, subtitleAlign, setSubtitleAlign,
+ subtitleBottomOffset, setSubtitleBottomOffset, subtitleLetterSpacing, setSubtitleLetterSpacing,
+ subtitleLineHeightMultiplier, setSubtitleLineHeightMultiplier, subtitleOffsetSec, setSubtitleOffsetSec,
}) => {
- const insets = useSafeAreaInsets();
const { width, height } = useWindowDimensions();
const isIos = Platform.OS === 'ios';
const isLandscape = width > height;
- // Track which specific addon subtitle is currently loaded
const [selectedOnlineSubtitleId, setSelectedOnlineSubtitleId] = React.useState(null);
- // Track which addon subtitle is currently loading to show spinner per-item
- const [loadingSubtitleId, setLoadingSubtitleId] = React.useState(null);
- // Active tab for better organization
- const [activeTab, setActiveTab] = React.useState<'built-in' | 'addon' | 'appearance'>(useCustomSubtitles ? 'addon' : 'built-in');
- // Responsive tuning
+ const [activeTab, setActiveTab] = React.useState<'built-in' | 'addon' | 'appearance'>('built-in');
+
const isCompact = width < 360 || height < 640;
const sectionPad = isCompact ? 12 : 16;
const chipPadH = isCompact ? 8 : 12;
const chipPadV = isCompact ? 6 : 8;
const controlBtn = { size: isCompact ? 28 : 32, radius: isCompact ? 14 : 16 };
const previewHeight = isCompact ? 90 : (isIos && isLandscape ? 100 : 120);
- const menuWidth = Math.min(
- width * (isIos ? (isLandscape ? 0.6 : 0.8) : 0.85),
- isIos ? 420 : 400
- );
-
+
+ const menuWidth = Math.min(width * 0.9, 420);
+ const menuMaxHeight = height * 0.95;
+
React.useEffect(() => {
- if (showSubtitleModal && !isLoadingSubtitleList && availableSubtitles.length === 0) {
- fetchAvailableSubtitles();
- }
+ if (showSubtitleModal && !isLoadingSubtitleList && availableSubtitles.length === 0) fetchAvailableSubtitles();
}, [showSubtitleModal]);
- // Reset selected addon subtitle when switching to built-in tracks
- React.useEffect(() => {
- if (!useCustomSubtitles) {
- setSelectedOnlineSubtitleId(null);
- }
- }, [useCustomSubtitles]);
+ const handleClose = () => setShowSubtitleModal(false);
- // Clear loading state when subtitles have finished loading
- React.useEffect(() => {
- if (!isLoadingSubtitles) {
- setLoadingSubtitleId(null);
- }
- }, [isLoadingSubtitles]);
+ if (!showSubtitleModal) return null;
- // Keep tab in sync with current usage
- React.useEffect(() => {
- setActiveTab(useCustomSubtitles ? 'addon' : 'built-in');
- }, [useCustomSubtitles]);
+ return (
+
+ {/* Backdrop */}
+
+
+
- const handleClose = () => {
- setShowSubtitleModal(false);
- };
-
- const handleLoadWyzieSubtitle = (subtitle: WyzieSubtitle) => {
- setSelectedOnlineSubtitleId(subtitle.id);
- setLoadingSubtitleId(subtitle.id);
- loadWyzieSubtitle(subtitle);
- };
-
- const getFileNameFromUrl = (url?: string): string | null => {
- if (!url || typeof url !== 'string') return null;
- try {
- // Prefer URL parsing to safely strip query/hash
- const u = new URL(url);
- const raw = u.pathname.split('/').pop() || '';
- const decoded = decodeURIComponent(raw);
- return decoded || null;
- } catch {
- // Fallback for non-standard URLs
- const path = url.split('?')[0].split('#')[0];
- const raw = path.split('/').pop() || '';
- try { return decodeURIComponent(raw) || null; } catch { return raw || null; }
- }
- };
-
- // Main subtitle menu
- const renderSubtitleMenu = () => {
- if (!showSubtitleModal) return null;
-
- return (
- <>
- {/* Backdrop */}
-
-
-
-
- {/* Side Menu */}
+ {/* Centered Modal Container */}
+
{/* Header */}
-
-
- Subtitles
-
-
- {useCustomSubtitles ? 'Addon in use' : 'Built‑in in use'}
-
-
-
-
-
-
+
+ Subtitles
- {/* Segmented Tabs */}
-
- {([
- { key: 'built-in', label: 'Built‑in' },
- { key: 'addon', label: 'Addons' },
- { key: 'appearance', label: 'Appearance' },
- ] as const).map(tab => (
- setActiveTab(tab.key)}
- style={{
- paddingHorizontal: chipPadH,
- paddingVertical: chipPadV,
- borderRadius: 16,
- backgroundColor: activeTab === tab.key ? 'rgba(255,255,255,0.15)' : 'rgba(255,255,255,0.06)',
- borderWidth: 1,
- borderColor: activeTab === tab.key ? 'rgba(255,255,255,0.3)' : 'rgba(255,255,255,0.1)'
- }}
- >
- {tab.label}
-
- ))}
+ {/* Tab Bar */}
+
+ setActiveTab('built-in')} />
+ setActiveTab('addon')} />
+ setActiveTab('appearance')} />
-
- {activeTab === 'built-in' && (
-
-
- Built-in Subtitles
-
+
+
+ {activeTab === 'built-in' && (
+
+ { selectTextTrack(-1); setSelectedOnlineSubtitleId(null); }}
+ style={{ padding: 10, borderRadius: 12, backgroundColor: selectedTextTrack === -1 ? 'white' : 'rgba(242, 184, 181)' }}
+ >
+ None
+
+ {ksTextTracks.map((track) => (
+ { selectTextTrack(track.id); setSelectedOnlineSubtitleId(null); }}
+ style={{ padding: 10, borderRadius: 12, backgroundColor: selectedTextTrack === track.id ? 'white' : 'rgba(255,255,255,0.05)', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}
+ >
+ {getTrackDisplayName(track)}
+ {selectedTextTrack === track.id && }
+
+ ))}
+
+ )}
- {/* Built-in subtitles now enabled for KSPlayer */}
- {isKsPlayerActive && (
-
-
-
-
-
+ {availableSubtitles.length === 0 ? (
+
+
+ Search Online Subtitles
+
+ ) : (
+ availableSubtitles.map((sub) => (
+ { setSelectedOnlineSubtitleId(sub.id); loadWyzieSubtitle(sub); }}
+ style={{ padding: 6, borderRadius: 12, backgroundColor: selectedOnlineSubtitleId === sub.id ? 'white' : 'rgba(255,255,255,0.05)', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}
+ >
+
+ {sub.display}
+ {formatLanguage(sub.language)}
+
+
+ ))
+ )}
+
+ )}
+
+ {activeTab === 'appearance' && (
+
+ {/* Live Preview */}
+
+
+
+ Preview
+
+
+
+
- Built-in subtitles enabled for KSPlayer
-
-
- KSPlayer built-in subtitle rendering is now available. You can select from embedded subtitle tracks below.
-
+
+ The quick brown fox jumps over the lazy dog.
+
+
- )}
- {/* Disable Subtitles Button */}
- {
- selectTextTrack(-1);
- setSelectedOnlineSubtitleId(null);
- }}
- activeOpacity={0.7}
- >
-
-
- Disable All Subtitles
-
- {selectedTextTrack === -1 && (
-
- )}
-
-
-
- {/* Always show built-in subtitles */}
- {ksTextTracks.length > 0 && (
-
- {ksTextTracks.map((track) => {
- const isSelected = selectedTextTrack === track.id && !useCustomSubtitles;
- return (
- {
- selectTextTrack(track.id);
- setSelectedOnlineSubtitleId(null);
- }}
- activeOpacity={0.7}
- >
-
-
- {getTrackDisplayName(track)}
-
- {isSelected && (
-
- )}
-
-
- );
- })}
-
- )}
-
- )}
-
- {activeTab === 'addon' && (
-
-
-
- Addon Subtitles
-
-
- {useCustomSubtitles && (
- {
- disableCustomSubtitles();
- setSelectedOnlineSubtitleId(null);
- }}
- activeOpacity={0.7}
- >
-
-
- Disable
-
-
- )}
- fetchAvailableSubtitles()}
- disabled={isLoadingSubtitleList}
- >
- {isLoadingSubtitleList ? (
-
- ) : (
-
- )}
-
- {isLoadingSubtitleList ? 'Searching' : 'Refresh'}
-
-
-
-
-
- {(availableSubtitles.length === 0) && !isLoadingSubtitleList ? (
- fetchAvailableSubtitles()}
- activeOpacity={0.7}
- >
-
-
- Tap to fetch from addons
-
-
- ) : isLoadingSubtitleList ? (
-
-
-
- Searching...
-
-
- ) : (
-
- {availableSubtitles.map((sub) => {
- const isSelected = useCustomSubtitles && selectedOnlineSubtitleId === sub.id;
- return (
+ {/* Quick Presets */}
+
+
+
+ Quick Presets
+
+
{
- handleLoadWyzieSubtitle(sub);
+ setSubtitleTextColor('#FFFFFF'); setSubtitleBgOpacity(0.7); setSubtitleTextShadow(true);
+ setSubtitleOutline(true); setSubtitleOutlineColor('#000000'); setSubtitleOutlineWidth(4);
+ setSubtitleAlign('center'); setSubtitleBottomOffset(10); setSubtitleLetterSpacing(0);
+ setSubtitleLineHeightMultiplier(1.2);
}}
- activeOpacity={0.7}
- disabled={isLoadingSubtitles}
+ style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)' }}
>
-
-
-
- {sub.display}
-
- {(() => {
- const filename = getFileNameFromUrl(sub.url);
- if (!filename) return null;
- return (
-
- {filename}
-
- );
- })()}
-
- {formatLanguage(sub.language)}{sub.source ? ` · ${sub.source}` : ''}
-
-
- {(isLoadingSubtitles && loadingSubtitleId === sub.id) ? (
-
- ) : isSelected ? (
-
- ) : (
-
- )}
-
+ Default
- );
- })}
+ {
+ setSubtitleTextColor('#FFD700'); setSubtitleOutline(true); setSubtitleOutlineColor('#000000'); setSubtitleOutlineWidth(4); setSubtitleBgOpacity(0.3); setSubtitleTextShadow(false);
+ }}
+ style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(255,215,0,0.12)', borderWidth: 1, borderColor: 'rgba(255,215,0,0.35)' }}
+ >
+ Yellow
+
+ {
+ setSubtitleTextColor('#FFFFFF'); setSubtitleOutline(true); setSubtitleOutlineColor('#000000'); setSubtitleOutlineWidth(3); setSubtitleBgOpacity(0.0); setSubtitleTextShadow(false); setSubtitleLetterSpacing(0.5);
+ }}
+ style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(34,197,94,0.12)', borderWidth: 1, borderColor: 'rgba(34,197,94,0.35)' }}
+ >
+ High Contrast
+
+ {
+ setSubtitleTextColor('#FFFFFF'); setSubtitleBgOpacity(0.6); setSubtitleTextShadow(true); setSubtitleOutline(true); setSubtitleAlign('center'); setSubtitleLineHeightMultiplier(1.3);
+ }}
+ style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(59,130,246,0.12)', borderWidth: 1, borderColor: 'rgba(59,130,246,0.35)' }}
+ >
+ Large
+
+
+
+
+ {/* Core controls */}
+
+
+
+ Core
+
+
+
+
+ Font Size
+
+
+
+
+
+
+ {subtitleSize}
+
+
+
+
+
+
+
+
+
+ Show Background
+
+
+
+
+
+
+
+ {/* Advanced controls */}
+
+
+
+ Advanced
+
+
+
+
+ Text Color
+
+
+ {['#FFFFFF', '#FFD700', '#00E5FF', '#FF5C5C', '#00FF88', '#9b59b6', '#f97316'].map(c => (
+ setSubtitleTextColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 2, borderColor: subtitleTextColor === c ? '#fff' : 'rgba(255,255,255,0.3)' }} />
+ ))}
+
+
+
+ Align
+
+ {([ { key: 'left', icon: 'format-align-left' }, { key: 'center', icon: 'format-align-center' }, { key: 'right', icon: 'format-align-right' } ] as const).map(a => (
+ 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)' }}>
+
+
+ ))}
+
+
+
+ Bottom Offset
+
+ 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' }}>
+
+
+
+ {subtitleBottomOffset}
+
+ setSubtitleBottomOffset(subtitleBottomOffset + 5)} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
+
+
+
+
+
+ Background Opacity
+
+ 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' }}>
+
+
+
+ {subtitleBgOpacity.toFixed(1)}
+
+ setSubtitleBgOpacity(Math.min(1, +(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' }}>
+
+
+
+
+
+ Text Shadow
+ 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' }}>
+ {subtitleTextShadow ? 'On' : 'Off'}
+
+
+
+ Outline Color
+
+ {['#000000', '#FFFFFF', '#00E5FF', '#FF5C5C'].map(c => (
+ setSubtitleOutlineColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 2, borderColor: subtitleOutlineColor === c ? '#fff' : 'rgba(255,255,255,0.3)' }} />
+ ))}
+
+
+
+ Outline Width
+
+ 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' }}>
+
+
+
+ {subtitleOutlineWidth}
+
+ setSubtitleOutlineWidth(subtitleOutlineWidth + 1)} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
+
+
+
+
+
+
+ Letter Spacing
+
+ 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' }}>
+
+
+
+ {subtitleLetterSpacing.toFixed(1)}
+
+ setSubtitleLetterSpacing(+(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' }}>
+
+
+
+
+
+ Line Height
+
+ 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' }}>
+
+
+
+ {subtitleLineHeightMultiplier.toFixed(1)}
+
+ setSubtitleLineHeightMultiplier(+(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' }}>
+
+
+
+
+
+
+
+ Timing Offset (s)
+
+ 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' }}>
+
+
+
+ {subtitleOffsetSec.toFixed(1)}
+
+ 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' }}>
+
+
+
+
+ Nudge subtitles earlier (-) or later (+) to sync if needed.
+
+
+ {
+ setSubtitleTextColor('#FFFFFF'); setSubtitleBgOpacity(0.7); setSubtitleTextShadow(true);
+ setSubtitleOutline(true); setSubtitleOutlineColor('#000000'); setSubtitleOutlineWidth(4);
+ setSubtitleAlign('center'); setSubtitleBottomOffset(10); setSubtitleLetterSpacing(0);
+ setSubtitleLineHeightMultiplier(1.2); setSubtitleOffsetSec(0);
+ }}
+ style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 8, backgroundColor: 'rgba(255,255,255,0.1)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)' }}
+ >
+ Reset to defaults
+
+
+
)}
- )}
-
- {activeTab === 'appearance' && (
-
- {/* Live Preview */}
-
-
-
- Preview
-
-
-
-
-
- The quick brown fox jumps over the lazy dog.
-
-
-
-
-
-
- {/* Quick Presets */}
-
-
-
- Quick Presets
-
-
- {
- setSubtitleTextColor('#FFFFFF');
- setSubtitleBgOpacity(0.7);
- setSubtitleTextShadow(true);
- setSubtitleOutline(true);
- setSubtitleOutlineColor('#000000');
- setSubtitleOutlineWidth(4);
- setSubtitleAlign('center');
- setSubtitleBottomOffset(10);
- setSubtitleLetterSpacing(0);
- setSubtitleLineHeightMultiplier(1.2);
- }}
- style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)' }}
- >
- Default
-
- {
- setSubtitleTextColor('#FFD700');
- setSubtitleOutline(true);
- setSubtitleOutlineColor('#000000');
- setSubtitleOutlineWidth(4);
- setSubtitleBgOpacity(0.3);
- setSubtitleTextShadow(false);
- }}
- style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(255,215,0,0.12)', borderWidth: 1, borderColor: 'rgba(255,215,0,0.35)' }}
- >
- Yellow
-
- {
- setSubtitleTextColor('#FFFFFF');
- setSubtitleOutline(true);
- setSubtitleOutlineColor('#000000');
- setSubtitleOutlineWidth(3);
- setSubtitleBgOpacity(0.0);
- setSubtitleTextShadow(false);
- setSubtitleLetterSpacing(0.5);
- }}
- style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(34,197,94,0.12)', borderWidth: 1, borderColor: 'rgba(34,197,94,0.35)' }}
- >
- High Contrast
-
- {
- setSubtitleTextColor('#FFFFFF');
- setSubtitleBgOpacity(0.6);
- setSubtitleTextShadow(true);
- setSubtitleOutline(true);
- setSubtitleAlign('center');
- setSubtitleLineHeightMultiplier(1.3);
- }}
- style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 20, backgroundColor: 'rgba(59,130,246,0.12)', borderWidth: 1, borderColor: 'rgba(59,130,246,0.35)' }}
- >
- Large
-
-
-
-
- {/* Core controls */}
-
-
-
- Core
-
- {/* Font Size */}
-
-
-
- Font Size
-
-
-
-
-
-
- {subtitleSize}
-
-
-
-
-
-
- {/* Background toggle */}
-
-
-
- Show Background
-
-
-
-
-
-
-
- {/* Advanced controls */}
-
-
-
- Advanced
-
-
- {/* Text Color */}
-
-
-
- Text Color
-
-
- {['#FFFFFF', '#FFD700', '#00E5FF', '#FF5C5C', '#00FF88', '#9b59b6', '#f97316'].map(c => (
- setSubtitleTextColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 2, borderColor: subtitleTextColor === c ? '#fff' : 'rgba(255,255,255,0.3)' }} />
- ))}
-
-
-
- {/* Align */}
-
- Align
-
- {([
- { key: 'left', icon: 'format-align-left' },
- { key: 'center', icon: 'format-align-center' },
- { key: 'right', icon: 'format-align-right' },
- ] as const).map(a => (
- 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)' }}
- >
-
-
- ))}
-
-
-
- {/* Bottom Offset */}
-
- Bottom Offset
-
- 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' }}>
-
-
-
- {subtitleBottomOffset}
-
- setSubtitleBottomOffset(subtitleBottomOffset + 5)} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
-
-
-
-
-
- {/* Background Opacity */}
-
- Background Opacity
-
- 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' }}>
-
-
-
- {subtitleBgOpacity.toFixed(1)}
-
- setSubtitleBgOpacity(Math.min(1, +(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' }}>
-
-
-
-
-
- {/* Shadow */}
-
- Text Shadow
- 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' }}>
- {subtitleTextShadow ? 'On' : 'Off'}
-
-
- {/* Outline color & width */}
-
- Outline Color
-
- {['#000000', '#FFFFFF', '#00E5FF', '#FF5C5C'].map(c => (
- setSubtitleOutlineColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 2, borderColor: subtitleOutlineColor === c ? '#fff' : 'rgba(255,255,255,0.3)' }} />
- ))}
-
-
-
- Outline Width
-
- 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' }}>
-
-
-
- {subtitleOutlineWidth}
-
- setSubtitleOutlineWidth(subtitleOutlineWidth + 1)} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
-
-
-
-
-
- {/* Spacing (two columns) */}
-
-
- Letter Spacing
-
- 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' }}>
-
-
-
- {subtitleLetterSpacing.toFixed(1)}
-
- setSubtitleLetterSpacing(+(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' }}>
-
-
-
-
-
- Line Height
-
- 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' }}>
-
-
-
- {subtitleLineHeightMultiplier.toFixed(1)}
-
- setSubtitleLineHeightMultiplier(+(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' }}>
-
-
-
-
-
-
- {/* Timing Offset */}
-
-
- Timing Offset (s)
-
- 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' }}>
-
-
-
- {subtitleOffsetSec.toFixed(1)}
-
- 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' }}>
-
-
-
-
- Nudge subtitles earlier (-) or later (+) to sync if needed.
-
-
- {/* Reset to defaults */}
-
- {
- setSubtitleTextColor('#FFFFFF');
- setSubtitleBgOpacity(0.7);
- setSubtitleTextShadow(true);
- setSubtitleOutline(true);
- setSubtitleOutlineColor('#000000');
- setSubtitleOutlineWidth(4);
- setSubtitleAlign('center');
- setSubtitleBottomOffset(10);
- setSubtitleLetterSpacing(0);
- setSubtitleLineHeightMultiplier(1.2);
- setSubtitleOffsetSec(0);
- }}
- style={{ paddingHorizontal: chipPadH, paddingVertical: chipPadV, borderRadius: 8, backgroundColor: 'rgba(255,255,255,0.1)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)' }}
- >
- Reset to defaults
-
-
-
-
- )}
-
- >
- );
- };
-
- return (
- <>
- {renderSubtitleMenu()}
- >
+
+
);
};
-export default SubtitleModals;
\ No newline at end of file
+export default SubtitleModals;