mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
added backdrop gallery
This commit is contained in:
parent
1c7fd533c7
commit
81a7f63782
8 changed files with 404 additions and 14 deletions
|
|
@ -936,12 +936,12 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
// Vertical Layout Styles
|
// Vertical Layout Styles
|
||||||
episodeListContentVertical: {
|
episodeListContentVertical: {
|
||||||
paddingBottom: 20,
|
paddingBottom: 8,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
},
|
},
|
||||||
episodeListContentVerticalTablet: {
|
episodeListContentVerticalTablet: {
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingBottom: 20,
|
paddingBottom: 8,
|
||||||
},
|
},
|
||||||
episodeGridVertical: {
|
episodeGridVertical: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
|
||||||
|
|
@ -1692,6 +1692,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
if (__DEV__) console.log('[useMetadata] fetched certification via TMDB id (extract path)', { type, fetchedTmdbId, certification });
|
if (__DEV__) console.log('[useMetadata] fetched certification via TMDB id (extract path)', { type, fetchedTmdbId, certification });
|
||||||
setMetadata(prev => prev ? {
|
setMetadata(prev => prev ? {
|
||||||
...prev,
|
...prev,
|
||||||
|
tmdbId: fetchedTmdbId,
|
||||||
certification
|
certification
|
||||||
} : null);
|
} : null);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1755,7 +1756,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
const cert = await tmdbSvc.getCertification(type, tmdbId);
|
const cert = await tmdbSvc.getCertification(type, tmdbId);
|
||||||
if (cert) {
|
if (cert) {
|
||||||
if (__DEV__) console.log('[useMetadata] fetched certification (attach path)', { type, tmdbId, cert });
|
if (__DEV__) console.log('[useMetadata] fetched certification (attach path)', { type, tmdbId, cert });
|
||||||
setMetadata(prev => prev ? { ...prev, certification: cert } : prev);
|
setMetadata(prev => prev ? { ...prev, tmdbId, certification: cert } : prev);
|
||||||
} else {
|
} else {
|
||||||
if (__DEV__) console.warn('[useMetadata] TMDB returned no certification (attach path)', { type, tmdbId });
|
if (__DEV__) console.warn('[useMetadata] TMDB returned no certification (attach path)', { type, tmdbId });
|
||||||
}
|
}
|
||||||
|
|
@ -1814,6 +1815,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
// Update metadata with TV details
|
// Update metadata with TV details
|
||||||
setMetadata((prev: any) => ({
|
setMetadata((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
tmdbId,
|
||||||
tvDetails
|
tvDetails
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -1849,6 +1851,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
// Update metadata with movie details
|
// Update metadata with movie details
|
||||||
setMetadata((prev: any) => ({
|
setMetadata((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
tmdbId,
|
||||||
movieDetails: movieDetailsObj
|
movieDetails: movieDetailsObj
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,17 @@ export const useMetadataAssets = (
|
||||||
// Optimized logo fetching
|
// Optimized logo fetching
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const logoPreference = settings.logoSourcePreference || 'tmdb';
|
const logoPreference = settings.logoSourcePreference || 'tmdb';
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
console.log('[useMetadataAssets] Logo fetch triggered:', {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
logoPreference,
|
||||||
|
hasImdbId: !!imdbId,
|
||||||
|
tmdbEnrichmentEnabled: settings.enrichMetadataWithTMDB,
|
||||||
|
logoFetchInProgress: logoFetchInProgress.current
|
||||||
|
});
|
||||||
|
}
|
||||||
const currentLogoUrl = metadata?.logo;
|
const currentLogoUrl = metadata?.logo;
|
||||||
let shouldFetchLogo = false;
|
let shouldFetchLogo = false;
|
||||||
|
|
||||||
|
|
@ -141,12 +152,12 @@ export const useMetadataAssets = (
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||||
|
|
||||||
if (logoPreference === 'tmdb') {
|
if (logoPreference === 'tmdb') {
|
||||||
// TMDB path - optimized flow
|
// TMDB path - optimized flow
|
||||||
let tmdbId: string | null = null;
|
let tmdbId: string | null = null;
|
||||||
let contentType = type === 'series' ? 'tv' : 'movie';
|
let contentType = type === 'series' ? 'tv' : 'movie';
|
||||||
|
|
||||||
// Extract or find TMDB ID in one step
|
// Extract or find TMDB ID in one step
|
||||||
if (id.startsWith('tmdb:')) {
|
if (id.startsWith('tmdb:')) {
|
||||||
tmdbId = id.split(':')[1];
|
tmdbId = id.split(':')[1];
|
||||||
|
|
@ -157,9 +168,11 @@ export const useMetadataAssets = (
|
||||||
if (foundId) {
|
if (foundId) {
|
||||||
tmdbId = String(foundId);
|
tmdbId = String(foundId);
|
||||||
setFoundTmdbId(tmdbId); // Save for banner fetching
|
setFoundTmdbId(tmdbId); // Save for banner fetching
|
||||||
|
} else if (__DEV__) {
|
||||||
|
console.log('[useMetadataAssets] Could not find TMDB ID for IMDB:', imdbId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle error silently
|
if (__DEV__) console.error('[useMetadataAssets] Error finding TMDB ID:', error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const parsedId = parseInt(id, 10);
|
const parsedId = parseInt(id, 10);
|
||||||
|
|
@ -173,15 +186,39 @@ export const useMetadataAssets = (
|
||||||
// Direct fetch - avoid multiple service calls
|
// Direct fetch - avoid multiple service calls
|
||||||
const tmdbService = TMDBService.getInstance();
|
const tmdbService = TMDBService.getInstance();
|
||||||
const logoUrl = await tmdbService.getContentLogo(contentType as 'tv' | 'movie', tmdbId, preferredLanguage);
|
const logoUrl = await tmdbService.getContentLogo(contentType as 'tv' | 'movie', tmdbId, preferredLanguage);
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
console.log('[useMetadataAssets] Logo fetch result:', {
|
||||||
|
contentType,
|
||||||
|
tmdbId,
|
||||||
|
preferredLanguage,
|
||||||
|
logoUrl,
|
||||||
|
logoPreference
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (logoUrl) {
|
if (logoUrl) {
|
||||||
// Preload the image
|
// Preload the image
|
||||||
FastImage.preload([{ uri: logoUrl }]);
|
FastImage.preload([{ uri: logoUrl }]);
|
||||||
|
|
||||||
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: logoUrl }));
|
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: logoUrl }));
|
||||||
|
} else {
|
||||||
|
// TMDB logo not found, try to restore addon logo if it exists
|
||||||
|
if (currentLogoUrl && !isTmdbUrl(currentLogoUrl)) {
|
||||||
|
if (__DEV__) console.log('[useMetadataAssets] Restoring addon logo after TMDB logo not found');
|
||||||
|
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: currentLogoUrl }));
|
||||||
|
} else if (__DEV__) {
|
||||||
|
console.log('[useMetadataAssets] No logo found for TMDB ID:', tmdbId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle error silently
|
// TMDB logo fetch failed, try to restore addon logo if it exists
|
||||||
|
if (currentLogoUrl && !isTmdbUrl(currentLogoUrl)) {
|
||||||
|
if (__DEV__) console.log('[useMetadataAssets] Restoring addon logo after TMDB fetch error');
|
||||||
|
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: currentLogoUrl }));
|
||||||
|
} else if (__DEV__) {
|
||||||
|
console.error('[useMetadataAssets] Logo fetch error:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import CastMoviesScreen from '../screens/CastMoviesScreen';
|
||||||
import UpdateScreen from '../screens/UpdateScreen';
|
import UpdateScreen from '../screens/UpdateScreen';
|
||||||
import AISettingsScreen from '../screens/AISettingsScreen';
|
import AISettingsScreen from '../screens/AISettingsScreen';
|
||||||
import AIChatScreen from '../screens/AIChatScreen';
|
import AIChatScreen from '../screens/AIChatScreen';
|
||||||
|
import BackdropGalleryScreen from '../screens/BackdropGalleryScreen';
|
||||||
import BackupScreen from '../screens/BackupScreen';
|
import BackupScreen from '../screens/BackupScreen';
|
||||||
|
|
||||||
// Stack navigator types
|
// Stack navigator types
|
||||||
|
|
@ -153,6 +154,11 @@ export type RootStackParamList = {
|
||||||
episodeNumber?: number;
|
episodeNumber?: number;
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
BackdropGallery: {
|
||||||
|
tmdbId: number;
|
||||||
|
type: 'movie' | 'tv';
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RootStackNavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
export type RootStackNavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||||
|
|
@ -1328,8 +1334,8 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="AIChat"
|
name="AIChat"
|
||||||
component={AIChatScreen}
|
component={AIChatScreen}
|
||||||
options={{
|
options={{
|
||||||
animation: Platform.OS === 'android' ? 'none' : 'slide_from_right',
|
animation: Platform.OS === 'android' ? 'none' : 'slide_from_right',
|
||||||
|
|
@ -1343,6 +1349,17 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="BackdropGallery"
|
||||||
|
component={BackdropGalleryScreen}
|
||||||
|
options={{
|
||||||
|
animation: 'slide_from_right',
|
||||||
|
headerShown: false,
|
||||||
|
contentStyle: {
|
||||||
|
backgroundColor: '#000',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</View>
|
</View>
|
||||||
</PaperProvider>
|
</PaperProvider>
|
||||||
|
|
|
||||||
254
src/screens/BackdropGalleryScreen.tsx
Normal file
254
src/screens/BackdropGalleryScreen.tsx
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
FlatList,
|
||||||
|
TouchableOpacity,
|
||||||
|
Dimensions,
|
||||||
|
ActivityIndicator,
|
||||||
|
StatusBar,
|
||||||
|
} from 'react-native';
|
||||||
|
import { useRoute, useNavigation } from '@react-navigation/native';
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
import FastImage from '@d11/react-native-fast-image';
|
||||||
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
|
import { TMDBService } from '../services/tmdbService';
|
||||||
|
|
||||||
|
const { width } = Dimensions.get('window');
|
||||||
|
const BACKDROP_WIDTH = width * 0.9;
|
||||||
|
const BACKDROP_HEIGHT = (BACKDROP_WIDTH * 9) / 16; // 16:9 aspect ratio
|
||||||
|
|
||||||
|
interface BackdropItem {
|
||||||
|
file_path: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
aspect_ratio: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RouteParams {
|
||||||
|
tmdbId: number;
|
||||||
|
type: 'movie' | 'tv';
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BackdropGalleryScreen: React.FC = () => {
|
||||||
|
const route = useRoute();
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const { tmdbId, type, title } = route.params as RouteParams;
|
||||||
|
|
||||||
|
const [backdrops, setBackdrops] = useState<BackdropItem[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchBackdrops = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const tmdbService = TMDBService.getInstance();
|
||||||
|
|
||||||
|
let images;
|
||||||
|
if (type === 'movie') {
|
||||||
|
images = await tmdbService.getMovieImagesFull(tmdbId);
|
||||||
|
} else {
|
||||||
|
images = await tmdbService.getTvShowImagesFull(tmdbId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
console.log('[BackdropGallery] TMDB response:', {
|
||||||
|
tmdbId,
|
||||||
|
type,
|
||||||
|
hasImages: !!images,
|
||||||
|
backdropsCount: images?.backdrops?.length || 0,
|
||||||
|
images
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (images && images.backdrops && images.backdrops.length > 0) {
|
||||||
|
setBackdrops(images.backdrops);
|
||||||
|
} else {
|
||||||
|
setError('No backdrops found');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to load backdrops');
|
||||||
|
console.error('Backdrop fetch error:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tmdbId) {
|
||||||
|
fetchBackdrops();
|
||||||
|
}
|
||||||
|
}, [tmdbId, type]);
|
||||||
|
|
||||||
|
const renderBackdrop = ({ item, index }: { item: BackdropItem; index: number }) => {
|
||||||
|
const imageUrl = `https://image.tmdb.org/t/p/w1280${item.file_path}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.backdropContainer}>
|
||||||
|
<FastImage
|
||||||
|
source={{ uri: imageUrl }}
|
||||||
|
style={styles.backdropImage}
|
||||||
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
|
/>
|
||||||
|
<View style={styles.backdropInfo}>
|
||||||
|
<Text style={styles.backdropResolution}>
|
||||||
|
{item.width} × {item.height}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.backdropAspect}>
|
||||||
|
{item.aspect_ratio.toFixed(2)}:1
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderHeader = () => (
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.backButton}
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
>
|
||||||
|
<MaterialIcons name="arrow-back" size={24} color="#fff" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={styles.titleContainer}>
|
||||||
|
<Text style={styles.title} numberOfLines={1}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.subtitle}>
|
||||||
|
{backdrops.length} Backdrop{backdrops.length !== 1 ? 's' : ''}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<StatusBar translucent backgroundColor="transparent" barStyle="light-content" />
|
||||||
|
{renderHeader()}
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator size="large" color="#fff" />
|
||||||
|
<Text style={styles.loadingText}>Loading backdrops...</Text>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || backdrops.length === 0) {
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<StatusBar translucent backgroundColor="transparent" barStyle="light-content" />
|
||||||
|
{renderHeader()}
|
||||||
|
<View style={styles.errorContainer}>
|
||||||
|
<MaterialIcons name="image-not-supported" size={64} color="rgba(255,255,255,0.5)" />
|
||||||
|
<Text style={styles.errorText}>
|
||||||
|
{error || 'No backdrops available'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<StatusBar translucent backgroundColor="transparent" barStyle="light-content" />
|
||||||
|
{renderHeader()}
|
||||||
|
<FlatList
|
||||||
|
data={backdrops}
|
||||||
|
keyExtractor={(item, index) => `${item.file_path}-${index}`}
|
||||||
|
renderItem={renderBackdrop}
|
||||||
|
contentContainerStyle={styles.listContainer}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
/>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#000',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: 'rgba(255,255,255,0.1)',
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
padding: 8,
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#fff',
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: 'rgba(255,255,255,0.7)',
|
||||||
|
},
|
||||||
|
listContainer: {
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
backdropContainer: {
|
||||||
|
marginBottom: 20,
|
||||||
|
borderRadius: 12,
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.05)',
|
||||||
|
},
|
||||||
|
backdropImage: {
|
||||||
|
width: BACKDROP_WIDTH,
|
||||||
|
height: BACKDROP_HEIGHT,
|
||||||
|
alignSelf: 'center',
|
||||||
|
},
|
||||||
|
backdropInfo: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 12,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||||
|
},
|
||||||
|
backdropResolution: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#fff',
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
backdropAspect: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#fff',
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
loadingContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
loadingText: {
|
||||||
|
marginTop: 12,
|
||||||
|
fontSize: 16,
|
||||||
|
color: 'rgba(255,255,255,0.7)',
|
||||||
|
},
|
||||||
|
errorContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 32,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
marginTop: 16,
|
||||||
|
fontSize: 16,
|
||||||
|
color: 'rgba(255,255,255,0.7)',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default BackdropGalleryScreen;
|
||||||
|
|
@ -995,6 +995,24 @@ const MetadataScreen: React.FC = () => {
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Backdrop Gallery section - shown after details for movies/TV when TMDB ID is available */}
|
||||||
|
{shouldLoadSecondaryData && metadata?.tmdbId && (
|
||||||
|
<View style={styles.backdropGalleryContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.backdropGalleryButton}
|
||||||
|
onPress={() => navigation.navigate('BackdropGallery' as any, {
|
||||||
|
tmdbId: metadata.tmdbId,
|
||||||
|
type: Object.keys(groupedEpisodes).length > 0 ? 'tv' : 'movie',
|
||||||
|
title: metadata.name || 'Gallery'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MaterialIcons name="photo-library" size={24} color="#fff" />
|
||||||
|
<Text style={styles.backdropGalleryText}>Backdrop Gallery</Text>
|
||||||
|
<MaterialIcons name="chevron-right" size={24} color="#fff" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Recommendations Section with skeleton when loading - Lazy loaded */}
|
{/* Recommendations Section with skeleton when loading - Lazy loaded */}
|
||||||
{type === 'movie' && shouldLoadSecondaryData && (
|
{type === 'movie' && shouldLoadSecondaryData && (
|
||||||
<MemoizedMoreLikeThisSection
|
<MemoizedMoreLikeThisSection
|
||||||
|
|
@ -1287,7 +1305,7 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
tvDetailsContainer: {
|
tvDetailsContainer: {
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
marginTop: 20,
|
marginTop: 8,
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
},
|
},
|
||||||
tvDetailsHeader: {
|
tvDetailsHeader: {
|
||||||
|
|
@ -1321,6 +1339,30 @@ const styles = StyleSheet.create({
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
backdropGalleryContainer: {
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
marginTop: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
backdropGalleryButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingVertical: 16,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.08)',
|
||||||
|
borderRadius: 12,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.15)',
|
||||||
|
},
|
||||||
|
backdropGalleryText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#fff',
|
||||||
|
marginLeft: 12,
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ export interface StreamingContent {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
tmdbId?: number;
|
||||||
poster: string;
|
poster: string;
|
||||||
posterShape?: string;
|
posterShape?: string;
|
||||||
banner?: string;
|
banner?: string;
|
||||||
|
|
|
||||||
|
|
@ -598,7 +598,25 @@ export class TMDBService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get movie images (logos, posters, backdrops) by TMDB ID
|
* Get movie images (logos, posters, backdrops) by TMDB ID - returns full images object
|
||||||
|
*/
|
||||||
|
async getMovieImagesFull(movieId: number | string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${BASE_URL}/movie/${movieId}/images`, {
|
||||||
|
headers: await this.getHeaders(),
|
||||||
|
params: await this.getParams({
|
||||||
|
include_image_language: `en,null`
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get movie images (logos only) by TMDB ID - legacy method
|
||||||
*/
|
*/
|
||||||
async getMovieImages(movieId: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
|
async getMovieImages(movieId: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -697,7 +715,25 @@ export class TMDBService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get TV show images (logos, posters, backdrops) by TMDB ID
|
* Get TV show images (logos, posters, backdrops) by TMDB ID - returns full images object
|
||||||
|
*/
|
||||||
|
async getTvShowImagesFull(showId: number | string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${BASE_URL}/tv/${showId}/images`, {
|
||||||
|
headers: await this.getHeaders(),
|
||||||
|
params: await this.getParams({
|
||||||
|
include_image_language: `en,null`
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get TV show images (logos only) by TMDB ID - legacy method
|
||||||
*/
|
*/
|
||||||
async getTvShowImages(showId: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
|
async getTvShowImages(showId: number | string, preferredLanguage: string = 'en'): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue