NuvioStreaming/src/screens/LogoSourceSettings.tsx
tapframe bd1d8e30ec Refactor FeaturedContent logo fetching logic for clarity and efficiency
This update enhances the FeaturedContent component by improving the logo fetching logic. Key changes include clearer variable naming, streamlined ID extraction, and optimized error handling. The logic now better handles logo source preferences and ensures that existing logos are used as fallbacks when necessary. Additionally, the dependency array for the fetch effect has been refined for better performance. Overall, these modifications enhance code readability and maintainability.
2025-05-04 18:35:04 +05:30

851 lines
No EOL
28 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
Switch,
SafeAreaView,
Image,
Alert,
StatusBar,
Platform,
ActivityIndicator,
} from 'react-native';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useSettings, DEFAULT_SETTINGS } from '../hooks/useSettings';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { TMDBService } from '../services/tmdbService';
import { logger } from '../utils/logger';
import { useTheme } from '../contexts/ThemeContext';
// 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
}
];
// Create a styles creator function that accepts the theme colors
const createStyles = (colors: any) => StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.darkBackground,
},
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 16,
paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight || 0) + 16 : 16,
backgroundColor: colors.darkBackground,
},
backButton: {
padding: 4,
},
headerTitle: {
fontSize: 22,
fontWeight: '600',
marginLeft: 16,
color: colors.white,
},
headerRight: {
width: 24,
},
scrollView: {
flex: 1,
},
scrollContent: {
paddingHorizontal: 16,
paddingBottom: 24,
},
descriptionContainer: {
marginBottom: 16,
},
description: {
color: colors.mediumEmphasis,
fontSize: 15,
lineHeight: 22,
},
showSelectorContainer: {
marginBottom: 16,
},
selectorLabel: {
color: colors.highEmphasis,
fontSize: 16,
fontWeight: '500',
marginBottom: 12,
},
showsScrollContent: {
paddingRight: 16,
},
showItem: {
paddingHorizontal: 12,
paddingVertical: 6,
backgroundColor: colors.elevation2,
borderRadius: 16,
marginRight: 6,
borderWidth: 1,
borderColor: 'transparent',
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 1,
elevation: 1,
},
selectedShowItem: {
borderColor: colors.primary,
backgroundColor: colors.elevation3,
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
elevation: 2,
},
showItemText: {
color: colors.mediumEmphasis,
fontSize: 14,
},
selectedShowItemText: {
color: colors.white,
fontWeight: '600',
},
optionsContainer: {
marginBottom: 16,
gap: 12,
},
optionCard: {
backgroundColor: colors.elevation2,
borderRadius: 8,
padding: 12,
borderWidth: 2,
borderColor: 'transparent',
marginBottom: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 3,
elevation: 2,
},
selectedCard: {
borderColor: colors.primary,
shadowColor: colors.primary,
shadowOpacity: 0.3,
elevation: 3,
},
optionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 6,
},
optionTitle: {
color: colors.white,
fontSize: 16,
fontWeight: '600',
},
optionDescription: {
color: colors.mediumEmphasis,
fontSize: 13,
lineHeight: 18,
marginBottom: 10,
},
exampleContainer: {
marginTop: 4,
},
exampleLabel: {
color: colors.mediumEmphasis,
fontSize: 13,
marginBottom: 4,
},
exampleImage: {
height: 60,
width: '100%',
backgroundColor: 'rgba(0,0,0,0.5)',
borderRadius: 8,
},
loadingContainer: {
justifyContent: 'center',
alignItems: 'center',
},
infoBox: {
marginBottom: 16,
padding: 12,
backgroundColor: 'rgba(255,255,255,0.05)',
borderRadius: 8,
borderLeftWidth: 3,
borderLeftColor: colors.primary,
},
infoText: {
color: colors.mediumEmphasis,
fontSize: 12,
lineHeight: 18,
},
logoSourceLabel: {
color: colors.mediumEmphasis,
fontSize: 11,
marginTop: 2,
},
languageSelectorContainer: {
marginTop: 10,
padding: 10,
backgroundColor: 'rgba(255,255,255,0.05)',
borderRadius: 6,
},
languageSelectorTitle: {
color: colors.white,
fontSize: 14,
fontWeight: '600',
marginBottom: 4,
},
languageSelectorDescription: {
color: colors.mediumEmphasis,
fontSize: 12,
lineHeight: 18,
marginBottom: 8,
},
languageSelectorLabel: {
color: colors.mediumEmphasis,
fontSize: 12,
marginBottom: 6,
},
languageScrollContent: {
paddingVertical: 2,
},
languageItem: {
paddingHorizontal: 10,
paddingVertical: 6,
backgroundColor: colors.elevation1,
borderRadius: 12,
marginRight: 6,
borderWidth: 1,
borderColor: colors.elevation3,
marginVertical: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 1,
elevation: 1,
},
selectedLanguageItem: {
backgroundColor: colors.primary,
borderColor: colors.primary,
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 1,
elevation: 2,
},
languageItemText: {
color: colors.mediumEmphasis,
fontSize: 12,
fontWeight: '600',
},
selectedLanguageItemText: {
color: colors.white,
},
noteText: {
color: colors.mediumEmphasis,
fontSize: 11,
marginTop: 8,
fontStyle: 'italic',
},
bannerContainer: {
height: 90,
width: '100%',
borderRadius: 6,
overflow: 'hidden',
position: 'relative',
},
bannerImage: {
...StyleSheet.absoluteFillObject,
},
bannerOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.5)',
},
logoOverBanner: {
position: 'absolute',
width: '80%',
height: '75%',
alignSelf: 'center',
top: '12.5%',
},
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,
},
});
const LogoSourceSettings = () => {
const { settings, updateSetting } = useSettings();
const navigation = useNavigation<NavigationProp<any>>();
const insets = useSafeAreaInsets();
const { currentTheme } = useTheme();
const colors = currentTheme.colors;
const styles = createStyles(colors);
// Get current preference
const [logoSource, setLogoSource] = useState<'metahub' | 'tmdb'>(
settings.logoSourcePreference || 'metahub'
);
// 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<string | null>(null);
const [metahubLogo, setMetahubLogo] = useState<string | null>(null);
const [tmdbBanner, setTmdbBanner] = useState<string | null>(null);
const [metahubBanner, setMetahubBanner] = useState<string | null>(null);
const [loadingLogos, setLoadingLogos] = useState(true);
// State for TMDB language selection
// 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);
// Load example logos for selected show
useEffect(() => {
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);
// Reset unique languages and logos data
setUniqueTmdbLanguages([]);
setTmdbLogosData(null);
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 preferred language directly from settings
const preferredTmdbLanguage = settings.tmdbLanguagePreference || 'en';
// Get TMDB logo and banner
try {
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();
// Store all TMDB logos data and extract unique languages
if (imagesData.logos && imagesData.logos.length > 0) {
setTmdbLogosData(imagesData.logos);
// Filter for logos with valid language codes and get unique codes
const validLogoLanguages = imagesData.logos
.map((logo: { iso_639_1: string | null }) => logo.iso_639_1)
.filter((lang: string | null): lang is string => lang !== null && typeof lang === 'string');
// Explicitly type the Set and resulting array
const uniqueCodes: string[] = [...new Set<string>(validLogoLanguages)];
setUniqueTmdbLanguages(uniqueCodes);
// Find initial logo (prefer selectedTmdbLanguage, then 'en')
let initialLogoPath: string | null = null;
let initialLanguage = preferredTmdbLanguage;
// 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 === preferredTmdbLanguage);
if (preferredLogo) {
initialLogoPath = preferredLogo.file_path;
initialLanguage = preferredTmdbLanguage;
logger.log(`[LogoSourceSettings] Found initial ${preferredTmdbLanguage} 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) {
setTmdbLogo(`https://image.tmdb.org/t/p/original${initialLogoPath}`);
} else {
logger.warn(`[LogoSourceSettings] No valid initial TMDB logo found for ${show.name}`);
}
} else {
logger.warn(`[LogoSourceSettings] No TMDB logos found in response for ${show.name}`);
setUniqueTmdbLanguages([]); // Ensure it's empty if no logos
}
// 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 logo source setting and show confirmation
const applyLogoSourceSetting = (source: 'metahub' | 'tmdb') => {
// Update local state first
setLogoSource(source);
// Update using the settings hook
updateSetting('logoSourcePreference', source);
// Also save directly to AsyncStorage for extra assurance
try {
// Get current settings
AsyncStorage.getItem('app_settings').then((settingsJson) => {
if (settingsJson) {
const currentSettings = JSON.parse(settingsJson);
// Update the logo source preference
const updatedSettings = {
...currentSettings,
logoSourcePreference: source
};
// Save back to AsyncStorage
AsyncStorage.setItem('app_settings', JSON.stringify(updatedSettings))
.then(() => {
logger.log(`[LogoSourceSettings] Successfully saved logo source preference '${source}' to AsyncStorage`);
})
.catch((error) => {
logger.error(`[LogoSourceSettings] Error saving logo source preference to AsyncStorage:`, error);
});
}
}).catch((error) => {
logger.error(`[LogoSourceSettings] Error getting current settings:`, error);
});
// Clear any cached logo data
AsyncStorage.removeItem('_last_logos_');
} catch (e) {
logger.error(`[LogoSourceSettings] Error in applyLogoSourceSetting:`, e);
}
// Show confirmation alert
Alert.alert(
'Settings Updated',
`Logo and background source preference set to ${source === 'metahub' ? 'Metahub' : 'TMDB'}. Changes will apply when you navigate to content.`,
[{ text: 'OK' }]
);
};
// Handle TMDB language selection
const handleTmdbLanguageSelect = (languageCode: string) => {
// Update the preview logo if possible
if (tmdbLogosData) {
const selectedLogoData = tmdbLogosData.find(logo => logo.iso_639_1 === languageCode);
if (selectedLogoData) {
setTmdbLogo(`https://image.tmdb.org/t/p/original${selectedLogoData.file_path}`);
logger.log(`[LogoSourceSettings] Switched TMDB logo preview to language: ${languageCode}`);
} else {
logger.warn(`[LogoSourceSettings] Could not find logo data for selected language: ${languageCode}`);
}
}
// Then persist the setting globally
saveLanguagePreference(languageCode);
};
// Get preferred language directly from settings for UI rendering
const preferredTmdbLanguage = settings.tmdbLanguagePreference || 'en';
// Save language preference with proper persistence
const saveLanguagePreference = async (languageCode: string) => {
logger.log(`[LogoSourceSettings] Saving TMDB language preference: ${languageCode}`);
try {
// First use the settings hook to update the setting - this is crucial
updateSetting('tmdbLanguagePreference', languageCode);
// Clear any cached logo data
await AsyncStorage.removeItem('_last_logos_');
// 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' }]
);
} catch (e) {
logger.error(`[LogoSourceSettings] Error in saveLanguagePreference:`, e);
// Show error notification
Alert.alert(
'Error Saving Preference',
'There was a problem saving your language preference. Please try again.',
[{ text: 'OK' }]
);
}
};
// 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 and background
const renderLogoExample = (logo: string | null, banner: string | null, isLoading: boolean) => {
if (isLoading) {
return (
<View style={[styles.exampleImage, styles.loadingContainer]}>
<ActivityIndicator size="small" color={colors.primary} />
</View>
);
}
return (
<View style={styles.bannerContainer}>
<Image
source={{ uri: banner || undefined }}
style={styles.bannerImage}
resizeMode="cover"
/>
<View style={styles.bannerOverlay} />
{logo && (
<Image
source={{ uri: logo }}
style={styles.logoOverBanner}
resizeMode="contain"
/>
)}
{!logo && (
<View style={styles.noLogoContainer}>
<Text style={styles.noLogoText}>No logo available</Text>
</View>
)}
</View>
);
};
return (
<SafeAreaView style={[styles.container]}>
<StatusBar barStyle="light-content" />
{/* Header */}
<View style={styles.header}>
<TouchableOpacity
onPress={handleBack}
style={styles.backButton}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<MaterialIcons name="arrow-back" size={24} color={colors.white} />
</TouchableOpacity>
<Text style={styles.headerTitle}>Logo Source</Text>
</View>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={true}
scrollEventThrottle={32}
decelerationRate="normal"
>
{/* Description */}
<View style={styles.descriptionContainer}>
<Text style={styles.description}>
Choose the primary source for content logos and backgrounds. The selected source will be used exclusively.
</Text>
</View>
{/* Show selector */}
<View style={styles.showSelectorContainer}>
<Text style={styles.selectorLabel}>Select a show/movie to preview:</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.showsScrollContent}
scrollEventThrottle={32}
decelerationRate="normal"
>
{EXAMPLE_SHOWS.map((show) => (
<TouchableOpacity
key={show.imdbId}
style={[
styles.showItem,
selectedShow.imdbId === show.imdbId && styles.selectedShowItem
]}
onPress={() => handleShowSelect(show)}
activeOpacity={0.7}
delayPressIn={100}
>
<Text
style={[
styles.showItemText,
selectedShow.imdbId === show.imdbId && styles.selectedShowItemText
]}
>
{show.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
{/* Options */}
<View style={styles.optionsContainer}>
<TouchableOpacity
style={[
styles.optionCard,
logoSource === 'metahub' && styles.selectedCard
]}
onPress={() => applyLogoSourceSetting('metahub')}
activeOpacity={0.7}
delayPressIn={100}
>
<View style={styles.optionHeader}>
<Text style={styles.optionTitle}>Metahub</Text>
{logoSource === 'metahub' && (
<MaterialIcons name="check-circle" size={24} color={colors.primary} />
)}
</View>
<Text style={styles.optionDescription}>
High-quality logos from Metahub. Best for popular titles.
</Text>
<View style={styles.exampleContainer}>
<Text style={styles.exampleLabel}>Example:</Text>
{renderLogoExample(metahubLogo, metahubBanner, loadingLogos)}
<Text style={styles.logoSourceLabel}>{selectedShow.name} logo from Metahub</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.optionCard,
logoSource === 'tmdb' && styles.selectedCard
]}
onPress={() => applyLogoSourceSetting('tmdb')}
activeOpacity={0.7}
delayPressIn={100}
>
<View style={styles.optionHeader}>
<Text style={styles.optionTitle}>TMDB</Text>
{logoSource === 'tmdb' && (
<MaterialIcons name="check-circle" size={24} color={colors.primary} />
)}
</View>
<Text style={styles.optionDescription}>
Logos from TMDB. Offers localized options and better coverage for recent content.
</Text>
<View style={styles.exampleContainer}>
<Text style={styles.exampleLabel}>Example:</Text>
{renderLogoExample(tmdbLogo, tmdbBanner, loadingLogos)}
<Text style={styles.logoSourceLabel}>{selectedShow.name} logo from TMDB</Text>
</View>
{/* TMDB Language Selector */}
{uniqueTmdbLanguages.length > 1 && (
<View style={styles.languageSelectorContainer}>
<Text style={styles.languageSelectorTitle}>Logo Language</Text>
<Text style={styles.languageSelectorDescription}>
Select your preferred language for TMDB logos.
</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.languageScrollContent}
scrollEventThrottle={32}
decelerationRate="normal"
>
{/* Iterate over unique language codes */}
{uniqueTmdbLanguages.map((langCode) => (
<TouchableOpacity
key={langCode} // Use the unique code as key
style={[
styles.languageItem,
preferredTmdbLanguage === langCode && styles.selectedLanguageItem
]}
onPress={() => handleTmdbLanguageSelect(langCode)}
activeOpacity={0.7}
delayPressIn={150}
>
<Text
style={[
styles.languageItemText,
preferredTmdbLanguage === langCode && styles.selectedLanguageItemText
]}
>
{(langCode || '').toUpperCase() || '??'}
</Text>
</TouchableOpacity>
))}
</ScrollView>
<Text style={styles.noteText}>
If unavailable in preferred language, English will be used as fallback.
</Text>
</View>
)}
</TouchableOpacity>
</View>
{/* Additional Info */}
<View style={styles.infoBox}>
<Text style={styles.infoText}>
The app will use only the selected source for logos and backgrounds. If no image is available from your chosen source, a text fallback will be used.
</Text>
</View>
</ScrollView>
</SafeAreaView>
);
};
export default LogoSourceSettings;