mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-25 02:23:11 +00:00
873 lines
No EOL
28 KiB
TypeScript
873 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',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 16,
|
|
paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight || 0) + 8 : 8,
|
|
backgroundColor: colors.darkBackground,
|
|
},
|
|
backButton: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
padding: 8,
|
|
},
|
|
backText: {
|
|
fontSize: 17,
|
|
marginLeft: 8,
|
|
color: colors.white,
|
|
},
|
|
headerActions: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
headerButton: {
|
|
padding: 8,
|
|
marginLeft: 8,
|
|
},
|
|
headerTitle: {
|
|
fontSize: 34,
|
|
fontWeight: 'bold',
|
|
paddingHorizontal: 16,
|
|
marginBottom: 24,
|
|
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) {
|
|
if (__DEV__) 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) {
|
|
if (__DEV__) 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} />
|
|
<Text style={styles.backText}>Settings</Text>
|
|
</TouchableOpacity>
|
|
|
|
<View style={styles.headerActions}>
|
|
{/* Empty for now, but ready for future actions */}
|
|
</View>
|
|
</View>
|
|
|
|
<Text style={styles.headerTitle}>Logo Source</Text>
|
|
|
|
<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;
|