diff --git a/src/components/home/FeaturedContent.tsx b/src/components/home/FeaturedContent.tsx index da73b27e..f161d8dd 100644 --- a/src/components/home/FeaturedContent.tsx +++ b/src/components/home/FeaturedContent.tsx @@ -28,7 +28,10 @@ import Animated, { } from 'react-native-reanimated'; import { StreamingContent } from '../../services/catalogService'; import { SkeletonFeatured } from './SkeletonLoaders'; -import { isValidMetahubLogo, hasValidLogoFormat } from '../../utils/logoUtils'; +import { isValidMetahubLogo, hasValidLogoFormat, isMetahubUrl, isTmdbUrl } from '../../utils/logoUtils'; +import { useSettings } from '../../hooks/useSettings'; +import { TMDBService } from '../../services/tmdbService'; +import { logger } from '../../utils/logger'; interface FeaturedContentProps { featuredContent: StreamingContent | null; @@ -43,11 +46,14 @@ const { width, height } = Dimensions.get('window'); const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: FeaturedContentProps) => { const navigation = useNavigation>(); + const { settings } = useSettings(); const [logoUrl, setLogoUrl] = useState(null); const [bannerUrl, setBannerUrl] = useState(null); const prevContentIdRef = useRef(null); // Add state for tracking logo load errors const [logoLoadError, setLogoLoadError] = useState(false); + // Add a ref to track logo fetch in progress + const logoFetchInProgress = useRef(false); // Animation values const posterOpacity = useSharedValue(0); @@ -107,13 +113,171 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat useEffect(() => { setLogoLoadError(false); }, [featuredContent?.id]); + + // Fetch logo based on preference + useEffect(() => { + if (!featuredContent || logoFetchInProgress.current) return; + + const fetchLogo = async () => { + // Set fetch in progress flag + logoFetchInProgress.current = true; + + try { + const contentId = featuredContent.id; + + // Get logo source preference from settings + const logoPreference = settings.logoSourcePreference || 'metahub'; // Default to metahub if not set + + // Check if current logo matches preferences + const currentLogo = featuredContent.logo; + if (currentLogo) { + const isCurrentMetahub = isMetahubUrl(currentLogo); + const isCurrentTmdb = isTmdbUrl(currentLogo); + + // If logo already matches preference, use it + if ((logoPreference === 'metahub' && isCurrentMetahub) || + (logoPreference === 'tmdb' && isCurrentTmdb)) { + setLogoUrl(currentLogo); + logoFetchInProgress.current = false; + return; + } + } + + logger.log(`[FeaturedContent] Fetching logo with preference: ${logoPreference}, ID: ${contentId}`); + + // Extract IMDB ID if available + let imdbId = null; + if (featuredContent.id.startsWith('tt')) { + // If the ID itself is an IMDB ID + imdbId = featuredContent.id; + } else if ((featuredContent as any).imdbId) { + // Try to get IMDB ID from the content object if available + imdbId = (featuredContent as any).imdbId; + } + + // Extract TMDB ID if available + let tmdbId = null; + if (contentId.startsWith('tmdb:')) { + tmdbId = contentId.split(':')[1]; + } + + // First source based on preference + if (logoPreference === 'metahub' && imdbId) { + // Try to get logo from Metahub first + const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`; + + try { + const response = await fetch(metahubUrl, { method: 'HEAD' }); + if (response.ok) { + logger.log(`[FeaturedContent] Using Metahub logo: ${metahubUrl}`); + setLogoUrl(metahubUrl); + logoFetchInProgress.current = false; + return; // Exit if Metahub logo was found + } + } catch (error) { + logger.warn(`[FeaturedContent] Failed to fetch Metahub logo:`, error); + } + + // Fall back to TMDB if Metahub fails and we have a TMDB ID + if (tmdbId) { + const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie'; + try { + const tmdbService = TMDBService.getInstance(); + const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId); + + if (logoUrl) { + logger.log(`[FeaturedContent] Using fallback TMDB logo: ${logoUrl}`); + setLogoUrl(logoUrl); + } else if (currentLogo) { + // If TMDB fails too, use existing logo if any + setLogoUrl(currentLogo); + } + } catch (error) { + logger.error('[FeaturedContent] Error fetching TMDB logo:', error); + if (currentLogo) setLogoUrl(currentLogo); + } + } else if (currentLogo) { + // Use existing logo if we don't have TMDB ID + setLogoUrl(currentLogo); + } + } else if (logoPreference === 'tmdb') { + // Try to get logo from TMDB first + if (tmdbId) { + const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie'; + try { + const tmdbService = TMDBService.getInstance(); + const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId); + + if (logoUrl) { + logger.log(`[FeaturedContent] Using TMDB logo: ${logoUrl}`); + setLogoUrl(logoUrl); + logoFetchInProgress.current = false; + return; // Exit if TMDB logo was found + } + } catch (error) { + logger.error('[FeaturedContent] Error fetching TMDB logo:', error); + } + } else if (imdbId) { + // If we have IMDB ID but no TMDB ID, try to find TMDB ID + try { + const tmdbService = TMDBService.getInstance(); + const foundTmdbId = await tmdbService.findTMDBIdByIMDB(imdbId); + + if (foundTmdbId) { + const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie'; + const logoUrl = await tmdbService.getContentLogo(tmdbType, foundTmdbId.toString()); + + if (logoUrl) { + logger.log(`[FeaturedContent] Using TMDB logo via IMDB lookup: ${logoUrl}`); + setLogoUrl(logoUrl); + logoFetchInProgress.current = false; + return; // Exit if TMDB logo was found + } + } + } catch (error) { + logger.error('[FeaturedContent] Error finding TMDB ID from IMDB:', error); + } + } + + // Fall back to Metahub if TMDB fails and we have an IMDB ID + if (imdbId) { + const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`; + + try { + const response = await fetch(metahubUrl, { method: 'HEAD' }); + if (response.ok) { + logger.log(`[FeaturedContent] Using fallback Metahub logo: ${metahubUrl}`); + setLogoUrl(metahubUrl); + } else if (currentLogo) { + // If Metahub fails too, use existing logo if any + setLogoUrl(currentLogo); + } + } catch (error) { + logger.warn(`[FeaturedContent] Failed to fetch fallback Metahub logo:`, error); + if (currentLogo) setLogoUrl(currentLogo); + } + } else if (currentLogo) { + // Use existing logo if we don't have IMDB ID + setLogoUrl(currentLogo); + } + } + } catch (error) { + logger.error('[FeaturedContent] Error fetching logo:', error); + if (featuredContent?.logo) setLogoUrl(featuredContent.logo); + } finally { + // Clear fetch in progress flag + logoFetchInProgress.current = false; + } + }; + + fetchLogo(); + }, [featuredContent?.id, settings.logoSourcePreference]); // Load poster and logo useEffect(() => { if (!featuredContent) return; const posterUrl = featuredContent.banner || featuredContent.poster; - const titleLogo = featuredContent.logo; const contentId = featuredContent.id; // Reset states for new content @@ -124,9 +288,8 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat prevContentIdRef.current = contentId; - // Set URLs immediately for instant display + // Set poster URL immediately for instant display if (posterUrl) setBannerUrl(posterUrl); - if (titleLogo) setLogoUrl(titleLogo); // Load images in background const loadImages = async () => { @@ -142,8 +305,8 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat } // Load logo if available - if (titleLogo) { - const logoSuccess = await preloadImage(titleLogo); + if (logoUrl) { + const logoSuccess = await preloadImage(logoUrl); if (logoSuccess) { logoOpacity.value = withDelay(300, withTiming(1, { duration: 500, @@ -152,13 +315,13 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat } else { // If prefetch fails, mark as error to show title text instead setLogoLoadError(true); - console.warn(`[FeaturedContent] Logo prefetch failed, falling back to text: ${titleLogo}`); + console.warn(`[FeaturedContent] Logo prefetch failed, falling back to text: ${logoUrl}`); } } }; loadImages(); - }, [featuredContent?.id]); + }, [featuredContent?.id, logoUrl]); if (!featuredContent) { return ; @@ -194,16 +357,16 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat - {featuredContent.logo && !logoLoadError ? ( + {logoUrl && !logoLoadError ? ( { - console.warn(`[FeaturedContent] Logo failed to load: ${featuredContent.logo}`); + console.warn(`[FeaturedContent] Logo failed to load: ${logoUrl}`); setLogoLoadError(true); }} /> diff --git a/src/screens/LogoSourceSettings.tsx b/src/screens/LogoSourceSettings.tsx index 30bb1ab0..e27ee9e0 100644 --- a/src/screens/LogoSourceSettings.tsx +++ b/src/screens/LogoSourceSettings.tsx @@ -11,7 +11,7 @@ import { Alert, StatusBar, Platform, - ActivityIndicator + ActivityIndicator, } from 'react-native'; import { NavigationProp, useNavigation } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; @@ -25,6 +25,52 @@ import { logger } from '../utils/logger'; // TMDB API key - since the default key might be private in the service, we'll use our own 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: '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 + } +]; + const LogoSourceSettings = () => { const { settings, updateSetting } = useSettings(); const navigation = useNavigation>(); @@ -35,117 +81,125 @@ const LogoSourceSettings = () => { settings.logoSourcePreference || 'metahub' ); - // Add state for example logos + // 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); - // Load example logos on mount + // Load example logos for selected show useEffect(() => { - const fetchExampleLogos = async () => { - setLoadingLogos(true); - - try { - const tmdbService = TMDBService.getInstance(); - - // Specifically search for Breaking Bad - const searchResults = await tmdbService.searchTVShow("Breaking Bad"); - - if (searchResults && searchResults.length > 0) { - // Get Breaking Bad (should be the first result) - const breakingBad = searchResults[0]; - const breakingBadId = breakingBad.id; - - logger.log(`[LogoSourceSettings] Found Breaking Bad with TMDB ID: ${breakingBadId}`); - - // Get the external IDs to get IMDB ID - const externalIds = await tmdbService.getShowExternalIds(breakingBadId); - - if (externalIds?.imdb_id) { - const imdbId = externalIds.imdb_id; - logger.log(`[LogoSourceSettings] Breaking Bad IMDB ID: ${imdbId}`); - - // Get TMDB logo using the images endpoint - try { - // Manually fetch images from TMDB API - const apiKey = TMDB_API_KEY; // Use the TMDB API key - const response = await fetch(`https://api.themoviedb.org/3/tv/${breakingBadId}/images?api_key=${apiKey}`); - const imagesData = await response.json(); - - if (imagesData.logos && imagesData.logos.length > 0) { - // Look for English logo first - let logoPath = null; - - // First try to find an English logo - const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => - logo.iso_639_1 === 'en' - ); - if (englishLogo) { - logoPath = englishLogo.file_path; - } else if (imagesData.logos[0]) { - // Fallback to the first logo - logoPath = imagesData.logos[0].file_path; - } - - if (logoPath) { - const tmdbLogoUrl = `https://image.tmdb.org/t/p/original${logoPath}`; - setTmdbLogo(tmdbLogoUrl); - logger.log(`[LogoSourceSettings] Got Breaking Bad TMDB logo: ${tmdbLogoUrl}`); - } else { - // Fallback to hardcoded Breaking Bad TMDB logo - setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png'); - logger.log(`[LogoSourceSettings] Using fallback Breaking Bad TMDB logo`); - } - } else { - // No logos found in the response - setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png'); - logger.log(`[LogoSourceSettings] No logos found in TMDB response, using fallback`); - } - } catch (tmdbError) { - logger.error(`[LogoSourceSettings] Error fetching TMDB images:`, tmdbError); - // Fallback to hardcoded Breaking Bad TMDB logo - setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png'); - } - - // Get Metahub logo - const metahubLogoUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`; - - // Check if Metahub logo exists - try { - const metahubResponse = await fetch(metahubLogoUrl, { method: 'HEAD' }); - if (metahubResponse.ok) { - setMetahubLogo(metahubLogoUrl); - logger.log(`[LogoSourceSettings] Got Breaking Bad Metahub logo: ${metahubLogoUrl}`); - } else { - // Fallback to hardcoded Breaking Bad Metahub logo - setMetahubLogo('https://images.metahub.space/logo/medium/tt0903747/img'); - logger.log(`[LogoSourceSettings] Using fallback Breaking Bad Metahub logo`); - } - } catch (metahubErr) { - logger.error(`[LogoSourceSettings] Error checking Metahub logo:`, metahubErr); - // Fallback to hardcoded Breaking Bad Metahub logo - setMetahubLogo('https://images.metahub.space/logo/medium/tt0903747/img'); - } - } - } else { - logger.warn(`[LogoSourceSettings] Breaking Bad not found in search results`); - // Use hardcoded Breaking Bad logos - setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png'); - setMetahubLogo('https://images.metahub.space/logo/medium/tt0903747/img'); - } - } catch (err) { - logger.error('[LogoSourceSettings] Error fetching Breaking Bad logos:', err); - - // Use hardcoded Breaking Bad logos - setTmdbLogo('https://image.tmdb.org/t/p/original/ggFHVNu6YYI5L9pCfOacjizRGt.png'); - setMetahubLogo('https://images.metahub.space/logo/medium/tt0903747/img'); - } finally { - setLoadingLogos(false); - } - }; + 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); - fetchExampleLogos(); - }, []); + 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 TMDB logo and banner + try { + // Manually fetch images from TMDB API + 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(); + + // Get TMDB logo + if (imagesData.logos && imagesData.logos.length > 0) { + // Look for English logo first + let logoPath = null; + + // First try to find an English logo + const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => + logo.iso_639_1 === 'en' + ); + if (englishLogo) { + logoPath = englishLogo.file_path; + } else if (imagesData.logos[0]) { + // Fallback to the first logo + logoPath = imagesData.logos[0].file_path; + } + + if (logoPath) { + const tmdbLogoUrl = `https://image.tmdb.org/t/p/original${logoPath}`; + setTmdbLogo(tmdbLogoUrl); + logger.log(`[LogoSourceSettings] Got ${show.name} TMDB logo: ${tmdbLogoUrl}`); + } + } + + // 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 setting and show confirmation const applyLogoSourceSetting = (source: 'metahub' | 'tmdb') => { @@ -167,13 +221,47 @@ const LogoSourceSettings = () => { ); }; + // 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) { + 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) { + 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 - const renderLogoExample = (url: string | null, isLoading: boolean) => { + // Render logo example with loading state and background + const renderLogoExample = (logo: string | null, banner: string | null, isLoading: boolean) => { if (isLoading) { return ( @@ -183,11 +271,26 @@ const LogoSourceSettings = () => { } return ( - + + + + {logo && ( + + )} + {!logo && ( + + No logo available + + )} + ); }; @@ -217,6 +320,36 @@ const LogoSourceSettings = () => { + {/* Show selector */} + + Select a show/movie to preview: + + {EXAMPLE_SHOWS.map((show) => ( + handleShowSelect(show)} + > + + {show.name} + + + ))} + + + {/* Options */} { Example: - {renderLogoExample(metahubLogo, loadingLogos)} - Breaking Bad logo from Metahub + {renderLogoExample(metahubLogo, metahubBanner, loadingLogos)} + {selectedShow.name} logo from Metahub @@ -266,8 +399,8 @@ const LogoSourceSettings = () => { Example: - {renderLogoExample(tmdbLogo, loadingLogos)} - Breaking Bad logo from TMDB + {renderLogoExample(tmdbLogo, tmdbBanner, loadingLogos)} + {selectedShow.name} logo from TMDB @@ -324,6 +457,39 @@ const styles = StyleSheet.create({ fontSize: 16, lineHeight: 24, }, + showSelectorContainer: { + padding: 16, + paddingBottom: 8, + }, + selectorLabel: { + color: colors.text, + fontSize: 16, + marginBottom: 12, + }, + showsScrollContent: { + paddingRight: 16, + }, + showItem: { + paddingHorizontal: 16, + paddingVertical: 8, + backgroundColor: colors.elevation2, + borderRadius: 20, + marginRight: 8, + borderWidth: 1, + borderColor: 'transparent', + }, + selectedShowItem: { + borderColor: colors.primary, + backgroundColor: colors.elevation3, + }, + showItemText: { + color: colors.mediumEmphasis, + fontSize: 14, + }, + selectedShowItemText: { + color: colors.white, + fontWeight: '600', + }, optionsContainer: { padding: 16, gap: 16, @@ -391,6 +557,44 @@ const styles = StyleSheet.create({ fontSize: 12, marginTop: 4, }, + bannerContainer: { + height: 120, + width: '100%', + borderRadius: 8, + overflow: 'hidden', + position: 'relative', + }, + bannerImage: { + ...StyleSheet.absoluteFillObject, + }, + bannerOverlay: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0,0,0,0.5)', + }, + logoOverBanner: { + position: 'absolute', + width: '80%', + height: '80%', + alignSelf: 'center', + top: '10%', + }, + 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, + }, }); export default LogoSourceSettings; \ No newline at end of file diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index 16e8c93d..db02a12f 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -222,6 +222,7 @@ const MetadataScreen = () => { // Add state for custom banner const [bannerImage, setBannerImage] = useState(null); const forcedBannerRefreshDone = useRef(false); + const [loadingBanner, setLoadingBanner] = useState(false); // Add debug log for settings when component mounts useEffect(() => { @@ -278,46 +279,147 @@ const MetadataScreen = () => { // Fetch banner image based on logo source preference useEffect(() => { const fetchBanner = async () => { - if (metadata && (!forcedBannerRefreshDone.current || foundTmdbId)) { - // Extract any existing TMDB ID if available - let tmdbId = null; + if (metadata) { + setLoadingBanner(true); + + // Clear the banner initially when starting a preference-driven fetch + setBannerImage(null); + + let finalBanner: string | null = metadata.banner || metadata.poster; // Default fallback + const preference = settings.logoSourcePreference || 'metahub'; + const apiKey = '439c478a771f35c05022f9feabcca01c'; // Re-using API key + + // Extract IDs + let currentTmdbId = null; if (id.startsWith('tmdb:')) { - tmdbId = id.split(':')[1]; + currentTmdbId = id.split(':')[1]; + } else if (foundTmdbId) { + currentTmdbId = foundTmdbId; + } else if ((metadata as any).tmdbId) { + currentTmdbId = (metadata as any).tmdbId; } - // Use our stored TMDB ID if we have one - const effectiveTmdbId = foundTmdbId || tmdbId || (metadata as any).tmdbId; + const currentImdbId = imdbId; + const contentType = type === 'series' ? 'tv' : 'movie'; - logger.log(`[MetadataScreen] Fetching banner with preference: ${settings.logoSourcePreference}, TMDB ID: ${effectiveTmdbId}`); + logger.log(`[MetadataScreen] Fetching banner with preference: ${preference}, TMDB ID: ${currentTmdbId}, IMDB ID: ${currentImdbId}`); try { - // Use our utility function to get the banner based on preference - const newBanner = await fetchBannerWithPreference( - imdbId, - effectiveTmdbId, - type as 'movie' | 'series', - settings.logoSourcePreference - ); - - if (newBanner) { - logger.log(`[MetadataScreen] Setting new banner: ${newBanner}`); - setBannerImage(newBanner); - } else { - // If no banner found from preferred source, use the existing one from metadata - logger.log(`[MetadataScreen] Using existing banner from metadata: ${metadata.banner}`); - setBannerImage(metadata.banner || metadata.poster); + if (preference === 'tmdb') { + // 1. Try TMDB first + let tmdbBannerUrl: string | null = null; + if (currentTmdbId) { + logger.log(`[MetadataScreen] Attempting TMDB banner fetch with ID: ${currentTmdbId}`); + try { + const endpoint = contentType === 'tv' ? 'tv' : 'movie'; + const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${currentTmdbId}/images?api_key=${apiKey}`); + const imagesData = await response.json(); + + if (imagesData.backdrops && imagesData.backdrops.length > 0) { + const backdropPath = imagesData.backdrops[0].file_path; + tmdbBannerUrl = `https://image.tmdb.org/t/p/original${backdropPath}`; + logger.log(`[MetadataScreen] Found TMDB banner via images endpoint: ${tmdbBannerUrl}`); + } else { + // Add log for when no backdrops are found + logger.warn(`[MetadataScreen] TMDB API successful, but no backdrops found for ID: ${currentTmdbId}`); + } + } catch (err) { + logger.error(`[MetadataScreen] Error fetching TMDB banner via images endpoint:`, err); + } + } else { + // Add log for when no TMDB ID is available + logger.warn(`[MetadataScreen] No TMDB ID available to fetch TMDB banner.`); + } + + if (tmdbBannerUrl) { + // TMDB SUCCESS: Set banner and EXIT + finalBanner = tmdbBannerUrl; + logger.log(`[MetadataScreen] Setting final banner to TMDB source: ${finalBanner}`); + setBannerImage(finalBanner); + setLoadingBanner(false); + forcedBannerRefreshDone.current = true; + return; // <-- Exit here, don't attempt fallback + } else { + // TMDB FAILED: Proceed to Metahub fallback + logger.log(`[MetadataScreen] TMDB banner failed, trying Metahub fallback.`); + if (currentImdbId) { + const metahubBannerUrl = `https://images.metahub.space/background/medium/${currentImdbId}/img`; + try { + const metahubResponse = await fetch(metahubBannerUrl, { method: 'HEAD' }); + if (metahubResponse.ok) { + finalBanner = metahubBannerUrl; + logger.log(`[MetadataScreen] Found Metahub banner as fallback: ${finalBanner}`); + } + } catch (err) { + logger.error(`[MetadataScreen] Error fetching Metahub fallback banner:`, err); + } + } + } + } else { // Preference is Metahub + // 1. Try Metahub first + let metahubBannerUrl: string | null = null; + if (currentImdbId) { + const url = `https://images.metahub.space/background/medium/${currentImdbId}/img`; + try { + const metahubResponse = await fetch(url, { method: 'HEAD' }); + if (metahubResponse.ok) { + metahubBannerUrl = url; + logger.log(`[MetadataScreen] Found Metahub banner: ${metahubBannerUrl}`); + } + } catch (err) { + logger.error(`[MetadataScreen] Error fetching Metahub banner:`, err); + } + } + + if (metahubBannerUrl) { + // METAHUB SUCCESS: Set banner and EXIT + finalBanner = metahubBannerUrl; + logger.log(`[MetadataScreen] Setting final banner to Metahub source: ${finalBanner}`); + setBannerImage(finalBanner); + setLoadingBanner(false); + forcedBannerRefreshDone.current = true; + return; // <-- Exit here, don't attempt fallback + } else { + // METAHUB FAILED: Proceed to TMDB fallback + logger.log(`[MetadataScreen] Metahub banner failed, trying TMDB fallback.`); + if (currentTmdbId) { + try { + const endpoint = contentType === 'tv' ? 'tv' : 'movie'; + const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${currentTmdbId}/images?api_key=${apiKey}`); + const imagesData = await response.json(); + + if (imagesData.backdrops && imagesData.backdrops.length > 0) { + const backdropPath = imagesData.backdrops[0].file_path; + finalBanner = `https://image.tmdb.org/t/p/original${backdropPath}`; + logger.log(`[MetadataScreen] Found TMDB banner as fallback: ${finalBanner}`); + } + } catch (err) { + logger.error(`[MetadataScreen] Error fetching TMDB fallback banner:`, err); + } + } + } } + + // Set the final determined banner (could be fallback or initial default) + setBannerImage(finalBanner); + logger.log(`[MetadataScreen] Final banner set after fallbacks (if any): ${finalBanner}`); + } catch (error) { - logger.error(`[MetadataScreen] Error fetching banner:`, error); - // Use existing banner as fallback + logger.error(`[MetadataScreen] General error fetching banner:`, error); + // Fallback to initial banner on general error setBannerImage(metadata.banner || metadata.poster); + } finally { + // Only set loading to false here if we didn't exit early + setLoadingBanner(false); + forcedBannerRefreshDone.current = true; // Mark refresh as done } - - forcedBannerRefreshDone.current = true; } }; + // Only run fetchBanner if metadata exists and preference/content might have changed + // The dependencies array handles triggering this effect fetchBanner(); + }, [metadata, id, type, imdbId, settings.logoSourcePreference, foundTmdbId]); // Reset forced refresh when preference changes @@ -325,7 +427,7 @@ const MetadataScreen = () => { if (forcedBannerRefreshDone.current) { logger.log(`[MetadataScreen] Logo preference changed, resetting banner refresh flag`); forcedBannerRefreshDone.current = false; - // Clear the banner image to force a new fetch + // Clear the banner image immediately to prevent showing the wrong source briefly setBannerImage(null); // This will trigger the banner fetch effect to run again } @@ -582,102 +684,84 @@ const MetadataScreen = () => { } } } else { // TMDB first - // Try to get logo from TMDB first - let tmdbId = null; - const tmdbType = type === 'series' ? 'tv' : 'movie'; + let tmdbLogoUrl: string | null = null; + // 1. Attempt to fetch TMDB logo if (id.startsWith('tmdb:')) { - // Direct TMDB ID - tmdbId = id.split(':')[1]; - logger.log(`[MetadataScreen] Content has direct TMDB ID: ${tmdbId}`); - } else if (id.startsWith('tt')) { - // IMDB ID - need to find the corresponding TMDB ID - logger.log(`[MetadataScreen] Content has IMDB ID (${id}), looking up TMDB ID`); - try { - // Use the passed imdbId if available, otherwise use id directly - const imdbIdToUse = imdbId || id; - logger.log(`[MetadataScreen] Using IMDB ID for lookup: ${imdbIdToUse}`); - - tmdbId = await TMDBService.getInstance().findTMDBIdByIMDB(imdbIdToUse); - if (tmdbId) { - logger.log(`[MetadataScreen] Found TMDB ID ${tmdbId} for IMDB ID ${imdbIdToUse}`); - - // Save the TMDB ID for banner fetching - setFoundTmdbId(String(tmdbId)); - } else { - logger.warn(`[MetadataScreen] Could not find TMDB ID for IMDB ID ${imdbIdToUse}`); - } - } catch (error) { - logger.error(`[MetadataScreen] Error finding TMDB ID for IMDB ID ${id}:`, error); - } - } - - if (tmdbId) { + const tmdbId = id.split(':')[1]; + const tmdbType = type === 'series' ? 'tv' : 'movie'; logger.log(`[MetadataScreen] Attempting to fetch logo from TMDB for ${tmdbType} (ID: ${tmdbId})`); - try { const tmdbService = TMDBService.getInstance(); - logger.log(`[MetadataScreen] Calling getContentLogo with type=${tmdbType}, id=${tmdbId}`); + tmdbLogoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId); - const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId); - - if (logoUrl) { - logger.log(`[MetadataScreen] Successfully fetched logo from TMDB: - - Content Type: ${tmdbType} - - TMDB ID: ${tmdbId} - - Logo URL: ${logoUrl} - `); - - // Update metadata with TMDB logo - setMetadata(prevMetadata => ({ - ...prevMetadata!, - logo: logoUrl - })); - - // Clear fetch in progress flag when done - logoFetchInProgress.current = false; - return; // Exit if TMDB logo was found + if (tmdbLogoUrl) { + logger.log(`[MetadataScreen] Successfully fetched logo from TMDB: ${tmdbLogoUrl}`); } else { - logger.warn(`[MetadataScreen] No logo found from TMDB for ${type} (ID: ${tmdbId}), trying Metahub`); + logger.warn(`[MetadataScreen] No logo found from TMDB for ${type} (ID: ${tmdbId})`); } } catch (error) { logger.error(`[MetadataScreen] Error fetching TMDB logo for ID ${tmdbId}:`, error); } - } else { - logger.warn(`[MetadataScreen] No TMDB ID available, falling back to Metahub`); + } else if (imdbId) { + // If we have IMDB ID but no direct TMDB ID, try to find TMDB ID + logger.log(`[MetadataScreen] Content has IMDB ID (${imdbId}), looking up TMDB ID for TMDB logo`); + try { + const tmdbService = TMDBService.getInstance(); + const foundTmdbId = await tmdbService.findTMDBIdByIMDB(imdbId); + + if (foundTmdbId) { + logger.log(`[MetadataScreen] Found TMDB ID ${foundTmdbId} for IMDB ID ${imdbId}`); + setFoundTmdbId(String(foundTmdbId)); // Save for banner fetching + + tmdbLogoUrl = await tmdbService.getContentLogo(type === 'series' ? 'tv' : 'movie', foundTmdbId.toString()); + + if (tmdbLogoUrl) { + logger.log(`[MetadataScreen] Successfully fetched logo from TMDB via IMDB lookup: ${tmdbLogoUrl}`); + } else { + logger.warn(`[MetadataScreen] No logo found from TMDB via IMDB lookup for ${type} (IMDB: ${imdbId})`); + } + } else { + logger.warn(`[MetadataScreen] Could not find TMDB ID for IMDB ID ${imdbId}`); + } + } catch (error) { + logger.error(`[MetadataScreen] Error finding TMDB ID or fetching logo for IMDB ID ${imdbId}:`, error); + } } - // If TMDB fails or isn't a TMDB ID, try Metahub as fallback + // 2. If TMDB logo was fetched successfully, update and return + if (tmdbLogoUrl) { + setMetadata(prevMetadata => ({ + ...prevMetadata!, + logo: tmdbLogoUrl + })); + logoFetchInProgress.current = false; + return; + } + + // 3. If TMDB failed, try Metahub as fallback + logger.log(`[MetadataScreen] TMDB logo fetch failed or not applicable. Attempting Metahub fallback.`); if (imdbId) { const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`; - logger.log(`[MetadataScreen] Attempting to fetch logo from Metahub as fallback for ${imdbId}`); try { const response = await fetch(metahubUrl, { method: 'HEAD' }); if (response.ok) { - logger.log(`[MetadataScreen] Successfully fetched fallback logo from Metahub: - - Content ID: ${id} - - Content Type: ${type} - - Logo URL: ${metahubUrl} - `); - - // Update metadata with Metahub logo - setMetadata(prevMetadata => ({ - ...prevMetadata!, - logo: metahubUrl - })); + logger.log(`[MetadataScreen] Successfully fetched fallback logo from Metahub: ${metahubUrl}`); + setMetadata(prevMetadata => ({ ...prevMetadata!, logo: metahubUrl })); } else { - // If both TMDB and Metahub fail, use the title as text instead of a logo - logger.warn(`[MetadataScreen] No logo found from either source for ${type} (ID: ${id}), using title text instead`); - - // Leave logo as null/undefined to trigger fallback to text + logger.warn(`[MetadataScreen] Metahub fallback failed. Using title text.`); + setMetadata(prevMetadata => ({ ...prevMetadata!, logo: undefined })); } } catch (metahubError) { - logger.warn(`[MetadataScreen] Failed to fetch logo from Metahub:`, metahubError); - - // Leave logo as null/undefined to trigger fallback to text + logger.warn(`[MetadataScreen] Failed to fetch fallback logo from Metahub:`, metahubError); + setMetadata(prevMetadata => ({ ...prevMetadata!, logo: undefined })); } + } else { + // No IMDB ID for Metahub fallback + logger.warn(`[MetadataScreen] No IMDB ID for Metahub fallback. Using title text.`); + setMetadata(prevMetadata => ({ ...prevMetadata!, logo: undefined })); } } } catch (error) { @@ -686,6 +770,8 @@ const MetadataScreen = () => { contentId: id, contentType: type }); + // Fallback to text on general error + setMetadata(prevMetadata => ({ ...prevMetadata!, logo: undefined })); } finally { // Clear fetch in progress flag when done logoFetchInProgress.current = false; @@ -1484,18 +1570,22 @@ const MetadataScreen = () => { {/* Use Animated.Image directly instead of ImageBackground with imageStyle */} - { - logger.warn(`[MetadataScreen] Banner failed to load: ${bannerImage}`); - // If custom banner fails, fall back to original metadata banner - if (bannerImage !== metadata.banner) { - setBannerImage(metadata.banner || metadata.poster); - } - }} - /> + {loadingBanner ? ( + + ) : ( + { + logger.warn(`[MetadataScreen] Banner failed to load: ${bannerImage}`); + // If custom banner fails, fall back to original metadata banner + if (bannerImage !== metadata.banner) { + setBannerImage(metadata.banner || metadata.poster); + } + }} + /> + )}