From d39f3723599bf6216acb023aa1fa8d641c96c984 Mon Sep 17 00:00:00 2001 From: tapframe Date: Sat, 3 May 2025 19:44:21 +0530 Subject: [PATCH] Enhance TMDB language preference functionality across components Add support for user-selected TMDB language preference in the LogoSourceSettings and MetadataScreen components. Update the settings interface to include a language preference, synchronize state with user selections, and improve logo fetching logic in TMDBService to prioritize the selected language. Enhance UI elements to provide clear feedback on language selection and ensure a seamless user experience when fetching logos. --- src/hooks/useSettings.ts | 2 + src/screens/LogoSourceSettings.tsx | 104 ++++++++++++++++++++++++----- src/screens/MetadataScreen.tsx | 11 +-- src/services/tmdbService.ts | 88 +++++++++++++++++++++--- 4 files changed, 173 insertions(+), 32 deletions(-) diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 576ff3a4b..3f55f4ec0 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -33,6 +33,7 @@ export interface AppSettings { featuredContentSource: 'tmdb' | 'catalogs'; selectedHeroCatalogs: string[]; // Array of catalog IDs to display in hero section logoSourcePreference: 'metahub' | 'tmdb'; // Preferred source for title logos + tmdbLanguagePreference: string; // Preferred language for TMDB logos (ISO 639-1 code) } export const DEFAULT_SETTINGS: AppSettings = { @@ -48,6 +49,7 @@ export const DEFAULT_SETTINGS: AppSettings = { featuredContentSource: 'tmdb', selectedHeroCatalogs: [], // Empty array means all catalogs are selected logoSourcePreference: 'metahub', // Default to Metahub as first source + tmdbLanguagePreference: 'en', // Default to English }; const SETTINGS_STORAGE_KEY = 'app_settings'; diff --git a/src/screens/LogoSourceSettings.tsx b/src/screens/LogoSourceSettings.tsx index 2ebb9beef..1ae37e778 100644 --- a/src/screens/LogoSourceSettings.tsx +++ b/src/screens/LogoSourceSettings.tsx @@ -81,11 +81,21 @@ const LogoSourceSettings = () => { settings.logoSourcePreference || 'metahub' ); + // TMDB Language Preference + const [selectedTmdbLanguage, setSelectedTmdbLanguage] = useState( + settings.tmdbLanguagePreference || 'en' + ); + // Make sure logoSource stays in sync with settings useEffect(() => { setLogoSource(settings.logoSourcePreference || 'metahub'); }, [settings.logoSourcePreference]); + // Keep selectedTmdbLanguage in sync with settings + useEffect(() => { + setSelectedTmdbLanguage(settings.tmdbLanguagePreference || 'en'); + }, [settings.tmdbLanguagePreference]); + // Selected example show const [selectedShow, setSelectedShow] = useState(EXAMPLE_SHOWS[0]); @@ -97,7 +107,6 @@ const LogoSourceSettings = () => { const [loadingLogos, setLoadingLogos] = useState(true); // State for TMDB language selection - const [selectedTmdbLanguage, setSelectedTmdbLanguage] = useState('en'); // Store unique language codes as strings const [uniqueTmdbLanguages, setUniqueTmdbLanguages] = useState([]); const [tmdbLogosData, setTmdbLogosData] = useState | null>(null); @@ -117,7 +126,6 @@ const LogoSourceSettings = () => { // Reset unique languages and logos data setUniqueTmdbLanguages([]); setTmdbLogosData(null); - setSelectedTmdbLanguage('en'); // Reset to default language try { const tmdbService = TMDBService.getInstance(); @@ -147,21 +155,31 @@ const LogoSourceSettings = () => { const uniqueCodes: string[] = [...new Set(validLogoLanguages)]; setUniqueTmdbLanguages(uniqueCodes); - // Find initial logo (prefer 'en') + // Find initial logo (prefer selectedTmdbLanguage, then 'en') let initialLogoPath: string | null = null; - let initialLanguage = 'en'; + let initialLanguage = selectedTmdbLanguage; - const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === 'en'); + // 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 === selectedTmdbLanguage); - if (englishLogo) { - initialLogoPath = englishLogo.file_path; - initialLanguage = 'en'; - logger.log(`[LogoSourceSettings] Found initial English TMDB logo for ${show.name}`); - } 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}`); + if (preferredLogo) { + initialLogoPath = preferredLogo.file_path; + initialLanguage = selectedTmdbLanguage; + logger.log(`[LogoSourceSettings] Found initial ${selectedTmdbLanguage} TMDB logo for ${show.name}`); + } 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}`); + } 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}`); + } } if (initialLogoPath) { @@ -228,7 +246,7 @@ const LogoSourceSettings = () => { } }; - // Apply setting and show confirmation + // Apply logo source setting and show confirmation const applyLogoSourceSetting = (source: 'metahub' | 'tmdb') => { setLogoSource(source); updateSetting('logoSourcePreference', source); @@ -248,6 +266,26 @@ const LogoSourceSettings = () => { ); }; + // Apply TMDB language setting + const applyTmdbLanguageSetting = (languageCode: string) => { + setSelectedTmdbLanguage(languageCode); + updateSetting('tmdbLanguagePreference', languageCode); + + // Clear any cached logo data in storage + try { + AsyncStorage.removeItem('_last_logos_'); + } catch (e) { + console.error('Error clearing logo cache:', e); + } + + // Show confirmation toast or feedback + Alert.alert( + 'TMDB Language Updated', + `TMDB logo language preference set to ${languageCode.toUpperCase()}. Changes will apply when you navigate to content.`, + [{ text: 'OK' }] + ); + }; + // Save selected show to AsyncStorage to persist across navigation const saveSelectedShow = async (show: typeof EXAMPLE_SHOWS[0]) => { try { @@ -323,7 +361,9 @@ const LogoSourceSettings = () => { // Handle TMDB language selection const handleTmdbLanguageSelect = (languageCode: string) => { + // Update local state for the example setSelectedTmdbLanguage(languageCode); + if (tmdbLogosData) { const selectedLogoData = tmdbLogosData.find(logo => logo.iso_639_1 === languageCode); if (selectedLogoData) { @@ -333,6 +373,9 @@ const LogoSourceSettings = () => { logger.warn(`[LogoSourceSettings] Could not find logo data for selected language: ${languageCode}`); } } + + // Also update the app-wide setting + applyTmdbLanguageSetting(languageCode); }; return ( @@ -447,7 +490,10 @@ const LogoSourceSettings = () => { {/* TMDB Language Selector */} {uniqueTmdbLanguages.length > 1 && ( - Available logo languages: + Logo Language + + Select your preferred language for TMDB logos. This affects all content when TMDB is used as the logo source. + { ))} + + Note: Not all titles have logos in all languages. If a logo isn't available in your preferred language, English will be used as a fallback. + )} @@ -633,6 +682,21 @@ const LogoSourceSettings = () => { }, languageSelectorContainer: { marginTop: 16, + padding: 12, + backgroundColor: 'rgba(255,255,255,0.05)', + borderRadius: 8, + }, + languageSelectorTitle: { + color: colors.white, + fontSize: 16, + fontWeight: '600', + marginBottom: 8, + }, + languageSelectorDescription: { + color: colors.mediumEmphasis, + fontSize: 14, + lineHeight: 20, + marginBottom: 12, }, languageSelectorLabel: { color: colors.mediumEmphasis, @@ -640,7 +704,7 @@ const LogoSourceSettings = () => { marginBottom: 8, }, languageScrollContent: { - paddingRight: 16, // Match container padding + paddingVertical: 4, }, languageItem: { paddingHorizontal: 12, @@ -663,6 +727,12 @@ const LogoSourceSettings = () => { selectedLanguageItemText: { color: colors.white, }, + noteText: { + color: colors.mediumEmphasis, + fontSize: 12, + marginTop: 12, + fontStyle: 'italic', + }, bannerContainer: { height: 120, width: '100%', diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index db02a12fc..d2ba5a905 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -690,10 +690,12 @@ const MetadataScreen = () => { if (id.startsWith('tmdb:')) { const tmdbId = id.split(':')[1]; const tmdbType = type === 'series' ? 'tv' : 'movie'; - logger.log(`[MetadataScreen] Attempting to fetch logo from TMDB for ${tmdbType} (ID: ${tmdbId})`); + const preferredLanguage = settings.tmdbLanguagePreference || 'en'; + + logger.log(`[MetadataScreen] Attempting to fetch logo from TMDB for ${tmdbType} (ID: ${tmdbId}, preferred language: ${preferredLanguage})`); try { const tmdbService = TMDBService.getInstance(); - tmdbLogoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId); + tmdbLogoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage); if (tmdbLogoUrl) { logger.log(`[MetadataScreen] Successfully fetched logo from TMDB: ${tmdbLogoUrl}`); @@ -705,7 +707,8 @@ const MetadataScreen = () => { } } 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`); + const preferredLanguage = settings.tmdbLanguagePreference || 'en'; + logger.log(`[MetadataScreen] Content has IMDB ID (${imdbId}), looking up TMDB ID for TMDB logo, preferred language: ${preferredLanguage}`); try { const tmdbService = TMDBService.getInstance(); const foundTmdbId = await tmdbService.findTMDBIdByIMDB(imdbId); @@ -714,7 +717,7 @@ const MetadataScreen = () => { 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()); + tmdbLogoUrl = await tmdbService.getContentLogo(type === 'series' ? 'tv' : 'movie', foundTmdbId.toString(), preferredLanguage); if (tmdbLogoUrl) { logger.log(`[MetadataScreen] Successfully fetched logo from TMDB via IMDB lookup: ${tmdbLogoUrl}`); diff --git a/src/services/tmdbService.ts b/src/services/tmdbService.ts index 3a1d2dff0..5fca12ed2 100644 --- a/src/services/tmdbService.ts +++ b/src/services/tmdbService.ts @@ -568,14 +568,14 @@ export class TMDBService { /** * Get movie images (logos, posters, backdrops) by TMDB ID */ - async getMovieImages(movieId: number | string): Promise { + async getMovieImages(movieId: number | string, preferredLanguage: string = 'en'): Promise { try { - logger.log(`[TMDBService] Fetching movie images for TMDB ID: ${movieId}`); + logger.log(`[TMDBService] Fetching movie images for TMDB ID: ${movieId}, preferred language: ${preferredLanguage}`); const response = await axios.get(`${BASE_URL}/movie/${movieId}/images`, { headers: await this.getHeaders(), params: await this.getParams({ - include_image_language: 'en,null' + include_image_language: `${preferredLanguage},en,null` }), }); @@ -583,7 +583,40 @@ export class TMDBService { logger.log(`[TMDBService] Retrieved ${images?.logos?.length || 0} logos for movie ID ${movieId}`); if (images && images.logos && images.logos.length > 0) { - // First prioritize English SVG logos + // First prioritize preferred language SVG logos if not English + if (preferredLanguage !== 'en') { + const preferredSvgLogo = images.logos.find((logo: any) => + logo.file_path && + logo.file_path.endsWith('.svg') && + logo.iso_639_1 === preferredLanguage + ); + if (preferredSvgLogo) { + logger.log(`[TMDBService] Found ${preferredLanguage} SVG logo for movie ID ${movieId}: ${preferredSvgLogo.file_path}`); + return this.getImageUrl(preferredSvgLogo.file_path); + } + + // Then preferred language PNG logos + const preferredPngLogo = images.logos.find((logo: any) => + logo.file_path && + logo.file_path.endsWith('.png') && + logo.iso_639_1 === preferredLanguage + ); + if (preferredPngLogo) { + logger.log(`[TMDBService] Found ${preferredLanguage} PNG logo for movie ID ${movieId}: ${preferredPngLogo.file_path}`); + return this.getImageUrl(preferredPngLogo.file_path); + } + + // Then any preferred language logo + const preferredLogo = images.logos.find((logo: any) => + logo.iso_639_1 === preferredLanguage + ); + if (preferredLogo) { + logger.log(`[TMDBService] Found ${preferredLanguage} logo for movie ID ${movieId}: ${preferredLogo.file_path}`); + return this.getImageUrl(preferredLogo.file_path); + } + } + + // Then prioritize English SVG logos const enSvgLogo = images.logos.find((logo: any) => logo.file_path && logo.file_path.endsWith('.svg') && @@ -649,14 +682,14 @@ export class TMDBService { /** * Get TV show images (logos, posters, backdrops) by TMDB ID */ - async getTvShowImages(showId: number | string): Promise { + async getTvShowImages(showId: number | string, preferredLanguage: string = 'en'): Promise { try { - logger.log(`[TMDBService] Fetching TV show images for TMDB ID: ${showId}`); + logger.log(`[TMDBService] Fetching TV show images for TMDB ID: ${showId}, preferred language: ${preferredLanguage}`); const response = await axios.get(`${BASE_URL}/tv/${showId}/images`, { headers: await this.getHeaders(), params: await this.getParams({ - include_image_language: 'en,null' + include_image_language: `${preferredLanguage},en,null` }), }); @@ -664,6 +697,39 @@ export class TMDBService { logger.log(`[TMDBService] Retrieved ${images?.logos?.length || 0} logos for TV show ID ${showId}`); if (images && images.logos && images.logos.length > 0) { + // First prioritize preferred language SVG logos if not English + if (preferredLanguage !== 'en') { + const preferredSvgLogo = images.logos.find((logo: any) => + logo.file_path && + logo.file_path.endsWith('.svg') && + logo.iso_639_1 === preferredLanguage + ); + if (preferredSvgLogo) { + logger.log(`[TMDBService] Found ${preferredLanguage} SVG logo for TV show ID ${showId}: ${preferredSvgLogo.file_path}`); + return this.getImageUrl(preferredSvgLogo.file_path); + } + + // Then preferred language PNG logos + const preferredPngLogo = images.logos.find((logo: any) => + logo.file_path && + logo.file_path.endsWith('.png') && + logo.iso_639_1 === preferredLanguage + ); + if (preferredPngLogo) { + logger.log(`[TMDBService] Found ${preferredLanguage} PNG logo for TV show ID ${showId}: ${preferredPngLogo.file_path}`); + return this.getImageUrl(preferredPngLogo.file_path); + } + + // Then any preferred language logo + const preferredLogo = images.logos.find((logo: any) => + logo.iso_639_1 === preferredLanguage + ); + if (preferredLogo) { + logger.log(`[TMDBService] Found ${preferredLanguage} logo for TV show ID ${showId}: ${preferredLogo.file_path}`); + return this.getImageUrl(preferredLogo.file_path); + } + } + // First prioritize English SVG logos const enSvgLogo = images.logos.find((logo: any) => logo.file_path && @@ -730,13 +796,13 @@ export class TMDBService { /** * Get content logo based on type (movie or TV show) */ - async getContentLogo(type: 'movie' | 'tv', id: number | string): Promise { + async getContentLogo(type: 'movie' | 'tv', id: number | string, preferredLanguage: string = 'en'): Promise { try { - logger.log(`[TMDBService] Getting content logo for ${type} with ID ${id}`); + logger.log(`[TMDBService] Getting content logo for ${type} with ID ${id}, preferred language: ${preferredLanguage}`); const result = type === 'movie' - ? await this.getMovieImages(id) - : await this.getTvShowImages(id); + ? await this.getMovieImages(id, preferredLanguage) + : await this.getTvShowImages(id, preferredLanguage); if (result) { logger.log(`[TMDBService] Successfully retrieved logo for ${type} ID ${id}: ${result}`);