mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
1435 lines
No EOL
50 KiB
TypeScript
1435 lines
No EOL
50 KiB
TypeScript
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';
|
|
// (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 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<Array<{ label: string; onPress: () => void; style?: object }>>([
|
|
{ label: 'OK', onPress: () => setAlertVisible(false) },
|
|
]);
|
|
const apiKeyInputRef = useRef<TextInput>(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<string | null>(null);
|
|
const [tmdbBanner, setTmdbBanner] = useState<string | null>(null);
|
|
const [loadingLogos, setLoadingLogos] = useState(true);
|
|
const [previewLanguage, setPreviewLanguage] = useState<string>('');
|
|
const [isPreviewFallback, setIsPreviewFallback] = useState<boolean>(false);
|
|
|
|
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: 'OK', onPress: () => setAlertVisible(false) }]);
|
|
}
|
|
setAlertVisible(true);
|
|
};
|
|
|
|
useEffect(() => {
|
|
logger.log('[TMDBSettingsScreen] Component mounted');
|
|
loadSettings();
|
|
return () => {
|
|
logger.log('[TMDBSettingsScreen] Component unmounted');
|
|
};
|
|
}, []);
|
|
|
|
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: 'API Key cannot be empty.' });
|
|
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: 'API key verified and saved successfully.' });
|
|
logger.log('[TMDBSettingsScreen] API key saved successfully');
|
|
} else {
|
|
logger.warn('[TMDBSettingsScreen] API key test failed');
|
|
setTestResult({ success: false, message: 'Invalid API key. Please check and try again.' });
|
|
}
|
|
} catch (error) {
|
|
logger.error('[TMDBSettingsScreen] Error saving API key:', error);
|
|
setTestResult({
|
|
success: false,
|
|
message: 'An error occurred while saving. Please try again.'
|
|
});
|
|
}
|
|
};
|
|
|
|
const testApiKey = async (key: string): Promise<boolean> => {
|
|
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(
|
|
'Clear API Key',
|
|
'Are you sure you want to remove your custom API key and revert to the default?',
|
|
[
|
|
{
|
|
label: 'Cancel',
|
|
onPress: () => logger.log('[TMDBSettingsScreen] Clear API key cancelled'),
|
|
},
|
|
{
|
|
label: '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('Error', 'Failed to clear API key');
|
|
}
|
|
},
|
|
},
|
|
]
|
|
);
|
|
};
|
|
|
|
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: 'Now using the built-in TMDb API 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: 'Now using your custom TMDb API 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: 'Please enter and save your custom TMDb API 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 (
|
|
<View style={[styles.exampleImage, styles.loadingContainer]}>
|
|
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.bannerContainer}>
|
|
<FastImage
|
|
source={{ uri: banner || undefined }}
|
|
style={styles.bannerImage}
|
|
resizeMode={FastImage.resizeMode.cover}
|
|
/>
|
|
<View style={styles.bannerOverlay} />
|
|
{logo && (
|
|
<FastImage
|
|
source={{ uri: logo }}
|
|
style={styles.logoOverBanner}
|
|
resizeMode={FastImage.resizeMode.contain}
|
|
/>
|
|
)}
|
|
{!logo && (
|
|
<View style={styles.noLogoContainer}>
|
|
<Text style={styles.noLogoText}>No logo available</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// 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 (
|
|
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
|
<StatusBar barStyle="light-content" />
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color={currentTheme.colors.primary} />
|
|
<Text style={[styles.loadingText, { color: currentTheme.colors.text }]}>Loading Settings...</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
|
<StatusBar barStyle="light-content" />
|
|
<View style={[styles.headerContainer, { paddingTop: topSpacing }]}>
|
|
<View style={styles.header}>
|
|
<TouchableOpacity
|
|
style={styles.backButton}
|
|
onPress={() => navigation.goBack()}
|
|
>
|
|
<MaterialIcons name="chevron-left" size={28} color={currentTheme.colors.primary} />
|
|
<Text style={[styles.backText, { color: currentTheme.colors.primary }]}>Settings</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
<Text style={[styles.headerTitle, { color: currentTheme.colors.text }]}>
|
|
TMDb Settings
|
|
</Text>
|
|
</View>
|
|
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
contentContainerStyle={styles.scrollContent}
|
|
keyboardShouldPersistTaps="handled"
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{/* Metadata Enrichment Section */}
|
|
<View style={[styles.sectionCard, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
|
<View style={styles.sectionHeader}>
|
|
<MaterialIcons name="movie" size={20} color={currentTheme.colors.primary} />
|
|
<Text style={[styles.sectionTitle, { color: currentTheme.colors.text }]}>Metadata Enrichment</Text>
|
|
</View>
|
|
<Text style={[styles.sectionDescription, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
Enhance your content metadata with TMDb data for better details and information.
|
|
</Text>
|
|
|
|
<View style={styles.settingRow}>
|
|
<View style={styles.settingTextContainer}>
|
|
<Text style={[styles.settingTitle, { color: currentTheme.colors.text }]}>Enable Enrichment</Text>
|
|
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
Augments addon metadata with TMDb for cast, certification, logos/posters, and episode fallback.
|
|
</Text>
|
|
</View>
|
|
<Switch
|
|
value={settings.enrichMetadataWithTMDB}
|
|
onValueChange={(v) => 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)'}
|
|
/>
|
|
</View>
|
|
|
|
{settings.enrichMetadataWithTMDB && (
|
|
<>
|
|
<View style={styles.divider} />
|
|
|
|
<View style={styles.settingRow}>
|
|
<View style={styles.settingTextContainer}>
|
|
<Text style={[styles.settingTitle, { color: currentTheme.colors.text }]}>Localized Text</Text>
|
|
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
Fetch titles and descriptions in your preferred language from TMDb.
|
|
</Text>
|
|
</View>
|
|
<Switch
|
|
value={settings.useTmdbLocalizedMetadata}
|
|
onValueChange={(v) => 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)'}
|
|
/>
|
|
</View>
|
|
|
|
{settings.useTmdbLocalizedMetadata && (
|
|
<>
|
|
<View style={styles.divider} />
|
|
|
|
<View style={styles.settingRow}>
|
|
<View style={styles.settingTextContainer}>
|
|
<Text style={[styles.settingTitle, { color: currentTheme.colors.text }]}>Language</Text>
|
|
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
Current: {(settings.tmdbLanguagePreference || 'en').toUpperCase()}
|
|
</Text>
|
|
</View>
|
|
<TouchableOpacity
|
|
onPress={() => setLanguagePickerVisible(true)}
|
|
style={[styles.languageButton, { backgroundColor: currentTheme.colors.primary }]}
|
|
>
|
|
<Text style={[styles.languageButtonText, { color: currentTheme.colors.white }]}>Change</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Logo Preview */}
|
|
<View style={styles.divider} />
|
|
|
|
<Text style={[styles.settingTitle, { color: currentTheme.colors.text, marginBottom: 8 }]}>Logo Preview</Text>
|
|
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis, marginBottom: 12 }]}>
|
|
Preview shows how localized logos will appear in the selected language.
|
|
</Text>
|
|
|
|
{/* Show selector */}
|
|
<Text style={[styles.selectorLabel, { color: currentTheme.colors.mediumEmphasis }]}>Example:</Text>
|
|
<ScrollView
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
contentContainerStyle={styles.showsScrollContent}
|
|
style={styles.showsScrollView}
|
|
>
|
|
{EXAMPLE_SHOWS.map((show) => (
|
|
<TouchableOpacity
|
|
key={show.imdbId}
|
|
style={[
|
|
styles.showItem,
|
|
{ backgroundColor: currentTheme.colors.elevation1 },
|
|
selectedShow.imdbId === show.imdbId && [styles.selectedShowItem, { borderColor: currentTheme.colors.primary }]
|
|
]}
|
|
onPress={() => handleShowSelect(show)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.showItemText,
|
|
{ color: currentTheme.colors.mediumEmphasis },
|
|
selectedShow.imdbId === show.imdbId && [styles.selectedShowItemText, { color: currentTheme.colors.white }]
|
|
]}
|
|
>
|
|
{show.name}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</ScrollView>
|
|
|
|
{/* Preview card */}
|
|
<View style={[styles.logoPreviewCard, { backgroundColor: currentTheme.colors.elevation1 }]}>
|
|
{renderLogoExample(tmdbLogo, tmdbBanner, loadingLogos)}
|
|
{tmdbLogo && (
|
|
<Text style={[styles.logoSourceLabel, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
{`Language: ${(previewLanguage || '').toUpperCase() || 'N/A'}${isPreviewFallback ? ' (fallback to available)' : ''}`}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</View>
|
|
|
|
{/* API Configuration Section */}
|
|
<View style={[styles.sectionCard, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
|
<View style={styles.sectionHeader}>
|
|
<MaterialIcons name="api" size={20} color={currentTheme.colors.primary} />
|
|
<Text style={[styles.sectionTitle, { color: currentTheme.colors.text }]}>API Configuration</Text>
|
|
</View>
|
|
<Text style={[styles.sectionDescription, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
Configure your TMDb API access for enhanced functionality.
|
|
</Text>
|
|
|
|
<View style={styles.settingRow}>
|
|
<View style={styles.settingTextContainer}>
|
|
<Text style={[styles.settingTitle, { color: currentTheme.colors.text }]}>Custom API Key</Text>
|
|
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
Use your own TMDb API key for better performance and dedicated rate limits.
|
|
</Text>
|
|
</View>
|
|
<Switch
|
|
value={useCustomKey}
|
|
onValueChange={toggleUseCustomKey}
|
|
trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }}
|
|
thumbColor={Platform.OS === 'android' ? (useCustomKey ? currentTheme.colors.white : currentTheme.colors.white) : ''}
|
|
ios_backgroundColor={'rgba(255,255,255,0.1)'}
|
|
/>
|
|
</View>
|
|
|
|
{useCustomKey && (
|
|
<>
|
|
<View style={styles.divider} />
|
|
|
|
{/* API Key Status */}
|
|
<View style={styles.statusRow}>
|
|
<MaterialIcons
|
|
name={isKeySet ? "check-circle" : "error-outline"}
|
|
size={20}
|
|
color={isKeySet ? currentTheme.colors.success : currentTheme.colors.warning}
|
|
/>
|
|
<Text style={[styles.statusText, {
|
|
color: isKeySet ? currentTheme.colors.success : currentTheme.colors.warning
|
|
}]}>
|
|
{isKeySet ? "Custom API key active" : "API key required"}
|
|
</Text>
|
|
</View>
|
|
|
|
{/* API Key Input */}
|
|
<View style={styles.apiKeyContainer}>
|
|
<View style={styles.inputContainer}>
|
|
<TextInput
|
|
ref={apiKeyInputRef}
|
|
style={[
|
|
styles.input,
|
|
{
|
|
backgroundColor: currentTheme.colors.elevation1,
|
|
color: currentTheme.colors.text,
|
|
borderColor: isInputFocused ? currentTheme.colors.primary : 'transparent'
|
|
}
|
|
]}
|
|
value={apiKey}
|
|
onChangeText={(text) => {
|
|
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)}
|
|
/>
|
|
<TouchableOpacity
|
|
style={styles.pasteButton}
|
|
onPress={pasteFromClipboard}
|
|
>
|
|
<MaterialIcons name="content-paste" size={20} color={currentTheme.colors.primary} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<View style={styles.buttonRow}>
|
|
<TouchableOpacity
|
|
style={[styles.button, { backgroundColor: currentTheme.colors.primary }]}
|
|
onPress={saveApiKey}
|
|
>
|
|
<Text style={[styles.buttonText, { color: currentTheme.colors.white }]}>Save</Text>
|
|
</TouchableOpacity>
|
|
|
|
{isKeySet && (
|
|
<TouchableOpacity
|
|
style={[styles.button, styles.clearButton, { borderColor: currentTheme.colors.error }]}
|
|
onPress={clearApiKey}
|
|
>
|
|
<Text style={[styles.buttonText, { color: currentTheme.colors.error }]}>Clear</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
|
|
{testResult && (
|
|
<View style={[
|
|
styles.resultMessage,
|
|
{ backgroundColor: testResult.success ? currentTheme.colors.success + '1A' : currentTheme.colors.error + '1A' }
|
|
]}>
|
|
<MaterialIcons
|
|
name={testResult.success ? "check-circle" : "error"}
|
|
size={16}
|
|
color={testResult.success ? currentTheme.colors.success : currentTheme.colors.error}
|
|
style={styles.resultIcon}
|
|
/>
|
|
<Text style={[
|
|
styles.resultText,
|
|
{ color: testResult.success ? currentTheme.colors.success : currentTheme.colors.error }
|
|
]}>
|
|
{testResult.message}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
|
|
<TouchableOpacity
|
|
style={styles.helpLink}
|
|
onPress={openTMDBWebsite}
|
|
>
|
|
<MaterialIcons name="help" size={16} color={currentTheme.colors.primary} style={styles.helpIcon} />
|
|
<Text style={[styles.helpText, { color: currentTheme.colors.primary }]}>
|
|
How to get a TMDb API key?
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</>
|
|
)}
|
|
|
|
{!useCustomKey && (
|
|
<View style={styles.infoContainer}>
|
|
<MaterialIcons name="info-outline" size={18} color={currentTheme.colors.primary} />
|
|
<Text style={[styles.infoText, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
Currently using built-in API key. Consider using your own key for better performance.
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
{/* Language Picker Modal */}
|
|
<Modal
|
|
visible={languagePickerVisible}
|
|
transparent
|
|
animationType="slide"
|
|
onRequestClose={() => setLanguagePickerVisible(false)}
|
|
>
|
|
<TouchableWithoutFeedback onPress={() => setLanguagePickerVisible(false)}>
|
|
<View style={styles.modalOverlay}>
|
|
<TouchableWithoutFeedback>
|
|
<View style={[styles.modalContent, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
|
{/* Header */}
|
|
<View style={styles.modalHeader}>
|
|
<View style={[styles.dragHandle, { backgroundColor: currentTheme.colors.elevation3 }]} />
|
|
<Text style={[styles.modalTitle, { color: currentTheme.colors.text }]}>Choose Language</Text>
|
|
<Text style={[styles.modalSubtitle, { color: currentTheme.colors.mediumEmphasis }]}>Select your preferred language for TMDb content</Text>
|
|
</View>
|
|
|
|
{/* Search Section */}
|
|
<View style={styles.searchSection}>
|
|
<View style={[styles.searchContainer, { backgroundColor: currentTheme.colors.elevation1 }]}>
|
|
<MaterialIcons name="search" size={20} color={currentTheme.colors.mediumEmphasis} style={styles.searchIcon} />
|
|
<TextInput
|
|
placeholder="Search languages..."
|
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
|
style={[styles.searchInput, { color: currentTheme.colors.text }]}
|
|
value={languageSearch}
|
|
onChangeText={setLanguageSearch}
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
/>
|
|
{languageSearch.length > 0 && (
|
|
<TouchableOpacity onPress={() => setLanguageSearch('')} style={styles.searchClearButton}>
|
|
<MaterialIcons name="close" size={20} color={currentTheme.colors.mediumEmphasis} />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Popular Languages */}
|
|
{languageSearch.length === 0 && (
|
|
<View style={styles.popularSection}>
|
|
<Text style={[styles.sectionTitle, { color: currentTheme.colors.mediumEmphasis }]}>Popular</Text>
|
|
<ScrollView
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
contentContainerStyle={styles.popularChips}
|
|
>
|
|
{[
|
|
{ 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 }) => (
|
|
<TouchableOpacity
|
|
key={code}
|
|
onPress={() => { 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)',
|
|
}
|
|
]}
|
|
>
|
|
<Text style={[
|
|
styles.popularChipText,
|
|
settings.tmdbLanguagePreference === code && styles.selectedChipText,
|
|
{ color: settings.tmdbLanguagePreference === code ? currentTheme.colors.white : currentTheme.colors.text }
|
|
]}>
|
|
{label}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</ScrollView>
|
|
</View>
|
|
)}
|
|
|
|
{/* All Languages */}
|
|
<View style={styles.languagesSection}>
|
|
<Text style={[
|
|
styles.sectionTitle,
|
|
languageSearch.length > 0 && styles.searchResultsTitle,
|
|
{ color: languageSearch.length > 0 ? currentTheme.colors.text : currentTheme.colors.mediumEmphasis }
|
|
]}>
|
|
{languageSearch.length > 0 ? 'Search Results' : 'All Languages'}
|
|
</Text>
|
|
|
|
<ScrollView style={styles.languageList} showsVerticalScrollIndicator={false}>
|
|
{(() => {
|
|
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' },
|
|
];
|
|
|
|
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 }) => (
|
|
<TouchableOpacity
|
|
key={code}
|
|
onPress={() => { updateSetting('tmdbLanguagePreference', code); setLanguagePickerVisible(false); }}
|
|
style={[
|
|
styles.languageItem,
|
|
settings.tmdbLanguagePreference === code && styles.selectedLanguageItem
|
|
]}
|
|
activeOpacity={0.7}
|
|
>
|
|
<View style={styles.languageContent}>
|
|
<View style={styles.languageInfo}>
|
|
<Text style={[
|
|
styles.languageName,
|
|
settings.tmdbLanguagePreference === code && styles.selectedLanguageName,
|
|
{
|
|
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.text,
|
|
}
|
|
]}>
|
|
{native}
|
|
</Text>
|
|
<Text style={[
|
|
styles.languageCode,
|
|
settings.tmdbLanguagePreference === code && styles.selectedLanguageCode,
|
|
{
|
|
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.mediumEmphasis,
|
|
}
|
|
]}>
|
|
{label} • {code.toUpperCase()}
|
|
</Text>
|
|
</View>
|
|
{settings.tmdbLanguagePreference === code && (
|
|
<View style={styles.checkmarkContainer}>
|
|
<MaterialIcons name="check-circle" size={24} color={currentTheme.colors.primary} />
|
|
</View>
|
|
)}
|
|
</View>
|
|
</TouchableOpacity>
|
|
))}
|
|
{languageSearch.length > 0 && filteredLanguages.length === 0 && (
|
|
<View style={styles.noResultsContainer}>
|
|
<MaterialIcons name="search-off" size={48} color={currentTheme.colors.mediumEmphasis} />
|
|
<Text style={[styles.noResultsText, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
No languages found for "{languageSearch}"
|
|
</Text>
|
|
<TouchableOpacity
|
|
onPress={() => setLanguageSearch('')}
|
|
style={[styles.clearSearchButton, { backgroundColor: currentTheme.colors.elevation1 }]}
|
|
>
|
|
<Text style={[styles.clearSearchButtonText, { color: currentTheme.colors.primary }]}>Clear search</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
</>
|
|
);
|
|
})()}
|
|
</ScrollView>
|
|
</View>
|
|
|
|
{/* Footer Actions */}
|
|
<View style={styles.modalFooter}>
|
|
<TouchableOpacity
|
|
onPress={() => setLanguagePickerVisible(false)}
|
|
style={styles.cancelButton}
|
|
>
|
|
<Text style={[styles.cancelButtonText, { color: currentTheme.colors.text }]}>Cancel</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
onPress={() => setLanguagePickerVisible(false)}
|
|
style={[styles.doneButton, { backgroundColor: currentTheme.colors.primary }]}
|
|
>
|
|
<Text style={[styles.doneButtonText, { color: currentTheme.colors.white }]}>Done</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</TouchableWithoutFeedback>
|
|
</View>
|
|
</TouchableWithoutFeedback>
|
|
</Modal>
|
|
</ScrollView>
|
|
<CustomAlert
|
|
visible={alertVisible}
|
|
title={alertTitle}
|
|
message={alertMessage}
|
|
onClose={() => setAlertVisible(false)}
|
|
actions={alertActions}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
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,
|
|
},
|
|
});
|
|
|
|
export default TMDBSettingsScreen;
|