import React, { useState, useEffect, useRef, useCallback } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, TextInput, SafeAreaView, StatusBar, Platform, ActivityIndicator, Linking, ScrollView, Keyboard, Clipboard, Switch, KeyboardAvoidingView, TouchableWithoutFeedback, Modal, } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import { mmkvStorage } from '../services/mmkvStorage'; import FastImage from '@d11/react-native-fast-image'; import { tmdbService } from '../services/tmdbService'; import { useSettings } from '../hooks/useSettings'; import { logger } from '../utils/logger'; import { useTheme } from '../contexts/ThemeContext'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import CustomAlert from '../components/CustomAlert'; import { useTranslation } from 'react-i18next'; // (duplicate import removed) const TMDB_API_KEY_STORAGE_KEY = 'tmdb_api_key'; const USE_CUSTOM_TMDB_API_KEY = 'use_custom_tmdb_api_key'; const TMDB_API_KEY = '439c478a771f35c05022f9feabcca01c'; // Define example shows with their IMDB IDs and TMDB IDs const EXAMPLE_SHOWS = [ { name: 'Breaking Bad', imdbId: 'tt0903747', tmdbId: '1396', type: 'tv' as const }, { name: 'Friends', imdbId: 'tt0108778', tmdbId: '1668', type: 'tv' as const }, { name: 'Stranger Things', imdbId: 'tt4574334', tmdbId: '66732', type: 'tv' as const }, { name: 'Avatar', imdbId: 'tt0499549', tmdbId: '19995', type: 'movie' as const }, ]; const TMDBSettingsScreen = () => { const { t } = useTranslation(); const navigation = useNavigation(); const [apiKey, setApiKey] = useState(''); const [isLoading, setIsLoading] = useState(true); const [isKeySet, setIsKeySet] = useState(false); const [useCustomKey, setUseCustomKey] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const [isInputFocused, setIsInputFocused] = useState(false); const [alertVisible, setAlertVisible] = useState(false); const [alertTitle, setAlertTitle] = useState(''); const [alertMessage, setAlertMessage] = useState(''); const [alertActions, setAlertActions] = useState void; style?: object }>>([ { label: t('common.ok'), onPress: () => setAlertVisible(false) }, ]); const apiKeyInputRef = useRef(null); const { currentTheme } = useTheme(); const insets = useSafeAreaInsets(); const { settings, updateSetting } = useSettings(); const [languagePickerVisible, setLanguagePickerVisible] = useState(false); const [languageSearch, setLanguageSearch] = useState(''); // Logo preview state const [selectedShow, setSelectedShow] = useState(EXAMPLE_SHOWS[0]); const [tmdbLogo, setTmdbLogo] = useState(null); const [tmdbBanner, setTmdbBanner] = useState(null); const [loadingLogos, setLoadingLogos] = useState(true); const [previewLanguage, setPreviewLanguage] = useState(''); const [isPreviewFallback, setIsPreviewFallback] = useState(false); const [cacheSize, setCacheSize] = useState('0 KB'); const openAlert = ( title: string, message: string, actions?: Array<{ label: string; onPress?: () => void; style?: object }> ) => { setAlertTitle(title); setAlertMessage(message); if (actions && actions.length > 0) { setAlertActions( actions.map(a => ({ label: a.label, style: a.style, onPress: () => { a.onPress?.(); }, })) ); } else { setAlertActions([{ label: t('common.ok'), onPress: () => setAlertVisible(false) }]); } setAlertVisible(true); }; useEffect(() => { logger.log('[TMDBSettingsScreen] Component mounted'); loadSettings(); calculateCacheSize(); return () => { logger.log('[TMDBSettingsScreen] Component unmounted'); }; }, []); const calculateCacheSize = async () => { try { const keys = await mmkvStorage.getAllKeys(); const tmdbKeys = keys.filter(key => key.startsWith('tmdb_cache_')); let totalSize = 0; for (const key of tmdbKeys) { const value = mmkvStorage.getString(key); if (value) { totalSize += value.length; } } // Convert to KB/MB let sizeStr = ''; if (totalSize < 1024) { sizeStr = `${totalSize} B`; } else if (totalSize < 1024 * 1024) { sizeStr = `${(totalSize / 1024).toFixed(2)} KB`; } else { sizeStr = `${(totalSize / (1024 * 1024)).toFixed(2)} MB`; } setCacheSize(sizeStr); } catch (error) { logger.error('[TMDBSettingsScreen] Error calculating cache size:', error); setCacheSize('Unknown'); } }; const handleClearCache = () => { openAlert( t('tmdb_settings.clear_cache_title'), t('tmdb_settings.clear_cache_msg', { size: cacheSize }), [ { label: t('common.cancel'), onPress: () => logger.log('[TMDBSettingsScreen] Clear cache cancelled'), }, { label: t('tmdb_settings.clear_cache'), onPress: async () => { logger.log('[TMDBSettingsScreen] Proceeding with cache clear'); try { await tmdbService.clearAllCache(); setCacheSize('0 KB'); logger.log('[TMDBSettingsScreen] Cache cleared successfully'); openAlert(t('common.success'), t('tmdb_settings.clear_cache_success')); } catch (error) { logger.error('[TMDBSettingsScreen] Failed to clear cache:', error); openAlert(t('common.error'), t('tmdb_settings.clear_cache_error')); } }, }, ] ); }; const loadSettings = async () => { logger.log('[TMDBSettingsScreen] Loading settings from storage'); try { const [savedKey, savedUseCustomKey] = await Promise.all([ mmkvStorage.getItem(TMDB_API_KEY_STORAGE_KEY), mmkvStorage.getItem(USE_CUSTOM_TMDB_API_KEY) ]); logger.log('[TMDBSettingsScreen] API key status:', savedKey ? 'Found' : 'Not found'); logger.log('[TMDBSettingsScreen] Use custom API setting:', savedUseCustomKey); if (savedKey) { setApiKey(savedKey); setIsKeySet(true); } else { setIsKeySet(false); } setUseCustomKey(savedUseCustomKey === 'true'); } catch (error) { logger.error('[TMDBSettingsScreen] Failed to load settings:', error); setIsKeySet(false); setUseCustomKey(false); } finally { setIsLoading(false); logger.log('[TMDBSettingsScreen] Finished loading settings'); } }; const saveApiKey = async () => { logger.log('[TMDBSettingsScreen] Starting API key save'); Keyboard.dismiss(); try { const trimmedKey = apiKey.trim(); if (!trimmedKey) { logger.warn('[TMDBSettingsScreen] Empty API key provided'); setTestResult({ success: false, message: t('tmdb_settings.empty_api_key') }); return; } // Test the API key to make sure it works if (await testApiKey(trimmedKey)) { logger.log('[TMDBSettingsScreen] API key test successful, saving key'); await mmkvStorage.setItem(TMDB_API_KEY_STORAGE_KEY, trimmedKey); await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, 'true'); setIsKeySet(true); setUseCustomKey(true); setTestResult({ success: true, message: t('tmdb_settings.key_verified') }); logger.log('[TMDBSettingsScreen] API key saved successfully'); } else { logger.warn('[TMDBSettingsScreen] API key test failed'); setTestResult({ success: false, message: t('tmdb_settings.invalid_api_key') }); } } catch (error) { logger.error('[TMDBSettingsScreen] Error saving API key:', error); setTestResult({ success: false, message: t('tmdb_settings.save_error') }); } }; const testApiKey = async (key: string): Promise => { try { // Simple API call to test the key using the API key parameter method const response = await fetch( `https://api.themoviedb.org/3/configuration?api_key=${key}`, { method: 'GET', headers: { 'Content-Type': 'application/json', } } ); return response.ok; } catch (error) { logger.error('[TMDBSettingsScreen] API key test error:', error); return false; } }; const clearApiKey = async () => { logger.log('[TMDBSettingsScreen] Clear API key requested'); openAlert( t('tmdb_settings.clear_api_key_title'), t('tmdb_settings.clear_api_key_msg'), [ { label: t('common.cancel'), onPress: () => logger.log('[TMDBSettingsScreen] Clear API key cancelled'), }, { label: t('mdblist.clear'), onPress: async () => { logger.log('[TMDBSettingsScreen] Proceeding with API key clear'); try { await mmkvStorage.removeItem(TMDB_API_KEY_STORAGE_KEY); await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, 'false'); setApiKey(''); setIsKeySet(false); setUseCustomKey(false); setTestResult(null); logger.log('[TMDBSettingsScreen] API key cleared successfully'); } catch (error) { logger.error('[TMDBSettingsScreen] Failed to clear API key:', error); openAlert(t('common.error'), t('tmdb_settings.clear_api_key_error')); } }, }, ] ); }; const toggleUseCustomKey = async (value: boolean) => { logger.log('[TMDBSettingsScreen] Toggle use custom key:', value); try { await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, value ? 'true' : 'false'); setUseCustomKey(value); if (!value) { // If switching to built-in key, show confirmation logger.log('[TMDBSettingsScreen] Switching to built-in API key'); setTestResult({ success: true, message: t('tmdb_settings.using_builtin_key') }); } else if (apiKey && isKeySet) { // If switching to custom key and we have a key logger.log('[TMDBSettingsScreen] Switching to custom API key'); setTestResult({ success: true, message: t('tmdb_settings.using_custom_key') }); } else { // If switching to custom key but don't have a key yet logger.log('[TMDBSettingsScreen] No custom key available yet'); setTestResult({ success: false, message: t('tmdb_settings.enter_custom_key') }); } } catch (error) { logger.error('[TMDBSettingsScreen] Failed to toggle custom key setting:', error); } }; const pasteFromClipboard = async () => { logger.log('[TMDBSettingsScreen] Attempting to paste from clipboard'); try { const clipboardContent = await Clipboard.getString(); if (clipboardContent) { logger.log('[TMDBSettingsScreen] Content pasted from clipboard'); setApiKey(clipboardContent); setTestResult(null); } else { logger.warn('[TMDBSettingsScreen] No content in clipboard'); } } catch (error) { logger.error('[TMDBSettingsScreen] Error pasting from clipboard:', error); } }; const openTMDBWebsite = () => { logger.log('[TMDBSettingsScreen] Opening TMDb website'); Linking.openURL('https://www.themoviedb.org/settings/api').catch(error => { logger.error('[TMDBSettingsScreen] Error opening website:', error); }); }; // Logo preview functions const fetchExampleLogos = async (show: typeof EXAMPLE_SHOWS[0]) => { setLoadingLogos(true); setTmdbLogo(null); setTmdbBanner(null); try { const tmdbId = show.tmdbId; const contentType = show.type; logger.log(`[TMDBSettingsScreen] Fetching ${show.name} with TMDB ID: ${tmdbId}`); const preferredTmdbLanguage = settings.tmdbLanguagePreference || 'en'; const apiKey = TMDB_API_KEY; const endpoint = contentType === 'tv' ? 'tv' : 'movie'; const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}/images?api_key=${apiKey}`); const imagesData = await response.json(); if (imagesData.logos && imagesData.logos.length > 0) { let logoPath: string | null = null; let logoLanguage = preferredTmdbLanguage; // Try to find logo in preferred language const preferredLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === preferredTmdbLanguage); if (preferredLogo) { logoPath = preferredLogo.file_path; logoLanguage = preferredTmdbLanguage; setIsPreviewFallback(false); } else { // Fallback to English const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === 'en'); if (englishLogo) { logoPath = englishLogo.file_path; logoLanguage = 'en'; setIsPreviewFallback(true); } else if (imagesData.logos[0]) { // Fallback to first available logoPath = imagesData.logos[0].file_path; logoLanguage = imagesData.logos[0].iso_639_1 || 'unknown'; setIsPreviewFallback(true); } } if (logoPath) { setTmdbLogo(`https://image.tmdb.org/t/p/original${logoPath}`); setPreviewLanguage(logoLanguage); } else { setPreviewLanguage(''); setIsPreviewFallback(false); } } else { setPreviewLanguage(''); setIsPreviewFallback(false); } // Get TMDB banner (backdrop) if (imagesData.backdrops && imagesData.backdrops.length > 0) { const backdropPath = imagesData.backdrops[0].file_path; setTmdbBanner(`https://image.tmdb.org/t/p/original${backdropPath}`); } else { const detailsResponse = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}?api_key=${apiKey}`); const details = await detailsResponse.json(); if (details.backdrop_path) { setTmdbBanner(`https://image.tmdb.org/t/p/original${details.backdrop_path}`); } } } catch (err) { logger.error(`[TMDBSettingsScreen] Error fetching ${show.name} preview:`, err); } finally { setLoadingLogos(false); } }; const handleShowSelect = (show: typeof EXAMPLE_SHOWS[0]) => { setSelectedShow(show); try { mmkvStorage.setItem('tmdb_settings_selected_show', show.imdbId); } catch (e) { if (__DEV__) console.error('Error saving selected show:', e); } }; const renderLogoExample = (logo: string | null, banner: string | null, isLoading: boolean) => { if (isLoading) { return ( ); } return ( {logo && ( )} {!logo && ( {t('tmdb_settings.no_logo')} )} ); }; // Load example logos when show or language changes useEffect(() => { if (settings.enrichMetadataWithTMDB && settings.useTmdbLocalizedMetadata) { fetchExampleLogos(selectedShow); } }, [selectedShow, settings.enrichMetadataWithTMDB, settings.useTmdbLocalizedMetadata, settings.tmdbLanguagePreference]); // Load selected show from AsyncStorage on mount useEffect(() => { const loadSelectedShow = async () => { try { const savedShowId = await mmkvStorage.getItem('tmdb_settings_selected_show'); if (savedShowId) { const foundShow = EXAMPLE_SHOWS.find(show => show.imdbId === savedShowId); if (foundShow) { setSelectedShow(foundShow); } } } catch (e) { if (__DEV__) console.error('Error loading selected show:', e); } }; loadSelectedShow(); }, []); const headerBaseHeight = Platform.OS === 'android' ? 80 : 60; const topSpacing = Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : insets.top; const headerHeight = headerBaseHeight + topSpacing; if (isLoading) { return ( {t('common.loading')} ); } return ( navigation.goBack()} > {t('settings.settings_title')} {t('tmdb_settings.title')} {/* Metadata Enrichment Section */} {t('tmdb_settings.metadata_enrichment')} {t('tmdb_settings.metadata_enrichment_desc')} {t('tmdb_settings.enable_enrichment')} {t('tmdb_settings.enable_enrichment_desc')} updateSetting('enrichMetadataWithTMDB', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? (settings.enrichMetadataWithTMDB ? currentTheme.colors.white : currentTheme.colors.white) : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {settings.enrichMetadataWithTMDB && ( <> {t('tmdb_settings.localized_text')} {t('tmdb_settings.localized_text_desc')} updateSetting('useTmdbLocalizedMetadata', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? (settings.useTmdbLocalizedMetadata ? currentTheme.colors.white : currentTheme.colors.white) : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {settings.useTmdbLocalizedMetadata && ( <> {t('tmdb_settings.language')} Current: {(settings.tmdbLanguagePreference || 'en').toUpperCase()} setLanguagePickerVisible(true)} style={[styles.languageButton, { backgroundColor: currentTheme.colors.primary }]} > {t('tmdb_settings.change')} {/* Logo Preview */} {t('tmdb_settings.logo_preview')} {t('tmdb_settings.logo_preview_desc')} {/* Show selector */} {t('tmdb_settings.example')} {EXAMPLE_SHOWS.map((show) => ( handleShowSelect(show)} activeOpacity={0.7} > {show.name} ))} {/* Preview card */} {renderLogoExample(tmdbLogo, tmdbBanner, loadingLogos)} {tmdbLogo && ( {`Language: ${(previewLanguage || '').toUpperCase() || 'N/A'}${isPreviewFallback ? ' (fallback to available)' : ''}`} )} )} {/* Granular Enrichment Options */} {t('tmdb_settings.enrichment_options')} {t('tmdb_settings.enrichment_options_desc')} {/* Cast & Crew */} {t('tmdb_settings.cast_crew')} {t('tmdb_settings.cast_crew_desc')} updateSetting('tmdbEnrichCast', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Title & Description */} {t('tmdb_settings.title_description')} {t('tmdb_settings.title_description_desc')} updateSetting('tmdbEnrichTitleDescription', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Title Logos */} {t('tmdb_settings.title_logos')} {t('tmdb_settings.title_logos_desc')} updateSetting('tmdbEnrichLogos', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Banners/Backdrops */} {t('tmdb_settings.banners_backdrops')} {t('tmdb_settings.banners_backdrops_desc')} updateSetting('tmdbEnrichBanners', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Certification */} {t('tmdb_settings.certification')} {t('tmdb_settings.certification_desc')} updateSetting('tmdbEnrichCertification', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Recommendations */} {t('tmdb_settings.recommendations')} {t('tmdb_settings.recommendations_desc')} updateSetting('tmdbEnrichRecommendations', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Episode Data */} {t('tmdb_settings.episode_data')} {t('tmdb_settings.episode_data_desc')} updateSetting('tmdbEnrichEpisodes', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Season Posters */} {t('tmdb_settings.season_posters')} {t('tmdb_settings.season_posters_desc')} updateSetting('tmdbEnrichSeasonPosters', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Production Info */} Production Info Networks & production companies with logos updateSetting('tmdbEnrichProductionInfo', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Movie Details */} Movie Details Budget, revenue, runtime, tagline updateSetting('tmdbEnrichMovieDetails', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* TV Details */} TV Show Details Status, seasons count, networks, creators updateSetting('tmdbEnrichTvDetails', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> {/* Collections */} Movie Collections Franchise movies (Marvel, Star Wars, etc.) updateSetting('tmdbEnrichCollections', v)} trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }} thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''} ios_backgroundColor={'rgba(255,255,255,0.1)'} /> )} {/* API Configuration Section */} API Configuration Configure your TMDb API access for enhanced functionality. Custom API Key Use your own TMDb API key for better performance and dedicated rate limits. {useCustomKey && ( <> {/* API Key Status */} {isKeySet ? "Custom API key active" : "API key required"} {/* API Key Input */} { setApiKey(text); if (testResult) setTestResult(null); }} placeholder="Paste your TMDb API key (v3)" placeholderTextColor={currentTheme.colors.mediumEmphasis} autoCapitalize="none" autoCorrect={false} spellCheck={false} onFocus={() => setIsInputFocused(true)} onBlur={() => setIsInputFocused(false)} /> Save {isKeySet && ( Clear )} {testResult && ( {testResult.message} )} How to get a TMDb API key? )} {!useCustomKey && ( Currently using built-in API key. Consider using your own key for better performance. )} {/* Cache Management Section */} Cache Size {cacheSize} Clear Cache TMDB responses are cached for 7 days to improve performance {/* TMDB Attribution */} This product uses the TMDB API but is not endorsed or certified by TMDB. {/* Language Picker Modal */} setLanguagePickerVisible(false)} > setLanguagePickerVisible(false)}> {/* Header */} Choose Language Select your preferred language for TMDb content {/* Search Section */} {languageSearch.length > 0 && ( setLanguageSearch('')} style={styles.searchClearButton}> )} {/* Popular Languages */} {languageSearch.length === 0 && ( Popular {[ { code: 'en', label: 'EN' }, { code: 'ar', label: 'AR' }, { code: 'es', label: 'ES' }, { code: 'fr', label: 'FR' }, { code: 'de', label: 'DE' }, { code: 'tr', label: 'TR' }, ].map(({ code, label }) => ( { updateSetting('tmdbLanguagePreference', code); setLanguagePickerVisible(false); }} style={[ styles.popularChip, settings.tmdbLanguagePreference === code && styles.selectedChip, { backgroundColor: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.elevation1, borderColor: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : 'rgba(255,255,255,0.1)', } ]} > {label} ))} )} {/* All Languages */} 0 && styles.searchResultsTitle, { color: languageSearch.length > 0 ? currentTheme.colors.text : currentTheme.colors.mediumEmphasis } ]}> {languageSearch.length > 0 ? 'Search Results' : 'All Languages'} {(() => { const languages = [ { code: 'en', label: 'English', native: 'English' }, { code: 'ar', label: 'العربية', native: 'Arabic' }, { code: 'es', label: 'Español', native: 'Spanish' }, { code: 'fr', label: 'Français', native: 'French' }, { code: 'de', label: 'Deutsch', native: 'German' }, { code: 'it', label: 'Italiano', native: 'Italian' }, { code: 'pt', label: 'Português', native: 'Portuguese' }, { code: 'ru', label: 'Русский', native: 'Russian' }, { code: 'tr', label: 'Türkçe', native: 'Turkish' }, { code: 'ja', label: '日本語', native: 'Japanese' }, { code: 'ko', label: '한국어', native: 'Korean' }, { code: 'zh', label: '中文', native: 'Chinese' }, { code: 'hi', label: 'हिन्दी', native: 'Hindi' }, { code: 'he', label: 'עברית', native: 'Hebrew' }, { code: 'id', label: 'Bahasa Indonesia', native: 'Indonesian' }, { code: 'nl', label: 'Nederlands', native: 'Dutch' }, { code: 'sv', label: 'Svenska', native: 'Swedish' }, { code: 'no', label: 'Norsk', native: 'Norwegian' }, { code: 'da', label: 'Dansk', native: 'Danish' }, { code: 'fi', label: 'Suomi', native: 'Finnish' }, { code: 'pl', label: 'Polski', native: 'Polish' }, { code: 'cs', label: 'Čeština', native: 'Czech' }, { code: 'ro', label: 'Română', native: 'Romanian' }, { code: 'uk', label: 'Українська', native: 'Ukrainian' }, { code: 'vi', label: 'Tiếng Việt', native: 'Vietnamese' }, { code: 'th', label: 'ไทย', native: 'Thai' }, { code: 'hr', label: 'Hrvatski', native: 'Croatian' }, { code: 'sr', label: 'Српски', native: 'Serbian' }, { code: 'bg', label: 'български', native: 'Bulgarian' }, { code: 'sl', label: 'Slovenščina', native: 'Slovenian' }, { code: 'mk', label: 'Македонски', native: 'Macedonian' }, { code: 'fil', label: 'Filipino', native: 'Filipino' }, { code: 'sq', label: 'Shqipe', native: 'Albanian' }, { code: 'ca', label: 'Català', native: 'Catalan' }, ]; const filteredLanguages = languages.filter(({ label, code, native }) => (languageSearch || '').length === 0 || label.toLowerCase().includes(languageSearch.toLowerCase()) || native.toLowerCase().includes(languageSearch.toLowerCase()) || code.toLowerCase().includes(languageSearch.toLowerCase()) ); return ( <> {filteredLanguages.map(({ code, label, native }) => ( { updateSetting('tmdbLanguagePreference', code); setLanguagePickerVisible(false); }} style={[ styles.languageItem, settings.tmdbLanguagePreference === code && styles.selectedLanguageItem ]} activeOpacity={0.7} > {native} {label} • {code.toUpperCase()} {settings.tmdbLanguagePreference === code && ( )} ))} {languageSearch.length > 0 && filteredLanguages.length === 0 && ( No languages found for "{languageSearch}" setLanguageSearch('')} style={[styles.clearSearchButton, { backgroundColor: currentTheme.colors.elevation1 }]} > Clear search )} ); })()} {/* Footer Actions */} setLanguagePickerVisible(false)} style={styles.cancelButton} > Cancel setLanguagePickerVisible(false)} style={[styles.doneButton, { backgroundColor: currentTheme.colors.primary }]} > Done setAlertVisible(false)} actions={alertActions} /> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, loadingText: { marginTop: 12, fontSize: 16, }, headerContainer: { paddingHorizontal: 20, paddingBottom: 8, backgroundColor: 'transparent', zIndex: 2, }, header: { flexDirection: 'row', alignItems: 'center', marginBottom: 12, }, backButton: { flexDirection: 'row', alignItems: 'center', }, backText: { fontSize: 16, fontWeight: '500', marginLeft: 4, }, headerTitle: { fontSize: 32, fontWeight: '800', letterSpacing: 0.3, paddingLeft: 4, }, scrollView: { flex: 1, zIndex: 1, }, scrollContent: { paddingHorizontal: 16, paddingBottom: 40, }, sectionCard: { borderRadius: 16, marginBottom: 20, padding: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, sectionHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, }, sectionTitle: { fontSize: 18, fontWeight: '700', marginLeft: 8, }, sectionDescription: { fontSize: 14, lineHeight: 20, marginBottom: 20, }, settingRow: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 16, }, settingTextContainer: { flex: 1, marginRight: 16, }, settingTitle: { fontSize: 16, fontWeight: '600', marginBottom: 4, }, settingDescription: { fontSize: 14, lineHeight: 20, opacity: 0.8, }, languageButton: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8, alignItems: 'center', }, languageButtonText: { fontSize: 14, fontWeight: '600', }, divider: { height: 1, backgroundColor: 'rgba(255,255,255,0.1)', marginVertical: 16, }, statusRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, }, statusText: { fontSize: 14, fontWeight: '500', marginLeft: 8, }, apiKeyContainer: { marginTop: 16, }, infoContainer: { flexDirection: 'row', alignItems: 'flex-start', marginTop: 16, padding: 12, backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 8, }, inputContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, }, input: { flex: 1, borderRadius: 12, paddingHorizontal: 16, paddingVertical: 14, fontSize: 15, borderWidth: 2, }, pasteButton: { position: 'absolute', right: 12, padding: 8, }, buttonRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, button: { borderRadius: 12, paddingVertical: 14, paddingHorizontal: 20, alignItems: 'center', flex: 1, marginRight: 8, }, buttonText: { fontWeight: '600', fontSize: 15, }, resultMessage: { borderRadius: 12, padding: 16, marginTop: 16, flexDirection: 'row', alignItems: 'center', }, resultIcon: { marginRight: 12, }, resultText: { flex: 1, fontSize: 14, fontWeight: '500', }, helpLink: { flexDirection: 'row', alignItems: 'center', marginTop: 16, paddingVertical: 8, }, helpIcon: { marginRight: 8, }, helpText: { fontSize: 14, fontWeight: '500', }, infoText: { fontSize: 14, flex: 1, lineHeight: 20, opacity: 0.8, marginLeft: 8, }, clearButton: { backgroundColor: 'transparent', borderWidth: 2, marginRight: 0, marginLeft: 8, flex: 0, paddingHorizontal: 16, }, // Modal Styles modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.6)', justifyContent: 'flex-end', }, modalContent: { borderTopLeftRadius: 24, borderTopRightRadius: 24, maxHeight: '85%', minHeight: '70%', // Increased minimum height flex: 1, }, modalHeader: { alignItems: 'center', paddingTop: 12, paddingBottom: 16, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.1)', }, dragHandle: { width: 40, height: 4, borderRadius: 2, marginBottom: 12, }, modalTitle: { fontSize: 20, fontWeight: '700', marginBottom: 4, }, modalSubtitle: { fontSize: 14, textAlign: 'center', }, searchSection: { paddingHorizontal: 20, paddingVertical: 16, }, searchContainer: { flexDirection: 'row', alignItems: 'center', borderRadius: 12, paddingHorizontal: 16, paddingVertical: 12, }, searchIcon: { marginRight: 12, }, searchInput: { flex: 1, fontSize: 16, paddingVertical: 0, }, searchClearButton: { padding: 4, marginLeft: 8, }, popularSection: { paddingHorizontal: 20, paddingBottom: 16, }, searchResultsTitle: { color: '#FFFFFF', }, popularChips: { paddingVertical: 2, }, popularChip: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, marginRight: 8, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', }, selectedChip: { // Border color handled by inline styles }, popularChipText: { fontSize: 14, fontWeight: '500', }, selectedChipText: { color: '#FFFFFF', }, languagesSection: { flex: 1, paddingHorizontal: 20, }, languageList: { flex: 1, }, languageItem: { paddingVertical: 16, paddingHorizontal: 16, borderRadius: 12, marginBottom: 4, minHeight: 60, }, selectedLanguageItem: { backgroundColor: 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)', }, languageContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, languageInfo: { flex: 1, marginRight: 12, }, languageName: { fontSize: 16, fontWeight: '500', marginBottom: 2, }, selectedLanguageName: { fontWeight: '600', }, languageCode: { fontSize: 12, }, selectedLanguageCode: { }, checkmarkContainer: { width: 32, height: 32, borderRadius: 16, backgroundColor: 'rgba(255,255,255,0.1)', alignItems: 'center', justifyContent: 'center', }, noResultsContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 40, }, noResultsText: { fontSize: 16, marginTop: 12, textAlign: 'center', }, clearSearchButton: { marginTop: 16, paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8, }, clearSearchButtonText: { fontSize: 14, fontWeight: '600', }, modalFooter: { flexDirection: 'row', paddingHorizontal: 20, paddingVertical: 20, paddingBottom: Platform.OS === 'ios' ? 40 : 20, gap: 12, }, cancelButton: { flex: 1, paddingVertical: 14, alignItems: 'center', borderRadius: 12, backgroundColor: 'rgba(255,255,255,0.1)', }, cancelButtonText: { fontSize: 16, fontWeight: '600', }, doneButton: { flex: 1, paddingVertical: 14, alignItems: 'center', borderRadius: 12, }, doneButtonText: { fontSize: 16, fontWeight: '700', }, // Logo Source Styles selectorLabel: { fontSize: 13, marginBottom: 8, marginTop: 4, }, showsScrollView: { marginBottom: 16, }, showsScrollContent: { paddingRight: 16, paddingVertical: 2, }, showItem: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, marginRight: 6, borderWidth: 1, borderColor: 'transparent', }, selectedShowItem: { borderWidth: 2, }, showItemText: { fontSize: 13, }, selectedShowItemText: { fontWeight: '600', }, logoPreviewCard: { borderRadius: 12, padding: 12, marginTop: 12, }, exampleImage: { height: 60, width: '100%', backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 8, }, bannerContainer: { height: 80, width: '100%', borderRadius: 8, overflow: 'hidden', position: 'relative', marginTop: 4, }, bannerImage: { ...StyleSheet.absoluteFillObject, }, bannerOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.4)', }, logoOverBanner: { position: 'absolute', width: '80%', height: '70%', alignSelf: 'center', top: '15%', }, noLogoContainer: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', }, noLogoText: { color: '#fff', fontSize: 13, backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 4, }, logoSourceLabel: { fontSize: 11, marginTop: 6, }, attributionContainer: { alignItems: 'center', marginBottom: 24, marginTop: 8, paddingHorizontal: 24, width: '100%', }, tmdbLogo: { width: 80, height: 60, marginBottom: 8, }, attributionText: { fontSize: 11, textAlign: 'center', lineHeight: 16, opacity: 0.7, }, }); export default TMDBSettingsScreen;