Ios #14

Merged
tapframe merged 88 commits from ios into main 2025-06-20 13:54:29 +00:00
5 changed files with 132 additions and 199 deletions
Showing only changes of commit bd1d8e30ec - Show all commits

View file

@ -122,153 +122,132 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat
if (!featuredContent || logoFetchInProgress.current) return;
const fetchLogo = async () => {
// Set fetch in progress flag
logoFetchInProgress.current = true;
try {
const contentId = featuredContent.id;
const contentData = featuredContent; // Use a clearer variable name
const currentLogo = contentData.logo;
// Get logo source preference from settings
const logoPreference = settings.logoSourcePreference || 'metahub'; // Default to metahub if not set
const preferredLanguage = settings.tmdbLanguagePreference || 'en'; // Get preferred language
// Get preferences
const logoPreference = settings.logoSourcePreference || 'metahub';
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
// Check if current logo matches preferences
const currentLogo = featuredContent.logo;
if (currentLogo) {
const isCurrentMetahub = isMetahubUrl(currentLogo);
const isCurrentTmdb = isTmdbUrl(currentLogo);
// If logo already matches preference, use it
if ((logoPreference === 'metahub' && isCurrentMetahub) ||
(logoPreference === 'tmdb' && isCurrentTmdb)) {
setLogoUrl(currentLogo);
logoFetchInProgress.current = false;
return;
}
// Reset state for new fetch
setLogoUrl(null);
setLogoLoadError(false);
// Extract IDs
let imdbId: string | null = null;
if (contentData.id.startsWith('tt')) {
imdbId = contentData.id;
} else if ((contentData as any).imdbId) {
imdbId = (contentData as any).imdbId;
} else if ((contentData as any).externalIds?.imdb_id) {
imdbId = (contentData as any).externalIds.imdb_id;
}
// Extract IMDB ID if available
let imdbId = null;
if (featuredContent.id.startsWith('tt')) {
// If the ID itself is an IMDB ID
imdbId = featuredContent.id;
} else if ((featuredContent as any).imdbId) {
// Try to get IMDB ID from the content object if available
imdbId = (featuredContent as any).imdbId;
let tmdbId: string | null = null;
if (contentData.id.startsWith('tmdb:')) {
tmdbId = contentData.id.split(':')[1];
} else if ((contentData as any).tmdb_id) {
tmdbId = String((contentData as any).tmdb_id);
}
// Extract TMDB ID if available
let tmdbId = null;
if (contentId.startsWith('tmdb:')) {
tmdbId = contentId.split(':')[1];
}
// First source based on preference
if (logoPreference === 'metahub' && imdbId) {
// Try to get logo from Metahub first
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
// If we only have IMDB ID, try to find TMDB ID proactively
if (imdbId && !tmdbId) {
try {
const response = await fetch(metahubUrl, { method: 'HEAD' });
if (response.ok) {
setLogoUrl(metahubUrl);
logoFetchInProgress.current = false;
return; // Exit if Metahub logo was found
const tmdbService = TMDBService.getInstance();
const foundData = await tmdbService.findTMDBIdByIMDB(imdbId);
if (foundData) {
tmdbId = String(foundData);
}
} catch (error) {
// Removed logger.warn
} catch (findError) {
// logger.warn(`[FeaturedContent] Failed to find TMDB ID for ${imdbId}:`, findError);
}
// Fall back to TMDB if Metahub fails and we have a TMDB ID
if (tmdbId) {
const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie';
try {
const tmdbService = TMDBService.getInstance();
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
if (logoUrl) {
setLogoUrl(logoUrl);
} else if (currentLogo) {
// If TMDB fails too, use existing logo if any
setLogoUrl(currentLogo);
}
} catch (error) {
// Removed logger.error
if (currentLogo) setLogoUrl(currentLogo);
}
} else if (currentLogo) {
// Use existing logo if we don't have TMDB ID
setLogoUrl(currentLogo);
}
} else if (logoPreference === 'tmdb') {
// Try to get logo from TMDB first
if (tmdbId) {
const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie';
try {
const tmdbService = TMDBService.getInstance();
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
if (logoUrl) {
setLogoUrl(logoUrl);
logoFetchInProgress.current = false;
return; // Exit if TMDB logo was found
}
} catch (error) {
// Removed logger.error
}
} else if (imdbId) {
// If we have IMDB ID but no TMDB ID, try to find TMDB ID
try {
const tmdbService = TMDBService.getInstance();
const foundTmdbId = await tmdbService.findTMDBIdByIMDB(imdbId);
if (foundTmdbId) {
const tmdbType = featuredContent.type === 'series' ? 'tv' : 'movie';
const logoUrl = await tmdbService.getContentLogo(tmdbType, foundTmdbId.toString(), preferredLanguage);
if (logoUrl) {
setLogoUrl(logoUrl);
logoFetchInProgress.current = false;
return; // Exit if TMDB logo was found
}
}
} catch (error) {
// Removed logger.error
}
}
// Fall back to Metahub if TMDB fails and we have an IMDB ID
}
const tmdbType = contentData.type === 'series' ? 'tv' : 'movie';
let finalLogoUrl: string | null = null;
let primaryAttempted = false;
let fallbackAttempted = false;
// --- Logo Fetching Logic ---
if (logoPreference === 'metahub') {
// Primary: Metahub (needs imdbId)
if (imdbId) {
primaryAttempted = true;
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
try {
const response = await fetch(metahubUrl, { method: 'HEAD' });
if (response.ok) {
setLogoUrl(metahubUrl);
} else if (currentLogo) {
// If Metahub fails too, use existing logo if any
setLogoUrl(currentLogo);
finalLogoUrl = metahubUrl;
}
} catch (error) {
// Removed logger.warn
if (currentLogo) setLogoUrl(currentLogo);
}
} else if (currentLogo) {
// Use existing logo if we don't have IMDB ID
setLogoUrl(currentLogo);
} catch (error) { /* Log if needed */ }
}
// Fallback: TMDB (needs tmdbId)
if (!finalLogoUrl && tmdbId) {
fallbackAttempted = true;
try {
const tmdbService = TMDBService.getInstance();
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
if (logoUrl) {
finalLogoUrl = logoUrl;
}
} catch (error) { /* Log if needed */ }
}
} else { // logoPreference === 'tmdb'
// Primary: TMDB (needs tmdbId)
if (tmdbId) {
primaryAttempted = true;
try {
const tmdbService = TMDBService.getInstance();
const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage);
if (logoUrl) {
finalLogoUrl = logoUrl;
}
} catch (error) { /* Log if needed */ }
}
// Fallback: Metahub (needs imdbId)
if (!finalLogoUrl && imdbId) {
fallbackAttempted = true;
const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`;
try {
const response = await fetch(metahubUrl, { method: 'HEAD' });
if (response.ok) {
finalLogoUrl = metahubUrl;
}
} catch (error) { /* Log if needed */ }
}
}
// --- Set Final Logo ---
if (finalLogoUrl) {
setLogoUrl(finalLogoUrl);
} else if (currentLogo) {
// Use existing logo only if primary and fallback failed or weren't applicable
setLogoUrl(currentLogo);
} else {
// No logo found from any source
setLogoLoadError(true);
// logger.warn(`[FeaturedContent] No logo found for ${contentData.name} (${contentId}) with preference ${logoPreference}. Primary attempted: ${primaryAttempted}, Fallback attempted: ${fallbackAttempted}`);
}
} catch (error) {
// Removed logger.error
// Optionally set a fallback logo or handle the error state
setLogoUrl(featuredContent.logo ?? null); // Fallback to initial logo or null
// logger.error('[FeaturedContent] Error in fetchLogo:', error);
setLogoLoadError(true);
} finally {
logoFetchInProgress.current = false;
}
};
// Trigger fetch when content changes
fetchLogo();
}, [featuredContent?.id, settings.logoSourcePreference, settings.tmdbLanguagePreference]);
}, [featuredContent, settings.logoSourcePreference, settings.tmdbLanguagePreference]);
// Load poster and logo
useEffect(() => {

View file

@ -286,9 +286,10 @@ const HeroSection: React.FC<HeroSectionProps> = ({
}));
const parallaxImageStyle = useAnimatedStyle(() => ({
width: '100%',
height: '120%',
width: '120%',
height: '100%',
top: '-10%',
left: '-10%',
transform: [
{
translateY: interpolate(
@ -302,7 +303,7 @@ const HeroSection: React.FC<HeroSectionProps> = ({
scale: interpolate(
dampedScrollY.value,
[0, 150, 300],
[1.1, 1.02, 0.95],
[1.05, 1.02, 0.99],
Extrapolate.CLAMP
)
}

View file

@ -196,7 +196,15 @@ export const useMetadataAssets = (
else if (shouldFetchLogo && logoFetchInProgress.current) {
logger.log(`[useMetadataAssets:Logo] Skipping logo fetch because logoFetchInProgress is true.`);
}
}, [id, type, metadata, setMetadata, imdbId, settings.logoSourcePreference, settings.tmdbLanguagePreference]); // Added tmdbLanguagePreference dependency
}, [
id,
type,
imdbId,
metadata?.logo, // Depend on the logo value itself, not the whole object
settings.logoSourcePreference,
settings.tmdbLanguagePreference,
setMetadata // Keep setMetadata, but ensure it's memoized in parent
]);
// Fetch banner image based on logo source preference - optimized version
useEffect(() => {

View file

@ -686,7 +686,8 @@ const AppNavigator = () => {
/>
<Stack.Screen
name="Metadata"
component={MetadataScreen as any}
component={MetadataScreen}
options={{ headerShown: false, animation: Platform.OS === 'ios' ? 'slide_from_right' : 'default' }}
/>
<Stack.Screen
name="Streams"
@ -700,22 +701,27 @@ const AppNavigator = () => {
<Stack.Screen
name="Player"
component={VideoPlayer as any}
options={{ animation: Platform.OS === 'ios' ? 'slide_from_right' : 'default' }}
/>
<Stack.Screen
name="Catalog"
component={CatalogScreen as any}
options={{ animation: Platform.OS === 'ios' ? 'slide_from_right' : 'default' }}
/>
<Stack.Screen
name="Addons"
component={AddonsScreen as any}
options={{ animation: Platform.OS === 'ios' ? 'slide_from_right' : 'default' }}
/>
<Stack.Screen
name="Search"
component={SearchScreen as any}
options={{ animation: Platform.OS === 'ios' ? 'slide_from_right' : 'default' }}
/>
<Stack.Screen
name="CatalogSettings"
component={CatalogSettingsScreen as any}
options={{ animation: Platform.OS === 'ios' ? 'slide_from_right' : 'default' }}
/>
<Stack.Screen
name="HomeScreenSettings"
@ -765,10 +771,12 @@ const AppNavigator = () => {
<Stack.Screen
name="Calendar"
component={CalendarScreen as any}
options={{ animation: Platform.OS === 'ios' ? 'slide_from_right' : 'default' }}
/>
<Stack.Screen
name="NotificationSettings"
component={NotificationSettingsScreen as any}
options={{ animation: Platform.OS === 'ios' ? 'slide_from_right' : 'default' }}
/>
<Stack.Screen
name="MDBListSettings"

View file

@ -348,48 +348,11 @@ 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]);
// Force reload settings from AsyncStorage when component mounts
useEffect(() => {
const loadSettingsFromStorage = async () => {
try {
const settingsJson = await AsyncStorage.getItem('app_settings');
if (settingsJson) {
const storedSettings = JSON.parse(settingsJson);
// Update local state to match stored settings
if (storedSettings.logoSourcePreference) {
setLogoSource(storedSettings.logoSourcePreference);
}
if (storedSettings.tmdbLanguagePreference) {
setSelectedTmdbLanguage(storedSettings.tmdbLanguagePreference);
}
logger.log('[LogoSourceSettings] Successfully loaded settings from AsyncStorage');
}
} catch (error) {
logger.error('[LogoSourceSettings] Error loading settings from AsyncStorage:', error);
}
};
loadSettingsFromStorage();
}, []);
// Selected example show
const [selectedShow, setSelectedShow] = useState(EXAMPLE_SHOWS[0]);
@ -429,6 +392,9 @@ const LogoSourceSettings = () => {
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;
@ -451,15 +417,15 @@ const LogoSourceSettings = () => {
// Find initial logo (prefer selectedTmdbLanguage, then 'en')
let initialLogoPath: string | null = null;
let initialLanguage = selectedTmdbLanguage;
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 === selectedTmdbLanguage);
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 = selectedTmdbLanguage;
logger.log(`[LogoSourceSettings] Found initial ${selectedTmdbLanguage} TMDB logo for ${show.name}`);
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');
@ -478,7 +444,6 @@ const LogoSourceSettings = () => {
if (initialLogoPath) {
setTmdbLogo(`https://image.tmdb.org/t/p/original${initialLogoPath}`);
setSelectedTmdbLanguage(initialLanguage); // Set selected language based on found logo
} else {
logger.warn(`[LogoSourceSettings] No valid initial TMDB logo found for ${show.name}`);
}
@ -588,9 +553,6 @@ const LogoSourceSettings = () => {
// Handle TMDB language selection
const handleTmdbLanguageSelect = (languageCode: string) => {
// First set local state for immediate UI updates
setSelectedTmdbLanguage(languageCode);
// Update the preview logo if possible
if (tmdbLogosData) {
const selectedLogoData = tmdbLogosData.find(logo => logo.iso_639_1 === languageCode);
@ -606,6 +568,9 @@ const LogoSourceSettings = () => {
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}`);
@ -614,34 +579,6 @@ const LogoSourceSettings = () => {
// First use the settings hook to update the setting - this is crucial
updateSetting('tmdbLanguagePreference', languageCode);
// For extra assurance, also save directly to AsyncStorage
// Get current settings from AsyncStorage
const settingsJson = await AsyncStorage.getItem('app_settings');
if (settingsJson) {
const currentSettings = JSON.parse(settingsJson);
// Update the language preference
const updatedSettings = {
...currentSettings,
tmdbLanguagePreference: languageCode
};
// Save back to AsyncStorage using await to ensure it completes
await AsyncStorage.setItem('app_settings', JSON.stringify(updatedSettings));
logger.log(`[LogoSourceSettings] Successfully saved TMDB language preference '${languageCode}' to AsyncStorage`);
} else {
// If no settings exist yet, create new settings object with this preference
const newSettings = {
...DEFAULT_SETTINGS,
tmdbLanguagePreference: languageCode
};
// Save to AsyncStorage
await AsyncStorage.setItem('app_settings', JSON.stringify(newSettings));
logger.log(`[LogoSourceSettings] Created new settings with TMDB language preference '${languageCode}'`);
}
// Clear any cached logo data
await AsyncStorage.removeItem('_last_logos_');
@ -875,7 +812,7 @@ const LogoSourceSettings = () => {
key={langCode} // Use the unique code as key
style={[
styles.languageItem,
selectedTmdbLanguage === langCode && styles.selectedLanguageItem
preferredTmdbLanguage === langCode && styles.selectedLanguageItem
]}
onPress={() => handleTmdbLanguageSelect(langCode)}
activeOpacity={0.7}
@ -884,7 +821,7 @@ const LogoSourceSettings = () => {
<Text
style={[
styles.languageItemText,
selectedTmdbLanguage === langCode && styles.selectedLanguageItemText
preferredTmdbLanguage === langCode && styles.selectedLanguageItemText
]}
>
{(langCode || '').toUpperCase() || '??'}