mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
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:
parent
7aba05f384
commit
d39f372359
4 changed files with 173 additions and 32 deletions
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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%',
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue