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.
This commit is contained in:
tapframe 2025-05-03 19:44:21 +05:30
parent 7aba05f384
commit d39f372359
4 changed files with 173 additions and 32 deletions

View file

@ -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';

View file

@ -81,11 +81,21 @@ const LogoSourceSettings = () => {
settings.logoSourcePreference || 'metahub'
);
// TMDB Language Preference
const [selectedTmdbLanguage, setSelectedTmdbLanguage] = useState<string>(
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<string>('en');
// Store unique language codes as strings
const [uniqueTmdbLanguages, setUniqueTmdbLanguages] = useState<string[]>([]);
const [tmdbLogosData, setTmdbLogosData] = useState<Array<{ iso_639_1: string; file_path: string }> | 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<string>(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 && (
<View style={styles.languageSelectorContainer}>
<Text style={styles.languageSelectorLabel}>Available logo languages:</Text>
<Text style={styles.languageSelectorTitle}>Logo Language</Text>
<Text style={styles.languageSelectorDescription}>
Select your preferred language for TMDB logos. This affects all content when TMDB is used as the logo source.
</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
@ -474,6 +520,9 @@ const LogoSourceSettings = () => {
</TouchableOpacity>
))}
</ScrollView>
<Text style={styles.noteText}>
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.
</Text>
</View>
)}
</TouchableOpacity>
@ -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%',

View file

@ -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}`);

View file

@ -568,14 +568,14 @@ export class TMDBService {
/**
* Get movie images (logos, posters, backdrops) by TMDB ID
*/
async getMovieImages(movieId: number | string): Promise<string | null> {
async getMovieImages(movieId: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
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<string | null> {
async getTvShowImages(showId: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
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<string | null> {
async getContentLogo(type: 'movie' | 'tv', id: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
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}`);