mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-20 08:12:05 +00:00
Add LogoSourceSettings screen and enhance logo fetching in MetadataScreen
Introduce a new LogoSourceSettings screen for users to select their logo source preference. Update the MetadataScreen to fetch banner images based on the selected logo source, improving the logic for handling logo refreshes and error states. Enhance the logoUtils with a new function to fetch banners according to user preferences, ensuring a more robust and user-friendly experience.
This commit is contained in:
parent
e686caebb8
commit
eb90192752
5 changed files with 491 additions and 103 deletions
|
|
@ -35,6 +35,7 @@ import HomeScreenSettings from '../screens/HomeScreenSettings';
|
|||
import HeroCatalogsScreen from '../screens/HeroCatalogsScreen';
|
||||
import TraktSettingsScreen from '../screens/TraktSettingsScreen';
|
||||
import PlayerSettingsScreen from '../screens/PlayerSettingsScreen';
|
||||
import LogoSourceSettings from '../screens/LogoSourceSettings';
|
||||
|
||||
// Stack navigator types
|
||||
export type RootStackParamList = {
|
||||
|
|
@ -90,6 +91,7 @@ export type RootStackParamList = {
|
|||
HeroCatalogs: undefined;
|
||||
TraktSettings: undefined;
|
||||
PlayerSettings: undefined;
|
||||
LogoSourceSettings: undefined;
|
||||
};
|
||||
|
||||
export type RootStackNavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||
|
|
@ -823,6 +825,21 @@ const AppNavigator = () => {
|
|||
},
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="LogoSourceSettings"
|
||||
component={LogoSourceSettings}
|
||||
options={{
|
||||
animation: 'fade',
|
||||
animationDuration: 200,
|
||||
presentation: 'card',
|
||||
gestureEnabled: true,
|
||||
gestureDirection: 'horizontal',
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor: colors.darkBackground,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</PaperProvider>
|
||||
</SafeAreaProvider>
|
||||
|
|
|
|||
256
src/screens/LogoSourceSettings.tsx
Normal file
256
src/screens/LogoSourceSettings.tsx
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
Switch,
|
||||
SafeAreaView,
|
||||
Image,
|
||||
Alert,
|
||||
StatusBar,
|
||||
Platform
|
||||
} 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 { colors } from '../styles/colors';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
const LogoSourceSettings = () => {
|
||||
const { settings, updateSetting } = useSettings();
|
||||
const navigation = useNavigation<NavigationProp<any>>();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
// Get current preference
|
||||
const [logoSource, setLogoSource] = useState<'metahub' | 'tmdb'>(
|
||||
settings.logoSourcePreference || 'metahub'
|
||||
);
|
||||
|
||||
// Apply setting and show confirmation
|
||||
const applyLogoSourceSetting = (source: 'metahub' | 'tmdb') => {
|
||||
setLogoSource(source);
|
||||
updateSetting('logoSourcePreference', source);
|
||||
|
||||
// Clear any cached logo data in storage
|
||||
try {
|
||||
AsyncStorage.removeItem('_last_logos_');
|
||||
} catch (e) {
|
||||
console.error('Error clearing logo cache:', 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 back navigation
|
||||
const handleBack = () => {
|
||||
navigation.goBack();
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
|
||||
{/* Header */}
|
||||
<View style={[styles.header, { paddingTop: Platform.OS === 'android' ? insets.top : 0 }]}>
|
||||
<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 style={styles.headerRight} />
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.scrollView}>
|
||||
{/* Description */}
|
||||
<View style={styles.descriptionContainer}>
|
||||
<Text style={styles.description}>
|
||||
Choose the primary source for content logos and background images. This affects the appearance
|
||||
of titles in the metadata screen.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Options */}
|
||||
<View style={styles.optionsContainer}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.optionCard,
|
||||
logoSource === 'metahub' && styles.selectedCard
|
||||
]}
|
||||
onPress={() => applyLogoSourceSetting('metahub')}
|
||||
>
|
||||
<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}>
|
||||
Prioritizes high-quality title logos from the Metahub image repository.
|
||||
Offers good coverage for popular titles.
|
||||
</Text>
|
||||
|
||||
<View style={styles.exampleContainer}>
|
||||
<Text style={styles.exampleLabel}>Example:</Text>
|
||||
<Image
|
||||
source={{ uri: 'https://images.metahub.space/logo/medium/tt1475582/img' }}
|
||||
style={styles.exampleImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.optionCard,
|
||||
logoSource === 'tmdb' && styles.selectedCard
|
||||
]}
|
||||
onPress={() => applyLogoSourceSetting('tmdb')}
|
||||
>
|
||||
<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}>
|
||||
Uses logos from The Movie Database. Often includes more localized and newer logos,
|
||||
with better coverage for recent content.
|
||||
</Text>
|
||||
|
||||
<View style={styles.exampleContainer}>
|
||||
<Text style={styles.exampleLabel}>Example:</Text>
|
||||
<Image
|
||||
source={{ uri: 'https://image.tmdb.org/t/p/original/wwemzKWzjKYJFfCeiB57q3r4Bcm.svg' }}
|
||||
style={styles.exampleImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Additional Info */}
|
||||
<View style={styles.infoBox}>
|
||||
<Text style={styles.infoText}>
|
||||
If a logo is not available from your preferred source, the app will automatically fall back to the other source.
|
||||
If no logo is found, the title text will be shown instead.
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.darkBackground,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 16,
|
||||
height: 56,
|
||||
backgroundColor: colors.elevation2,
|
||||
},
|
||||
backButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
headerTitle: {
|
||||
color: colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
},
|
||||
headerRight: {
|
||||
width: 40,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
descriptionContainer: {
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: 'rgba(255,255,255,0.1)',
|
||||
},
|
||||
description: {
|
||||
color: colors.text,
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
},
|
||||
optionsContainer: {
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
},
|
||||
optionCard: {
|
||||
backgroundColor: colors.elevation2,
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
borderWidth: 2,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
selectedCard: {
|
||||
borderColor: colors.primary,
|
||||
},
|
||||
optionHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
optionTitle: {
|
||||
color: colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
},
|
||||
optionDescription: {
|
||||
color: colors.mediumEmphasis,
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
marginBottom: 16,
|
||||
},
|
||||
exampleContainer: {
|
||||
marginTop: 8,
|
||||
},
|
||||
exampleLabel: {
|
||||
color: colors.mediumEmphasis,
|
||||
fontSize: 14,
|
||||
marginBottom: 8,
|
||||
},
|
||||
exampleImage: {
|
||||
height: 60,
|
||||
width: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,0.3)',
|
||||
borderRadius: 8,
|
||||
},
|
||||
infoBox: {
|
||||
margin: 16,
|
||||
padding: 16,
|
||||
backgroundColor: 'rgba(255,255,255,0.05)',
|
||||
borderRadius: 8,
|
||||
borderLeftWidth: 4,
|
||||
borderLeftColor: colors.primary,
|
||||
},
|
||||
infoText: {
|
||||
color: colors.mediumEmphasis,
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default LogoSourceSettings;
|
||||
|
|
@ -56,7 +56,7 @@ import { TMDBService } from '../services/tmdbService';
|
|||
import { storageService } from '../services/storageService';
|
||||
import { logger } from '../utils/logger';
|
||||
import { useGenres } from '../contexts/GenreContext';
|
||||
import { isValidMetahubLogo, isMetahubUrl, isTmdbUrl } from '../utils/logoUtils';
|
||||
import { isValidMetahubLogo, isMetahubUrl, isTmdbUrl, fetchBannerWithPreference } from '../utils/logoUtils';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
|
|
@ -218,6 +218,10 @@ const MetadataScreen = () => {
|
|||
|
||||
// Add a flag to track if we need to do a forced initial logo refresh
|
||||
const forcedLogoRefreshDone = useRef<boolean>(false);
|
||||
|
||||
// Add state for custom banner
|
||||
const [bannerImage, setBannerImage] = useState<string | null>(null);
|
||||
const forcedBannerRefreshDone = useRef<boolean>(false);
|
||||
|
||||
// Add debug log for settings when component mounts
|
||||
useEffect(() => {
|
||||
|
|
@ -268,6 +272,65 @@ const MetadataScreen = () => {
|
|||
}
|
||||
}, [metadata?.logo, settings.logoSourcePreference, setMetadata]);
|
||||
|
||||
// Store found TMDB ID for banner fetching
|
||||
const [foundTmdbId, setFoundTmdbId] = useState<string | null>(null);
|
||||
|
||||
// Fetch banner image based on logo source preference
|
||||
useEffect(() => {
|
||||
const fetchBanner = async () => {
|
||||
if (metadata && (!forcedBannerRefreshDone.current || foundTmdbId)) {
|
||||
// Extract any existing TMDB ID if available
|
||||
let tmdbId = null;
|
||||
if (id.startsWith('tmdb:')) {
|
||||
tmdbId = id.split(':')[1];
|
||||
}
|
||||
|
||||
// Use our stored TMDB ID if we have one
|
||||
const effectiveTmdbId = foundTmdbId || tmdbId || (metadata as any).tmdbId;
|
||||
|
||||
logger.log(`[MetadataScreen] Fetching banner with preference: ${settings.logoSourcePreference}, TMDB ID: ${effectiveTmdbId}`);
|
||||
|
||||
try {
|
||||
// Use our utility function to get the banner based on preference
|
||||
const newBanner = await fetchBannerWithPreference(
|
||||
imdbId,
|
||||
effectiveTmdbId,
|
||||
type as 'movie' | 'series',
|
||||
settings.logoSourcePreference
|
||||
);
|
||||
|
||||
if (newBanner) {
|
||||
logger.log(`[MetadataScreen] Setting new banner: ${newBanner}`);
|
||||
setBannerImage(newBanner);
|
||||
} else {
|
||||
// If no banner found from preferred source, use the existing one from metadata
|
||||
logger.log(`[MetadataScreen] Using existing banner from metadata: ${metadata.banner}`);
|
||||
setBannerImage(metadata.banner || metadata.poster);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[MetadataScreen] Error fetching banner:`, error);
|
||||
// Use existing banner as fallback
|
||||
setBannerImage(metadata.banner || metadata.poster);
|
||||
}
|
||||
|
||||
forcedBannerRefreshDone.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
fetchBanner();
|
||||
}, [metadata, id, type, imdbId, settings.logoSourcePreference, foundTmdbId]);
|
||||
|
||||
// Reset forced refresh when preference changes
|
||||
useEffect(() => {
|
||||
if (forcedBannerRefreshDone.current) {
|
||||
logger.log(`[MetadataScreen] Logo preference changed, resetting banner refresh flag`);
|
||||
forcedBannerRefreshDone.current = false;
|
||||
// Clear the banner image to force a new fetch
|
||||
setBannerImage(null);
|
||||
// This will trigger the banner fetch effect to run again
|
||||
}
|
||||
}, [settings.logoSourcePreference]);
|
||||
|
||||
// Get genres from context
|
||||
const { genreMap, loadingGenres } = useGenres();
|
||||
|
||||
|
|
@ -328,11 +391,14 @@ const MetadataScreen = () => {
|
|||
const currentLogoIsMetahub = isMetahubUrl(metadata.logo);
|
||||
const currentLogoIsTmdb = isTmdbUrl(metadata.logo);
|
||||
const preferenceIsMetahub = settings.logoSourcePreference === 'metahub';
|
||||
const preferenceIsTmdb = settings.logoSourcePreference === 'tmdb';
|
||||
|
||||
// Only refresh if the current logo doesn't match the preference
|
||||
if ((preferenceIsMetahub && !currentLogoIsMetahub) ||
|
||||
(!preferenceIsMetahub && !currentLogoIsTmdb)) {
|
||||
logger.log(`[MetadataScreen] Logo preference (${settings.logoSourcePreference}) doesn't match current logo source, refreshing`);
|
||||
// Only refresh if the current logo source clearly doesn't match the preference
|
||||
const needsRefresh = (preferenceIsMetahub && currentLogoIsTmdb) ||
|
||||
(preferenceIsTmdb && currentLogoIsMetahub);
|
||||
|
||||
if (needsRefresh) {
|
||||
logger.log(`[MetadataScreen] Logo preference (${settings.logoSourcePreference}) doesn't match current logo source, triggering one-time refresh`);
|
||||
|
||||
// Prevent endless refreshes
|
||||
if (logoRefreshCounter.current < MAX_LOGO_REFRESHES) {
|
||||
|
|
@ -345,7 +411,7 @@ const MetadataScreen = () => {
|
|||
logger.warn(`[MetadataScreen] Maximum logo refreshes (${MAX_LOGO_REFRESHES}) reached, stopping to prevent loop`);
|
||||
}
|
||||
} else {
|
||||
logger.log(`[MetadataScreen] Logo source already matches preference, no refresh needed`);
|
||||
logger.log(`[MetadataScreen] Logo source already matches preference (${settings.logoSourcePreference}), no refresh needed`);
|
||||
logoRefreshCounter.current = 0; // Reset for future changes
|
||||
}
|
||||
}
|
||||
|
|
@ -370,10 +436,14 @@ const MetadataScreen = () => {
|
|||
const currentLogoIsMetahub = isMetahubUrl(metadata.logo);
|
||||
const currentLogoIsTmdb = isTmdbUrl(metadata.logo);
|
||||
const preferenceIsMetahub = settings.logoSourcePreference === 'metahub';
|
||||
const preferenceIsTmdb = settings.logoSourcePreference === 'tmdb';
|
||||
|
||||
if ((preferenceIsMetahub && currentLogoIsMetahub) ||
|
||||
(!preferenceIsMetahub && currentLogoIsTmdb)) {
|
||||
logger.log('[MetadataScreen] Logo source now matches preference, refresh complete');
|
||||
// Check if current logo source matches preference
|
||||
const logoSourceMatches = (preferenceIsMetahub && currentLogoIsMetahub) ||
|
||||
(preferenceIsTmdb && currentLogoIsTmdb);
|
||||
|
||||
if (logoSourceMatches) {
|
||||
logger.log(`[MetadataScreen] Logo source (${currentLogoIsMetahub ? 'Metahub' : 'TMDB'}) now matches preference (${settings.logoSourcePreference}), refresh complete`);
|
||||
logoRefreshCounter.current = 0; // Reset counter since we've achieved our goal
|
||||
}
|
||||
}
|
||||
|
|
@ -468,6 +538,9 @@ const MetadataScreen = () => {
|
|||
...prevMetadata!,
|
||||
logo: metahubUrl
|
||||
}));
|
||||
|
||||
// Clear fetch in progress flag when done
|
||||
logoFetchInProgress.current = false;
|
||||
return; // Exit if Metahub logo was found
|
||||
} else {
|
||||
logger.warn(`[MetadataScreen] Metahub logo request failed with status ${response.status}`);
|
||||
|
|
@ -486,7 +559,7 @@ const MetadataScreen = () => {
|
|||
const logoUrl = await TMDBService.getInstance().getContentLogo(tmdbType, tmdbId);
|
||||
|
||||
if (logoUrl) {
|
||||
logger.log(`[MetadataScreen] Successfully fetched fallback logo from TMDB:
|
||||
logger.log(`[MetadataScreen] Successfully fetched logo from TMDB:
|
||||
- Content Type: ${tmdbType}
|
||||
- TMDB ID: ${tmdbId}
|
||||
- Logo URL: ${logoUrl}
|
||||
|
|
@ -497,6 +570,10 @@ const MetadataScreen = () => {
|
|||
...prevMetadata!,
|
||||
logo: logoUrl
|
||||
}));
|
||||
|
||||
// Clear fetch in progress flag when done
|
||||
logoFetchInProgress.current = false;
|
||||
return; // Exit if TMDB logo was found
|
||||
} else {
|
||||
// If both Metahub and TMDB fail, use the title as text instead of a logo
|
||||
logger.warn(`[MetadataScreen] No logo found from either Metahub or TMDB for ${type} (ID: ${id}), using title text instead`);
|
||||
|
|
@ -524,6 +601,9 @@ const MetadataScreen = () => {
|
|||
tmdbId = await TMDBService.getInstance().findTMDBIdByIMDB(imdbIdToUse);
|
||||
if (tmdbId) {
|
||||
logger.log(`[MetadataScreen] Found TMDB ID ${tmdbId} for IMDB ID ${imdbIdToUse}`);
|
||||
|
||||
// Save the TMDB ID for banner fetching
|
||||
setFoundTmdbId(String(tmdbId));
|
||||
} else {
|
||||
logger.warn(`[MetadataScreen] Could not find TMDB ID for IMDB ID ${imdbIdToUse}`);
|
||||
}
|
||||
|
|
@ -553,6 +633,9 @@ const MetadataScreen = () => {
|
|||
...prevMetadata!,
|
||||
logo: logoUrl
|
||||
}));
|
||||
|
||||
// Clear fetch in progress flag when done
|
||||
logoFetchInProgress.current = false;
|
||||
return; // Exit if TMDB logo was found
|
||||
} else {
|
||||
logger.warn(`[MetadataScreen] No logo found from TMDB for ${type} (ID: ${tmdbId}), trying Metahub`);
|
||||
|
|
@ -565,34 +648,36 @@ const MetadataScreen = () => {
|
|||
}
|
||||
|
||||
// If TMDB fails or isn't a TMDB ID, try Metahub as fallback
|
||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
|
||||
logger.log(`[MetadataScreen] Attempting to fetch logo from Metahub as fallback for ${imdbId}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
||||
if (response.ok) {
|
||||
logger.log(`[MetadataScreen] Successfully fetched fallback logo from Metahub:
|
||||
- Content ID: ${id}
|
||||
- Content Type: ${type}
|
||||
- Logo URL: ${metahubUrl}
|
||||
`);
|
||||
|
||||
// Update metadata with Metahub logo
|
||||
setMetadata(prevMetadata => ({
|
||||
...prevMetadata!,
|
||||
logo: metahubUrl
|
||||
}));
|
||||
} else {
|
||||
// If both TMDB and Metahub fail, use the title as text instead of a logo
|
||||
logger.warn(`[MetadataScreen] No logo found from either source for ${type} (ID: ${id}), using title text instead`);
|
||||
if (imdbId) {
|
||||
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
|
||||
|
||||
logger.log(`[MetadataScreen] Attempting to fetch logo from Metahub as fallback for ${imdbId}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
||||
if (response.ok) {
|
||||
logger.log(`[MetadataScreen] Successfully fetched fallback logo from Metahub:
|
||||
- Content ID: ${id}
|
||||
- Content Type: ${type}
|
||||
- Logo URL: ${metahubUrl}
|
||||
`);
|
||||
|
||||
// Update metadata with Metahub logo
|
||||
setMetadata(prevMetadata => ({
|
||||
...prevMetadata!,
|
||||
logo: metahubUrl
|
||||
}));
|
||||
} else {
|
||||
// If both TMDB and Metahub fail, use the title as text instead of a logo
|
||||
logger.warn(`[MetadataScreen] No logo found from either source for ${type} (ID: ${id}), using title text instead`);
|
||||
|
||||
// Leave logo as null/undefined to trigger fallback to text
|
||||
}
|
||||
} catch (metahubError) {
|
||||
logger.warn(`[MetadataScreen] Failed to fetch logo from Metahub:`, metahubError);
|
||||
|
||||
// Leave logo as null/undefined to trigger fallback to text
|
||||
}
|
||||
} catch (metahubError) {
|
||||
logger.warn(`[MetadataScreen] Failed to fetch logo from Metahub:`, metahubError);
|
||||
|
||||
// Leave logo as null/undefined to trigger fallback to text
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -1400,9 +1485,16 @@ const MetadataScreen = () => {
|
|||
<View style={styles.heroSection}>
|
||||
{/* Use Animated.Image directly instead of ImageBackground with imageStyle */}
|
||||
<Animated.Image
|
||||
source={{ uri: metadata.banner || metadata.poster }}
|
||||
source={{ uri: bannerImage || metadata.banner || metadata.poster }}
|
||||
style={[styles.absoluteFill, parallaxImageStyle]}
|
||||
resizeMode="cover"
|
||||
onError={() => {
|
||||
logger.warn(`[MetadataScreen] Banner failed to load: ${bannerImage}`);
|
||||
// If custom banner fails, fall back to original metadata banner
|
||||
if (bannerImage !== metadata.banner) {
|
||||
setBannerImage(metadata.banner || metadata.poster);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
|
|
|
|||
|
|
@ -352,75 +352,11 @@ const SettingsScreen: React.FC = () => {
|
|||
/>
|
||||
<SettingItem
|
||||
title="Logo Source Preference"
|
||||
description="Choose primary source for title logos"
|
||||
description="Choose primary source for title logos and backgrounds"
|
||||
icon="image"
|
||||
isDarkMode={isDarkMode}
|
||||
renderControl={() => (
|
||||
<View style={styles.selectorContainer}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.selectorButton,
|
||||
settings.logoSourcePreference === 'metahub' && styles.selectorButtonActive
|
||||
]}
|
||||
onPress={() => {
|
||||
console.log('Setting logo source preference to Metahub');
|
||||
updateSetting('logoSourcePreference', 'metahub');
|
||||
console.log('New logo source preference:', 'metahub');
|
||||
|
||||
// Clear any cached logo data in storage
|
||||
try {
|
||||
// This is just to help clear any cached state - the exact implementation may vary
|
||||
AsyncStorage.removeItem('_last_logos_');
|
||||
} catch (e) {
|
||||
console.error('Error clearing logo cache:', e);
|
||||
}
|
||||
|
||||
// Show alert that settings have been updated
|
||||
Alert.alert(
|
||||
'Settings Updated',
|
||||
'Logo source preference set to Metahub. Changes will apply when you navigate to content.',
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Text style={[
|
||||
styles.selectorText,
|
||||
settings.logoSourcePreference === 'metahub' && styles.selectorTextActive
|
||||
]}>Metahub</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.selectorButton,
|
||||
settings.logoSourcePreference === 'tmdb' && styles.selectorButtonActive
|
||||
]}
|
||||
onPress={() => {
|
||||
console.log('Setting logo source preference to TMDB');
|
||||
updateSetting('logoSourcePreference', 'tmdb');
|
||||
console.log('New logo source preference:', 'tmdb');
|
||||
|
||||
// Clear any cached logo data in storage
|
||||
try {
|
||||
// This is just to help clear any cached state - the exact implementation may vary
|
||||
AsyncStorage.removeItem('_last_logos_');
|
||||
} catch (e) {
|
||||
console.error('Error clearing logo cache:', e);
|
||||
}
|
||||
|
||||
// Show alert that settings have been updated
|
||||
Alert.alert(
|
||||
'Settings Updated',
|
||||
'Logo source preference set to TMDB. Changes will apply when you navigate to content.',
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Text style={[
|
||||
styles.selectorText,
|
||||
settings.logoSourcePreference === 'tmdb' && styles.selectorTextActive
|
||||
]}>TMDB</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
renderControl={ChevronRight}
|
||||
onPress={() => navigation.navigate('LogoSourceSettings')}
|
||||
/>
|
||||
<SettingItem
|
||||
title="TMDB"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { logger } from './logger';
|
||||
import { TMDBService } from '../services/tmdbService';
|
||||
|
||||
/**
|
||||
* Checks if a URL is a valid Metahub logo by performing a HEAD request
|
||||
|
|
@ -80,4 +81,90 @@ export const isMetahubUrl = (url: string | null): boolean => {
|
|||
export const isTmdbUrl = (url: string | null): boolean => {
|
||||
if (!url) return false;
|
||||
return url.includes('themoviedb.org') || url.includes('tmdb.org') || url.includes('image.tmdb.org');
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches a banner image based on logo source preference
|
||||
* @param imdbId The IMDB ID of the content
|
||||
* @param tmdbId The TMDB ID of the content (if available)
|
||||
* @param type The content type ('movie' or 'series')
|
||||
* @param preference The logo source preference ('metahub' or 'tmdb')
|
||||
* @returns The URL of the banner image, or null if none found
|
||||
*/
|
||||
export const fetchBannerWithPreference = async (
|
||||
imdbId: string | null,
|
||||
tmdbId: number | string | null,
|
||||
type: 'movie' | 'series',
|
||||
preference: 'metahub' | 'tmdb'
|
||||
): Promise<string | null> => {
|
||||
logger.log(`[logoUtils] Fetching banner with preference ${preference} for ${type} (IMDB: ${imdbId}, TMDB: ${tmdbId})`);
|
||||
|
||||
// Determine which source to try first based on preference
|
||||
if (preference === 'tmdb') {
|
||||
// Try TMDB first if it's the preferred source
|
||||
if (tmdbId) {
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
|
||||
// Get backdrop from TMDB
|
||||
const tmdbType = type === 'series' ? 'tv' : 'movie';
|
||||
logger.log(`[logoUtils] Attempting to fetch banner from TMDB for ${tmdbType} (ID: ${tmdbId})`);
|
||||
|
||||
let bannerUrl = null;
|
||||
if (tmdbType === 'movie') {
|
||||
const movieDetails = await tmdbService.getMovieDetails(tmdbId.toString());
|
||||
if (movieDetails && movieDetails.backdrop_path) {
|
||||
bannerUrl = tmdbService.getImageUrl(movieDetails.backdrop_path, 'original');
|
||||
logger.log(`[logoUtils] Found backdrop_path: ${movieDetails.backdrop_path}`);
|
||||
} else {
|
||||
logger.warn(`[logoUtils] No backdrop_path found in movie details for ID ${tmdbId}`);
|
||||
}
|
||||
} else {
|
||||
const showDetails = await tmdbService.getTVShowDetails(Number(tmdbId));
|
||||
if (showDetails && showDetails.backdrop_path) {
|
||||
bannerUrl = tmdbService.getImageUrl(showDetails.backdrop_path, 'original');
|
||||
logger.log(`[logoUtils] Found backdrop_path: ${showDetails.backdrop_path}`);
|
||||
} else {
|
||||
logger.warn(`[logoUtils] No backdrop_path found in TV show details for ID ${tmdbId}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (bannerUrl) {
|
||||
logger.log(`[logoUtils] Successfully fetched ${tmdbType} banner from TMDB: ${bannerUrl}`);
|
||||
return bannerUrl;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[logoUtils] Error fetching banner from TMDB for ID ${tmdbId}:`, error);
|
||||
}
|
||||
|
||||
logger.warn(`[logoUtils] No banner found from TMDB for ${type} (ID: ${tmdbId}), falling back to Metahub`);
|
||||
} else {
|
||||
logger.warn(`[logoUtils] Cannot fetch from TMDB - no TMDB ID provided, falling back to Metahub`);
|
||||
}
|
||||
}
|
||||
|
||||
// Try Metahub if it's preferred or TMDB failed
|
||||
if (imdbId) {
|
||||
const metahubUrl = `https://images.metahub.space/background/large/${imdbId}/img`;
|
||||
|
||||
logger.log(`[logoUtils] Attempting to fetch banner from Metahub for ${imdbId}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(metahubUrl, { method: 'HEAD' });
|
||||
if (response.ok) {
|
||||
logger.log(`[logoUtils] Successfully fetched banner from Metahub: ${metahubUrl}`);
|
||||
return metahubUrl;
|
||||
} else {
|
||||
logger.warn(`[logoUtils] Metahub banner request failed with status ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[logoUtils] Failed to fetch banner from Metahub:`, error);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`[logoUtils] Cannot fetch from Metahub - no IMDB ID provided`);
|
||||
}
|
||||
|
||||
// If both sources fail or aren't available, return null
|
||||
logger.warn(`[logoUtils] No banner found from any source for ${type} (IMDB: ${imdbId}, TMDB: ${tmdbId})`);
|
||||
return null;
|
||||
};
|
||||
Loading…
Reference in a new issue