From ea25526ded927d3dbe7b7eef7c62bef6a0d17f03 Mon Sep 17 00:00:00 2001 From: AdityasahuX07 Date: Fri, 19 Dec 2025 19:45:46 +0530 Subject: [PATCH] Update Ui of Subtitle selection tab made modern popup window --- .../player/modals/SubtitleModals.tsx | 1178 +++++------------ 1 file changed, 363 insertions(+), 815 deletions(-) diff --git a/src/components/player/modals/SubtitleModals.tsx b/src/components/player/modals/SubtitleModals.tsx index b6713d4..da5f5b5 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;