diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 1899269e..576ff3a4 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -32,6 +32,7 @@ export interface AppSettings { showHeroSection: boolean; featuredContentSource: 'tmdb' | 'catalogs'; selectedHeroCatalogs: string[]; // Array of catalog IDs to display in hero section + logoSourcePreference: 'metahub' | 'tmdb'; // Preferred source for title logos } export const DEFAULT_SETTINGS: AppSettings = { @@ -46,6 +47,7 @@ export const DEFAULT_SETTINGS: AppSettings = { showHeroSection: true, featuredContentSource: 'tmdb', selectedHeroCatalogs: [], // Empty array means all catalogs are selected + logoSourcePreference: 'metahub', // Default to Metahub as first source }; const SETTINGS_STORAGE_KEY = 'app_settings'; diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index 246fdfe7..bcc3dd52 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -57,6 +57,7 @@ import { storageService } from '../services/storageService'; import { logger } from '../utils/logger'; import { useGenres } from '../contexts/GenreContext'; import { isValidMetahubLogo, isMetahubUrl, isTmdbUrl } from '../utils/logoUtils'; +import { useSettings } from '../hooks/useSettings'; const { width, height } = Dimensions.get('window'); @@ -211,6 +212,17 @@ const MetadataScreen = () => { const route = useRoute, string>>(); const navigation = useNavigation>(); const { id, type, episodeId } = route.params; + + // Add settings hook + const { settings } = useSettings(); + + // Add a flag to track if we need to do a forced initial logo refresh + const forcedLogoRefreshDone = useRef(false); + + // Add debug log for settings when component mounts + useEffect(() => { + logger.log(`[MetadataScreen] Component mounted with logo preference setting: ${settings.logoSourcePreference}`); + }, [settings.logoSourcePreference]); const { metadata, @@ -232,6 +244,30 @@ const MetadataScreen = () => { imdbId, } = useMetadata({ id, type }); + // Force an initial logo refresh when component mounts and logo doesn't match preference + useEffect(() => { + if (metadata?.logo && !forcedLogoRefreshDone.current) { + const currentLogoIsMetahub = isMetahubUrl(metadata.logo); + const currentLogoIsTmdb = isTmdbUrl(metadata.logo); + const preferenceIsMetahub = settings.logoSourcePreference === 'metahub'; + + // Check if logo source doesn't match preference + if ((preferenceIsMetahub && !currentLogoIsMetahub) || + (!preferenceIsMetahub && !currentLogoIsTmdb)) { + logger.log(`[MetadataScreen] Initial load: Logo source doesn't match preference. Forcing refresh.`); + + // Clear logo to force a new fetch according to preference + setMetadata(prevMetadata => ({ + ...prevMetadata!, + logo: undefined + })); + } + + // Mark that we've checked this so we don't endlessly loop + forcedLogoRefreshDone.current = true; + } + }, [metadata?.logo, settings.logoSourcePreference, setMetadata]); + // Get genres from context const { genreMap, loadingGenres } = useGenres(); @@ -273,6 +309,76 @@ const MetadataScreen = () => { setLogoLoadError(false); }, [metadata?.logo]); + // Add a ref to track logo fetch in progress + const logoFetchInProgress = useRef(false); + + // Add a ref to track logo preference changes to prevent infinite loops + const logoRefreshCounter = useRef(0); + const MAX_LOGO_REFRESHES = 2; + + // Force refresh logo when logo preference changes - only when preference actually changes + useEffect(() => { + // Reset the counter when preference actually changes + if (logoRefreshCounter.current === 0) { + logoRefreshCounter.current = 1; // Mark that we've started a refresh cycle + + // Only clear logo if we already have metadata with a logo + if (metadata?.logo) { + // Check if the current logo source doesn't match the preference + const currentLogoIsMetahub = isMetahubUrl(metadata.logo); + const currentLogoIsTmdb = isTmdbUrl(metadata.logo); + const preferenceIsMetahub = settings.logoSourcePreference === 'metahub'; + + // Only refresh if the current logo doesn't match the preference + if ((preferenceIsMetahub && !currentLogoIsMetahub) || + (!preferenceIsMetahub && !currentLogoIsTmdb)) { + logger.log(`[MetadataScreen] Logo preference (${settings.logoSourcePreference}) doesn't match current logo source, refreshing`); + + // Prevent endless refreshes + if (logoRefreshCounter.current < MAX_LOGO_REFRESHES) { + logoRefreshCounter.current++; + setMetadata(prevMetadata => ({ + ...prevMetadata!, + logo: undefined + })); + } else { + logger.warn(`[MetadataScreen] Maximum logo refreshes (${MAX_LOGO_REFRESHES}) reached, stopping to prevent loop`); + } + } else { + logger.log(`[MetadataScreen] Logo source already matches preference, no refresh needed`); + logoRefreshCounter.current = 0; // Reset for future changes + } + } + } else { + logoRefreshCounter.current++; + logger.log(`[MetadataScreen] Logo refresh already in progress (${logoRefreshCounter.current}/${MAX_LOGO_REFRESHES})`); + + // Reset counter after max refreshes to allow future preference changes to work + if (logoRefreshCounter.current >= MAX_LOGO_REFRESHES) { + logger.warn(`[MetadataScreen] Maximum refreshes reached, resetting counter`); + // After a timeout to avoid immediate re-triggering + setTimeout(() => { + logoRefreshCounter.current = 0; + }, 1000); + } + } + }, [settings.logoSourcePreference, metadata?.logo, setMetadata]); + + // Add effect to track when logo source matches preference + useEffect(() => { + if (metadata?.logo) { + const currentLogoIsMetahub = isMetahubUrl(metadata.logo); + const currentLogoIsTmdb = isTmdbUrl(metadata.logo); + const preferenceIsMetahub = settings.logoSourcePreference === 'metahub'; + + if ((preferenceIsMetahub && currentLogoIsMetahub) || + (!preferenceIsMetahub && currentLogoIsTmdb)) { + logger.log('[MetadataScreen] Logo source now matches preference, refresh complete'); + logoRefreshCounter.current = 0; // Reset counter since we've achieved our goal + } + } + }, [metadata?.logo, settings.logoSourcePreference]); + // Add wrapper for toggleLibrary that includes haptic feedback const handleToggleLibrary = useCallback(() => { // Trigger appropriate haptic feedback based on action @@ -323,71 +429,171 @@ const MetadataScreen = () => { // Debug log for route params // logger.log('[MetadataScreen] Component mounted with route params:', { id, type, episodeId }); - // Fetch logo immediately for TMDB content + // Fetch logo immediately for TMDB content - with guard against recursive updates useEffect(() => { - if (metadata && !metadata.logo) { + // Guard against infinite loops by checking if we're already fetching + if (metadata && !metadata.logo && !logoFetchInProgress.current) { + console.log('[MetadataScreen] Current settings:', JSON.stringify(settings)); + console.log('[MetadataScreen] Current metadata:', JSON.stringify(metadata, null, 2)); + const fetchLogo = async () => { + // Set fetch in progress flag + logoFetchInProgress.current = true; + try { - // First try to get logo from Metahub - const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`; + // Get logo source preference from settings + const logoPreference = settings.logoSourcePreference || 'metahub'; // Default to metahub if not set - logger.log(`[MetadataScreen] Attempting to fetch logo from Metahub for ${imdbId}`); + console.log(`[MetadataScreen] Using logo preference: ${logoPreference}, TMDB first: ${logoPreference === 'tmdb'}`); + logger.log(`[MetadataScreen] Logo source preference: ${logoPreference}`); - // For now, skip detailed validation and just check if URL is accessible - try { - const response = await fetch(metahubUrl, { method: 'HEAD' }); - if (response.ok) { - logger.log(`[MetadataScreen] Successfully fetched logo from Metahub: - - Content ID: ${id} - - Content Type: ${type} - - Logo URL: ${metahubUrl} - `); - - // Update metadata with Metahub logo - setMetadata(prevMetadata => ({ - ...prevMetadata!, - logo: metahubUrl - })); - return; // Exit if Metahub logo was found - } else { - logger.warn(`[MetadataScreen] Metahub logo request failed with status ${response.status}`); + // First source based on preference + if (logoPreference === 'metahub') { + // Try to get logo from Metahub first + const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`; + + logger.log(`[MetadataScreen] Attempting to fetch logo from Metahub for ${imdbId}`); + + try { + const response = await fetch(metahubUrl, { method: 'HEAD' }); + if (response.ok) { + logger.log(`[MetadataScreen] Successfully fetched logo from Metahub: + - Content ID: ${id} + - Content Type: ${type} + - Logo URL: ${metahubUrl} + `); + + // Update metadata with Metahub logo + setMetadata(prevMetadata => ({ + ...prevMetadata!, + logo: metahubUrl + })); + return; // Exit if Metahub logo was found + } else { + logger.warn(`[MetadataScreen] Metahub logo request failed with status ${response.status}`); + } + } catch (metahubError) { + logger.warn(`[MetadataScreen] Failed to fetch logo from Metahub:`, metahubError); } - } catch (metahubError) { - logger.warn(`[MetadataScreen] Failed to fetch logo from Metahub:`, metahubError); - } - - // If Metahub fails, try TMDB as fallback - if (id.startsWith('tmdb:')) { - const tmdbId = id.split(':')[1]; + + // If Metahub fails, try TMDB as fallback + if (id.startsWith('tmdb:')) { + const tmdbId = id.split(':')[1]; + const tmdbType = type === 'series' ? 'tv' : 'movie'; + + logger.log(`[MetadataScreen] Attempting to fetch logo from TMDB as fallback for ${tmdbType} (ID: ${tmdbId})`); + + const logoUrl = await TMDBService.getInstance().getContentLogo(tmdbType, tmdbId); + + if (logoUrl) { + logger.log(`[MetadataScreen] Successfully fetched fallback logo from TMDB: + - Content Type: ${tmdbType} + - TMDB ID: ${tmdbId} + - Logo URL: ${logoUrl} + `); + + // Update metadata with TMDB logo + setMetadata(prevMetadata => ({ + ...prevMetadata!, + logo: logoUrl + })); + } else { + // If both Metahub and TMDB fail, use the title as text instead of a logo + logger.warn(`[MetadataScreen] No logo found from either Metahub or TMDB for ${type} (ID: ${id}), using title text instead`); + + // Leave logo as null/undefined to trigger fallback to text + } + } + } else { // TMDB first + // Try to get logo from TMDB first + let tmdbId = null; const tmdbType = type === 'series' ? 'tv' : 'movie'; - logger.log(`[MetadataScreen] Attempting to fetch logo from TMDB as fallback for ${tmdbType} (ID: ${tmdbId})`); + 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}`); + } 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); + } + } - const logoUrl = await TMDBService.getInstance().getContentLogo(tmdbType, tmdbId); - - if (logoUrl) { - logger.log(`[MetadataScreen] Successfully fetched fallback logo from TMDB: - - Content Type: ${tmdbType} - - TMDB ID: ${tmdbId} - - Logo URL: ${logoUrl} - `); + if (tmdbId) { + logger.log(`[MetadataScreen] Attempting to fetch logo from TMDB for ${tmdbType} (ID: ${tmdbId})`); - // Update metadata with TMDB logo - setMetadata(prevMetadata => ({ - ...prevMetadata!, - logo: logoUrl - })); + try { + const tmdbService = TMDBService.getInstance(); + logger.log(`[MetadataScreen] Calling getContentLogo with type=${tmdbType}, id=${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 + })); + return; // Exit if TMDB logo was found + } else { + logger.warn(`[MetadataScreen] No logo found from TMDB for ${type} (ID: ${tmdbId}), trying Metahub`); + } + } catch (error) { + logger.error(`[MetadataScreen] Error fetching TMDB logo for ID ${tmdbId}:`, error); + } } else { - // If both Metahub and TMDB fail, use the title as text instead of a logo - logger.warn(`[MetadataScreen] No logo found from either Metahub or TMDB for ${type} (ID: ${id}), using title text instead`); + logger.warn(`[MetadataScreen] No TMDB ID available, falling back to Metahub`); + } + + // If TMDB fails or isn't a TMDB ID, try Metahub as fallback + 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 + })); + } 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 + } + } catch (metahubError) { + logger.warn(`[MetadataScreen] Failed to fetch logo from Metahub:`, metahubError); // Leave logo as null/undefined to trigger fallback to text } - } else { - // If no TMDB ID and Metahub failed, use the title as text instead of a logo - logger.warn(`[MetadataScreen] No logo found for ${type} (ID: ${id}), using title text instead`); - - // Leave logo as null/undefined to trigger fallback to text } } catch (error) { logger.error('[MetadataScreen] Failed to fetch logo from all sources:', { @@ -395,10 +601,15 @@ const MetadataScreen = () => { contentId: id, contentType: type }); + } finally { + // Clear fetch in progress flag when done + logoFetchInProgress.current = false; } }; fetchLogo(); + } else if (logoFetchInProgress.current) { + console.log('[MetadataScreen] Logo fetch already in progress, skipping'); } else if (metadata?.logo) { logger.log(`[MetadataScreen] Using existing logo from metadata: - Content ID: ${id} @@ -407,7 +618,7 @@ const MetadataScreen = () => { - Source: ${isMetahubUrl(metadata.logo) ? 'Metahub' : (isTmdbUrl(metadata.logo) ? 'TMDB' : 'Other')} `); } - }, [id, type, metadata, setMetadata, imdbId]); + }, [id, type, metadata, setMetadata, imdbId, settings.logoSourcePreference]); // Function to get episode details from episodeId const getEpisodeDetails = useCallback((episodeId: string): { seasonNumber: string; episodeNumber: string; episodeName: string } | null => { diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 47a5757b..7437d324 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -350,6 +350,78 @@ const SettingsScreen: React.FC = () => { renderControl={ChevronRight} onPress={() => navigation.navigate('MDBListSettings')} /> + ( + + { + console.log('Setting logo source preference to Metahub'); + updateSetting('logoSourcePreference', 'metahub'); + console.log('New logo source preference:', 'metahub'); + + // Clear any cached logo data in storage + try { + // This is just to help clear any cached state - the exact implementation may vary + AsyncStorage.removeItem('_last_logos_'); + } catch (e) { + console.error('Error clearing logo cache:', e); + } + + // Show alert that settings have been updated + Alert.alert( + 'Settings Updated', + 'Logo source preference set to Metahub. Changes will apply when you navigate to content.', + [{ text: 'OK' }] + ); + }} + > + Metahub + + { + console.log('Setting logo source preference to TMDB'); + updateSetting('logoSourcePreference', 'tmdb'); + console.log('New logo source preference:', 'tmdb'); + + // Clear any cached logo data in storage + try { + // This is just to help clear any cached state - the exact implementation may vary + AsyncStorage.removeItem('_last_logos_'); + } catch (e) { + console.error('Error clearing logo cache:', e); + } + + // Show alert that settings have been updated + Alert.alert( + 'Settings Updated', + 'Logo source preference set to TMDB. Changes will apply when you navigate to content.', + [{ text: 'OK' }] + ); + }} + > + TMDB + + + )} + />