import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Switch, SafeAreaView, Image, StatusBar, Platform, ActivityIndicator, } from 'react-native'; import { NavigationProp, useNavigation } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useSettings, DEFAULT_SETTINGS } from '../hooks/useSettings'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { TMDBService } from '../services/tmdbService'; import { logger } from '../utils/logger'; import { useTheme } from '../contexts/ThemeContext'; import CustomAlert from '../components/CustomAlert'; // TMDB API key - since the default key might be private in the service, we'll use our own const TMDB_API_KEY = '439c478a771f35c05022f9feabcca01c'; // Extra TMDB logo languages to always offer (only Arabic per request) const COMMON_TMDB_LANGUAGES: string[] = ['ar']; // 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: 'Game of Thrones', imdbId: 'tt0944947', tmdbId: '1399', type: 'tv' as const }, { name: 'Stranger Things', imdbId: 'tt4574334', tmdbId: '66732', type: 'tv' as const }, { name: 'Squid Game', imdbId: 'tt10919420', tmdbId: '93405', type: 'tv' as const }, { name: 'Avatar', imdbId: 'tt0499549', tmdbId: '19995', type: 'movie' as const }, { name: 'The Witcher', imdbId: 'tt5180504', tmdbId: '71912', type: 'tv' as const } ]; // Create a styles creator function that accepts the theme colors const createStyles = (colors: any) => StyleSheet.create({ container: { flex: 1, backgroundColor: colors.darkBackground, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight || 0) + 8 : 8, backgroundColor: colors.darkBackground, }, backButton: { flexDirection: 'row', alignItems: 'center', padding: 8, }, backText: { fontSize: 17, marginLeft: 8, color: colors.white, }, headerActions: { flexDirection: 'row', alignItems: 'center', }, headerButton: { padding: 8, marginLeft: 8, }, headerTitle: { fontSize: 34, fontWeight: 'bold', paddingHorizontal: 16, marginBottom: 24, color: colors.white, }, headerRight: { width: 24, }, scrollView: { flex: 1, }, scrollContent: { paddingHorizontal: 16, paddingBottom: 24, }, descriptionContainer: { marginBottom: 16, }, description: { color: colors.mediumEmphasis, fontSize: 15, lineHeight: 22, }, showSelectorContainer: { marginBottom: 16, }, selectorLabel: { color: colors.highEmphasis, fontSize: 16, fontWeight: '500', marginBottom: 12, }, showsScrollContent: { paddingRight: 16, }, showItem: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: colors.elevation2, borderRadius: 16, marginRight: 6, borderWidth: 1, borderColor: 'transparent', shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 1, elevation: 1, }, selectedShowItem: { borderColor: colors.primary, backgroundColor: colors.elevation3, shadowColor: colors.primary, shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.2, shadowRadius: 2, elevation: 2, }, showItemText: { color: colors.mediumEmphasis, fontSize: 14, }, selectedShowItemText: { color: colors.white, fontWeight: '600', }, optionsContainer: { marginBottom: 16, gap: 12, }, optionCard: { backgroundColor: colors.elevation2, borderRadius: 8, padding: 12, borderWidth: 2, borderColor: 'transparent', marginBottom: 8, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 3, elevation: 2, }, selectedCard: { borderColor: colors.primary, shadowColor: colors.primary, shadowOpacity: 0.3, elevation: 3, }, optionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6, }, optionTitle: { color: colors.white, fontSize: 16, fontWeight: '600', }, optionDescription: { color: colors.mediumEmphasis, fontSize: 13, lineHeight: 18, marginBottom: 10, }, exampleContainer: { marginTop: 4, }, exampleLabel: { color: colors.mediumEmphasis, fontSize: 13, marginBottom: 4, }, exampleImage: { height: 60, width: '100%', backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 8, }, loadingContainer: { justifyContent: 'center', alignItems: 'center', }, infoBox: { marginBottom: 16, padding: 12, backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 8, borderLeftWidth: 3, borderLeftColor: colors.primary, }, infoText: { color: colors.mediumEmphasis, fontSize: 12, lineHeight: 18, }, logoSourceLabel: { color: colors.mediumEmphasis, fontSize: 11, marginTop: 2, }, languageSelectorContainer: { marginTop: 10, padding: 10, backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 6, }, languageSelectorTitle: { color: colors.white, fontSize: 14, fontWeight: '600', marginBottom: 4, }, languageSelectorDescription: { color: colors.mediumEmphasis, fontSize: 12, lineHeight: 18, marginBottom: 8, }, languageSelectorLabel: { color: colors.mediumEmphasis, fontSize: 12, marginBottom: 6, }, languageScrollContent: { paddingVertical: 2, }, languageItem: { paddingHorizontal: 10, paddingVertical: 6, backgroundColor: colors.elevation1, borderRadius: 12, marginRight: 6, borderWidth: 1, borderColor: colors.elevation3, marginVertical: 1, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 1, elevation: 1, }, selectedLanguageItem: { backgroundColor: colors.primary, borderColor: colors.primary, shadowColor: colors.primary, shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.2, shadowRadius: 1, elevation: 2, }, languageItemText: { color: colors.mediumEmphasis, fontSize: 12, fontWeight: '600', }, selectedLanguageItemText: { color: colors.white, }, noteText: { color: colors.mediumEmphasis, fontSize: 11, marginTop: 8, fontStyle: 'italic', }, bannerContainer: { height: 90, width: '100%', borderRadius: 6, overflow: 'hidden', position: 'relative', }, bannerImage: { ...StyleSheet.absoluteFillObject, }, bannerOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.5)', }, logoOverBanner: { position: 'absolute', width: '80%', height: '75%', alignSelf: 'center', top: '12.5%', }, noLogoContainer: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', }, noLogoText: { color: colors.white, fontSize: 14, backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 4, }, }); const LogoSourceSettings = () => { const { settings, updateSetting } = useSettings(); const navigation = useNavigation>(); const insets = useSafeAreaInsets(); const { currentTheme } = useTheme(); const colors = currentTheme.colors; const styles = createStyles(colors); // CustomAlert state const [alertVisible, setAlertVisible] = useState(false); const [alertTitle, setAlertTitle] = useState(''); const [alertMessage, setAlertMessage] = useState(''); const [alertActions, setAlertActions] = useState void; style?: object }>>([ { label: 'OK', onPress: () => setAlertVisible(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); }; // Get current preference const [logoSource, setLogoSource] = useState<'metahub' | 'tmdb'>( settings.logoSourcePreference || 'metahub' ); // Make sure logoSource stays in sync with settings useEffect(() => { setLogoSource(settings.logoSourcePreference || 'metahub'); }, [settings.logoSourcePreference]); // Selected example show const [selectedShow, setSelectedShow] = useState(EXAMPLE_SHOWS[0]); // Add state for example logos and banners const [tmdbLogo, setTmdbLogo] = useState(null); const [metahubLogo, setMetahubLogo] = useState(null); const [tmdbBanner, setTmdbBanner] = useState(null); const [metahubBanner, setMetahubBanner] = useState(null); const [loadingLogos, setLoadingLogos] = useState(true); // Track which language the preview is actually using and if it is a fallback const [previewLanguage, setPreviewLanguage] = useState(''); const [isPreviewFallback, setIsPreviewFallback] = useState(false); // State for TMDB language selection // Store unique language codes as strings const [uniqueTmdbLanguages, setUniqueTmdbLanguages] = useState([]); const [tmdbLogosData, setTmdbLogosData] = useState | null>(null); // Load example logos for selected show useEffect(() => { fetchExampleLogos(selectedShow); }, [selectedShow]); // Function to fetch logos and banners for a specific show const fetchExampleLogos = async (show: typeof EXAMPLE_SHOWS[0]) => { setLoadingLogos(true); setTmdbLogo(null); setMetahubLogo(null); setTmdbBanner(null); setMetahubBanner(null); // Reset unique languages and logos data setUniqueTmdbLanguages([]); setTmdbLogosData(null); try { const tmdbService = TMDBService.getInstance(); const imdbId = show.imdbId; const tmdbId = show.tmdbId; const contentType = show.type; logger.log(`[LogoSourceSettings] Fetching ${show.name} with TMDB ID: ${tmdbId}, IMDB ID: ${imdbId}`); // Get preferred language directly from settings const preferredTmdbLanguage = settings.tmdbLanguagePreference || 'en'; // Get TMDB logo and banner try { 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(); // Store all TMDB logos data and extract unique languages if (imagesData.logos && imagesData.logos.length > 0) { setTmdbLogosData(imagesData.logos); // Filter for logos with valid language codes and get unique codes const validLogoLanguages = imagesData.logos .map((logo: { iso_639_1: string | null }) => logo.iso_639_1) .filter((lang: string | null): lang is string => lang !== null && typeof lang === 'string'); // Explicitly type the Set and resulting array const uniqueCodes: string[] = [...new Set(validLogoLanguages)]; setUniqueTmdbLanguages(uniqueCodes); // Find initial logo (prefer selectedTmdbLanguage, then 'en') let initialLogoPath: string | null = null; let initialLanguage = preferredTmdbLanguage; // First try to find a logo in the user's preferred language const preferredLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === preferredTmdbLanguage); if (preferredLogo) { initialLogoPath = preferredLogo.file_path; initialLanguage = preferredTmdbLanguage; logger.log(`[LogoSourceSettings] Found initial ${preferredTmdbLanguage} TMDB logo for ${show.name}`); setIsPreviewFallback(false); } else { // Fallback to English logo const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === 'en'); if (englishLogo) { initialLogoPath = englishLogo.file_path; initialLanguage = 'en'; logger.log(`[LogoSourceSettings] Found initial English TMDB logo for ${show.name}`); setIsPreviewFallback(true); } else if (imagesData.logos[0]) { // Fallback to the first available logo initialLogoPath = imagesData.logos[0].file_path; initialLanguage = imagesData.logos[0].iso_639_1; logger.log(`[LogoSourceSettings] No English logo, using first available (${initialLanguage}) TMDB logo for ${show.name}`); setIsPreviewFallback(true); } } if (initialLogoPath) { setTmdbLogo(`https://image.tmdb.org/t/p/original${initialLogoPath}`); setPreviewLanguage(initialLanguage || ''); } else { logger.warn(`[LogoSourceSettings] No valid initial TMDB logo found for ${show.name}`); } } else { logger.warn(`[LogoSourceSettings] No TMDB logos found in response for ${show.name}`); setUniqueTmdbLanguages([]); // Ensure it's empty if no logos setPreviewLanguage(''); setIsPreviewFallback(false); } // Get TMDB banner (backdrop) if (imagesData.backdrops && imagesData.backdrops.length > 0) { const backdropPath = imagesData.backdrops[0].file_path; const tmdbBannerUrl = `https://image.tmdb.org/t/p/original${backdropPath}`; setTmdbBanner(tmdbBannerUrl); logger.log(`[LogoSourceSettings] Got ${show.name} TMDB banner: ${tmdbBannerUrl}`); } else { // Try to get backdrop from details const detailsResponse = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}?api_key=${apiKey}`); const details = await detailsResponse.json(); if (details.backdrop_path) { const tmdbBannerUrl = `https://image.tmdb.org/t/p/original${details.backdrop_path}`; setTmdbBanner(tmdbBannerUrl); logger.log(`[LogoSourceSettings] Got ${show.name} TMDB banner from details: ${tmdbBannerUrl}`); } } } catch (tmdbError) { logger.error(`[LogoSourceSettings] Error fetching TMDB images:`, tmdbError); } // Get Metahub logo and banner try { // Metahub logo const metahubLogoUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`; const logoResponse = await fetch(metahubLogoUrl, { method: 'HEAD' }); if (logoResponse.ok) { setMetahubLogo(metahubLogoUrl); logger.log(`[LogoSourceSettings] Got ${show.name} Metahub logo: ${metahubLogoUrl}`); } // Metahub banner const metahubBannerUrl = `https://images.metahub.space/background/medium/${imdbId}/img`; const bannerResponse = await fetch(metahubBannerUrl, { method: 'HEAD' }); if (bannerResponse.ok) { setMetahubBanner(metahubBannerUrl); logger.log(`[LogoSourceSettings] Got ${show.name} Metahub banner: ${metahubBannerUrl}`); } else if (tmdbBanner) { // If Metahub banner doesn't exist, use TMDB banner setMetahubBanner(tmdbBanner); } } catch (metahubErr) { logger.error(`[LogoSourceSettings] Error checking Metahub images:`, metahubErr); } } catch (err) { logger.error(`[LogoSourceSettings] Error fetching ${show.name} logos:`, err); } finally { setLoadingLogos(false); } }; // Apply logo source setting and show confirmation const applyLogoSourceSetting = (source: 'metahub' | 'tmdb') => { // Update local state first setLogoSource(source); // Update using the settings hook updateSetting('logoSourcePreference', source); // Also save directly to AsyncStorage for extra assurance try { // Get current settings AsyncStorage.getItem('app_settings').then((settingsJson) => { if (settingsJson) { const currentSettings = JSON.parse(settingsJson); // Update the logo source preference const updatedSettings = { ...currentSettings, logoSourcePreference: source }; // Save back to AsyncStorage AsyncStorage.setItem('app_settings', JSON.stringify(updatedSettings)) .then(() => { logger.log(`[LogoSourceSettings] Successfully saved logo source preference '${source}' to AsyncStorage`); }) .catch((error) => { logger.error(`[LogoSourceSettings] Error saving logo source preference to AsyncStorage:`, error); }); } }).catch((error) => { logger.error(`[LogoSourceSettings] Error getting current settings:`, error); }); // Clear any cached logo data AsyncStorage.removeItem('_last_logos_'); } catch (e) { logger.error(`[LogoSourceSettings] Error in applyLogoSourceSetting:`, e); } // Show confirmation alert openAlert( 'Settings Updated', `Logo and background source preference set to ${source === 'metahub' ? 'Metahub' : 'TMDB'}. Changes will apply when you navigate to content.` ); }; // Handle TMDB language selection const handleTmdbLanguageSelect = (languageCode: string) => { // Update the preview logo if possible if (tmdbLogosData) { const selectedLogoData = tmdbLogosData.find(logo => logo.iso_639_1 === languageCode); if (selectedLogoData) { setTmdbLogo(`https://image.tmdb.org/t/p/original${selectedLogoData.file_path}`); logger.log(`[LogoSourceSettings] Switched TMDB logo preview to language: ${languageCode}`); setPreviewLanguage(languageCode); setIsPreviewFallback(false); } else { logger.warn(`[LogoSourceSettings] Could not find logo data for selected language: ${languageCode}`); // Fallback to English, then first available if English is not present const englishData = tmdbLogosData.find(logo => logo.iso_639_1 === 'en'); if (englishData) { setTmdbLogo(`https://image.tmdb.org/t/p/original${englishData.file_path}`); setPreviewLanguage('en'); setIsPreviewFallback(true); } else if (tmdbLogosData[0]) { setTmdbLogo(`https://image.tmdb.org/t/p/original${tmdbLogosData[0].file_path}`); setPreviewLanguage(tmdbLogosData[0].iso_639_1 || ''); setIsPreviewFallback(true); } else { setPreviewLanguage(''); setIsPreviewFallback(false); } } } // Then persist the setting globally saveLanguagePreference(languageCode); }; // Get preferred language directly from settings for UI rendering const preferredTmdbLanguage = settings.tmdbLanguagePreference || 'en'; // Save language preference with proper persistence const saveLanguagePreference = async (languageCode: string) => { logger.log(`[LogoSourceSettings] Saving TMDB language preference: ${languageCode}`); try { // First use the settings hook to update the setting - this is crucial updateSetting('tmdbLanguagePreference', languageCode); // Clear any cached logo data await AsyncStorage.removeItem('_last_logos_'); // Show confirmation toast or feedback openAlert( 'TMDB Language Updated', `TMDB logo language preference set to ${languageCode.toUpperCase()}. Changes will apply when you navigate to content.` ); } catch (e) { logger.error(`[LogoSourceSettings] Error in saveLanguagePreference:`, e); // Show error notification openAlert( 'Error Saving Preference', 'There was a problem saving your language preference. Please try again.' ); } }; // Save selected show to AsyncStorage to persist across navigation const saveSelectedShow = async (show: typeof EXAMPLE_SHOWS[0]) => { try { await AsyncStorage.setItem('logo_settings_selected_show', show.imdbId); } catch (e) { if (__DEV__) console.error('Error saving selected show:', e); } }; // Load selected show from AsyncStorage on mount useEffect(() => { const loadSelectedShow = async () => { try { const savedShowId = await AsyncStorage.getItem('logo_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(); }, []); // Update selected show and save to AsyncStorage const handleShowSelect = (show: typeof EXAMPLE_SHOWS[0]) => { setSelectedShow(show); saveSelectedShow(show); }; // Handle back navigation const handleBack = () => { navigation.goBack(); }; // Render logo example with loading state and background const renderLogoExample = (logo: string | null, banner: string | null, isLoading: boolean) => { if (isLoading) { return ( ); } return ( {logo && ( )} {!logo && ( No logo available )} ); }; return ( {/* Header */} Settings {/* Empty for now, but ready for future actions */} Logo Source {/* Description */} Choose the primary source for content logos and backgrounds. The selected source will be used exclusively. {/* Show selector */} Select a show/movie to preview: {EXAMPLE_SHOWS.map((show) => ( handleShowSelect(show)} activeOpacity={0.7} delayPressIn={100} > {show.name} ))} {/* Options */} applyLogoSourceSetting('metahub')} activeOpacity={0.7} delayPressIn={100} > Metahub {logoSource === 'metahub' && ( )} High-quality logos from Metahub. Best for popular titles. Example: {renderLogoExample(metahubLogo, metahubBanner, loadingLogos)} {selectedShow.name} logo from Metahub applyLogoSourceSetting('tmdb')} activeOpacity={0.7} delayPressIn={100} > TMDB {logoSource === 'tmdb' && ( )} Logos from TMDB. Offers localized options and better coverage for recent content. Example: {renderLogoExample(tmdbLogo, tmdbBanner, loadingLogos)} {`Preview language: ${(previewLanguage || '').toUpperCase() || 'N/A'}${isPreviewFallback ? ' (fallback)' : ''}`} {selectedShow.name} logo from TMDB {/* TMDB Language Selector */} {true && ( Logo Language Select your preferred language for TMDB logos (includes common languages like Arabic even if not shown in this preview). {/* Merge unique languages from TMDB with a common list to ensure wider options */} {Array.from(new Set([...uniqueTmdbLanguages, ...COMMON_TMDB_LANGUAGES])).map((langCode) => ( handleTmdbLanguageSelect(langCode)} activeOpacity={0.7} delayPressIn={150} > {(langCode || '').toUpperCase() || '??'} ))} If unavailable in preferred language, English will be used as fallback. )} {/* Additional Info */} The app will use only the selected source for logos and backgrounds. If no image is available from your chosen source, a text fallback will be used. setAlertVisible(false)} actions={alertActions} /> ); }; export default LogoSourceSettings;