From 2599fd85d7d0f297df33daf3896ebac9d6908bae Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 13 Oct 2025 14:51:04 +0530 Subject: [PATCH] UI fix --- src/components/player/AndroidVideoPlayer.tsx | 24 +--- src/components/player/KSPlayerCore.tsx | 24 +--- src/hooks/useMetadata.ts | 70 ++++++++++- src/screens/BackdropGalleryScreen.tsx | 117 +------------------ src/screens/MetadataScreen.tsx | 63 ++++++---- src/screens/StreamsScreen.tsx | 2 +- src/utils/backdropStorage.ts | 31 ----- 7 files changed, 120 insertions(+), 211 deletions(-) delete mode 100644 src/utils/backdropStorage.ts diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index a36fe6c7..ee9efc49 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -12,7 +12,6 @@ import { storageService } from '../../services/storageService'; import { logger } from '../../utils/logger'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { MaterialIcons } from '@expo/vector-icons'; -import { getSelectedBackdropUrl } from '../../utils/backdropStorage'; import { LinearGradient } from 'expo-linear-gradient'; import { useTraktAutosync } from '../../hooks/useTraktAutosync'; import { useTraktAutosyncSettings } from '../../hooks/useTraktAutosyncSettings'; @@ -558,8 +557,6 @@ const AndroidVideoPlayer: React.FC = () => { const [nextLoadingQuality, setNextLoadingQuality] = useState(null); const [nextLoadingTitle, setNextLoadingTitle] = useState(null); - // Custom backdrop state - const [customBackdropUrl, setCustomBackdropUrl] = useState(null); const nextEpisodeButtonOpacity = useRef(new Animated.Value(0)).current; const nextEpisodeButtonScale = useRef(new Animated.Value(0.8)).current; @@ -583,27 +580,16 @@ const AndroidVideoPlayer: React.FC = () => { // Check if we have a logo to show const hasLogo = metadata && metadata.logo && !metadataLoading; - // Load custom backdrop on mount - useEffect(() => { - const loadCustomBackdrop = async () => { - const backdropUrl = await getSelectedBackdropUrl('original'); - setCustomBackdropUrl(backdropUrl); - }; - - loadCustomBackdrop(); - }, []); - // Prefetch backdrop and title logo for faster loading screen appearance useEffect(() => { - const finalBackdrop = customBackdropUrl || backdrop; - if (finalBackdrop && typeof finalBackdrop === 'string') { + if (backdrop && typeof backdrop === 'string') { // Reset loading state setIsBackdropLoaded(false); backdropImageOpacityAnim.setValue(0); // Prefetch the image try { - FastImage.preload([{ uri: finalBackdrop }]); + FastImage.preload([{ uri: backdrop }]); // Image prefetch initiated, fade it in smoothly setIsBackdropLoaded(true); Animated.timing(backdropImageOpacityAnim, { @@ -622,7 +608,7 @@ const AndroidVideoPlayer: React.FC = () => { setIsBackdropLoaded(true); backdropImageOpacityAnim.setValue(0); } - }, [backdrop, customBackdropUrl]); + }, [backdrop]); useEffect(() => { const logoUrl = (metadata && (metadata as any).logo) as string | undefined; @@ -3135,7 +3121,7 @@ const AndroidVideoPlayer: React.FC = () => { ]} pointerEvents={isOpeningAnimationComplete ? 'none' : 'auto'} > - {(customBackdropUrl || backdrop) && ( + {backdrop && ( { } ]}> diff --git a/src/components/player/KSPlayerCore.tsx b/src/components/player/KSPlayerCore.tsx index 53bbcac4..94e65628 100644 --- a/src/components/player/KSPlayerCore.tsx +++ b/src/components/player/KSPlayerCore.tsx @@ -11,7 +11,6 @@ import { storageService } from '../../services/storageService'; import { logger } from '../../utils/logger'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { MaterialIcons } from '@expo/vector-icons'; -import { getSelectedBackdropUrl } from '../../utils/backdropStorage'; import { LinearGradient } from 'expo-linear-gradient'; import Slider from '@react-native-community/slider'; import KSPlayerComponent, { KSPlayerRef, KSPlayerSource } from './KSPlayerComponent'; @@ -136,9 +135,6 @@ const KSPlayerCore: React.FC = () => { const [isBackdropLoaded, setIsBackdropLoaded] = useState(false); const backdropImageOpacityAnim = useRef(new Animated.Value(0)).current; - // Custom backdrop state - const [customBackdropUrl, setCustomBackdropUrl] = useState(null); - const [isBuffering, setIsBuffering] = useState(false); const [ksAudioTracks, setKsAudioTracks] = useState>([]); const [ksTextTracks, setKsTextTracks] = useState>([]); @@ -316,26 +312,16 @@ const KSPlayerCore: React.FC = () => { const hasLogo = metadata && metadata.logo && !metadataLoading; // Load custom backdrop on mount - useEffect(() => { - const loadCustomBackdrop = async () => { - const backdropUrl = await getSelectedBackdropUrl('original'); - setCustomBackdropUrl(backdropUrl); - }; - - loadCustomBackdrop(); - }, []); - // Prefetch backdrop and title logo for faster loading screen appearance useEffect(() => { - const finalBackdrop = customBackdropUrl || backdrop; - if (finalBackdrop && typeof finalBackdrop === 'string') { + if (backdrop && typeof backdrop === 'string') { // Reset loading state setIsBackdropLoaded(false); backdropImageOpacityAnim.setValue(0); // Prefetch the image try { - FastImage.preload([{ uri: finalBackdrop }]); + FastImage.preload([{ uri: backdrop }]); // Image prefetch initiated, fade it in smoothly setIsBackdropLoaded(true); Animated.timing(backdropImageOpacityAnim, { @@ -354,7 +340,7 @@ const KSPlayerCore: React.FC = () => { setIsBackdropLoaded(true); backdropImageOpacityAnim.setValue(0); } - }, [backdrop, customBackdropUrl]); + }, [backdrop]); useEffect(() => { const logoUrl = (metadata && (metadata as any).logo) as string | undefined; @@ -2455,7 +2441,7 @@ const KSPlayerCore: React.FC = () => { ]} pointerEvents={shouldHideOpeningOverlay ? 'none' : 'auto'} > - {(customBackdropUrl || backdrop) && ( + {backdrop && ( { } ]}> diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts index 16f84b74..86d7697b 100644 --- a/src/hooks/useMetadata.ts +++ b/src/hooks/useMetadata.ts @@ -786,30 +786,68 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat try { if (settings.enrichMetadataWithTMDB && settings.useTmdbLocalizedMetadata) { const tmdbSvc = TMDBService.getInstance(); - // Ensure we have a TMDB ID let finalTmdbId: number | null = tmdbId; if (!finalTmdbId) { finalTmdbId = await tmdbSvc.extractTMDBIdFromStremioId(actualId); if (finalTmdbId) setTmdbId(finalTmdbId); } + if (finalTmdbId) { const lang = settings.tmdbLanguagePreference || 'en'; if (type === 'movie') { const localized = await tmdbSvc.getMovieDetails(String(finalTmdbId), lang); if (localized) { + const movieDetailsObj = { + status: localized.status, + releaseDate: localized.release_date, + runtime: localized.runtime, + budget: localized.budget, + revenue: localized.revenue, + originalLanguage: localized.original_language, + originCountry: localized.production_countries?.map((c: any) => c.iso_3166_1), + tagline: localized.tagline, + }; + const productionInfo = Array.isArray(localized.production_companies) + ? localized.production_companies + .map((c: any) => ({ id: c?.id, name: c?.name, logo: tmdbSvc.getImageUrl(c?.logo_path, 'w185') })) + .filter((c: any) => c && (c.logo || c.name)) + : []; + finalMetadata = { ...finalMetadata, name: localized.title || finalMetadata.name, description: localized.overview || finalMetadata.description, + movieDetails: movieDetailsObj, + ...(productionInfo.length > 0 && { networks: productionInfo }), }; } - } else { + } else { // 'series' const localized = await tmdbSvc.getTVShowDetails(Number(finalTmdbId), lang); if (localized) { + const tvDetails = { + status: localized.status, + firstAirDate: localized.first_air_date, + lastAirDate: localized.last_air_date, + numberOfSeasons: localized.number_of_seasons, + numberOfEpisodes: localized.number_of_episodes, + episodeRunTime: localized.episode_run_time, + type: localized.type, + originCountry: localized.origin_country, + originalLanguage: localized.original_language, + createdBy: localized.created_by, + }; + const productionInfo = Array.isArray(localized.networks) + ? localized.networks + .map((n: any) => ({ id: n?.id, name: n?.name, logo: tmdbSvc.getImageUrl(n?.logo_path, 'w185') })) + .filter((n: any) => n && (n.logo || n.name)) + : []; + finalMetadata = { ...finalMetadata, name: localized.name || finalMetadata.name, description: localized.overview || finalMetadata.description, + tvDetails, + ...(productionInfo.length > 0 && { networks: productionInfo }), }; } } @@ -1781,10 +1819,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat const tmdbService = TMDBService.getInstance(); let productionInfo: any[] = []; + if (__DEV__) console.log('[useMetadata] fetchProductionInfo starting', { + contentKey, + type, + tmdbId, + useLocalized: settings.useTmdbLocalizedMetadata, + lang: settings.useTmdbLocalizedMetadata ? (settings.tmdbLanguagePreference || 'en') : 'en', + hasExistingNetworks: !!(metadata as any).networks + }); + if (type === 'series') { // Fetch networks and additional details for TV shows - const showDetails = await tmdbService.getTVShowDetails(tmdbId, 'en-US'); + const lang = settings.useTmdbLocalizedMetadata ? (settings.tmdbLanguagePreference || 'en') : 'en'; + const showDetails = await tmdbService.getTVShowDetails(tmdbId, lang); if (showDetails) { + if (__DEV__) console.log('[useMetadata] fetchProductionInfo got showDetails', { + hasNetworks: !!showDetails.networks, + networksCount: showDetails.networks?.length || 0 + }); // Fetch networks if (showDetails.networks) { productionInfo = Array.isArray(showDetails.networks) @@ -1821,8 +1873,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat } } else if (type === 'movie') { // Fetch production companies and additional details for movies - const movieDetails = await tmdbService.getMovieDetails(String(tmdbId), 'en-US'); + const lang = settings.useTmdbLocalizedMetadata ? (settings.tmdbLanguagePreference || 'en') : 'en'; + const movieDetails = await tmdbService.getMovieDetails(String(tmdbId), lang); if (movieDetails) { + if (__DEV__) console.log('[useMetadata] fetchProductionInfo got movieDetails', { + hasProductionCompanies: !!movieDetails.production_companies, + productionCompaniesCount: movieDetails.production_companies?.length || 0 + }); // Fetch production companies if (movieDetails.production_companies) { productionInfo = Array.isArray(movieDetails.production_companies) @@ -1844,7 +1901,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat budget: movieDetails.budget, revenue: movieDetails.revenue, originalLanguage: movieDetails.original_language, - originCountry: movieDetails.origin_country, + originCountry: movieDetails.production_countries?.map((c: any) => c.iso_3166_1), tagline: movieDetails.tagline, }; @@ -1859,7 +1916,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat if (__DEV__) console.log('[useMetadata] Fetched production info via TMDB:', productionInfo); if (productionInfo.length > 0) { + if (__DEV__) console.log('[useMetadata] Setting production info on metadata', { productionInfoCount: productionInfo.length }); setMetadata((prev: any) => ({ ...prev, networks: productionInfo })); + } else { + if (__DEV__) console.log('[useMetadata] No production info found, not setting networks'); } } catch (error) { if (__DEV__) console.error('[useMetadata] Failed to fetch production info:', error); diff --git a/src/screens/BackdropGalleryScreen.tsx b/src/screens/BackdropGalleryScreen.tsx index b8a829ee..5ea099b8 100644 --- a/src/screens/BackdropGalleryScreen.tsx +++ b/src/screens/BackdropGalleryScreen.tsx @@ -8,13 +8,11 @@ import { Dimensions, ActivityIndicator, StatusBar, - Alert, } 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 AsyncStorage from '@react-native-async-storage/async-storage'; import { TMDBService } from '../services/tmdbService'; import { useTheme } from '../contexts/ThemeContext'; @@ -22,7 +20,6 @@ const { width } = Dimensions.get('window'); const BACKDROP_WIDTH = width * 0.9; const BACKDROP_HEIGHT = (BACKDROP_WIDTH * 9) / 16; // 16:9 aspect ratio -const SELECTED_BACKDROP_KEY = 'selected_custom_backdrop'; interface BackdropItem { file_path: string; @@ -46,7 +43,6 @@ const BackdropGalleryScreen: React.FC = () => { const [backdrops, setBackdrops] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [selectedBackdrop, setSelectedBackdrop] = useState(null); useEffect(() => { const fetchBackdrops = async () => { @@ -89,78 +85,18 @@ const BackdropGalleryScreen: React.FC = () => { } }, [tmdbId, type]); - // Load selected backdrop from storage - useEffect(() => { - const loadSelectedBackdrop = async () => { - try { - const saved = await AsyncStorage.getItem(SELECTED_BACKDROP_KEY); - if (saved) { - const backdrop = JSON.parse(saved); - setSelectedBackdrop(backdrop); - } - } catch (error) { - console.error('Failed to load selected backdrop:', error); - } - }; - loadSelectedBackdrop(); - }, []); - - const saveSelectedBackdrop = async (backdrop: BackdropItem) => { - try { - await AsyncStorage.setItem(SELECTED_BACKDROP_KEY, JSON.stringify(backdrop)); - setSelectedBackdrop(backdrop); - Alert.alert('Success', 'Custom backdrop set successfully!', [ - { text: 'OK', onPress: () => navigation.goBack() } - ]); - } catch (error) { - console.error('Failed to save selected backdrop:', error); - Alert.alert('Error', 'Failed to save backdrop'); - } - }; - - const resetSelectedBackdrop = async () => { - try { - await AsyncStorage.removeItem(SELECTED_BACKDROP_KEY); - setSelectedBackdrop(null); - Alert.alert('Success', 'Custom backdrop reset to default!', [ - { text: 'OK', onPress: () => navigation.goBack() } - ]); - } catch (error) { - console.error('Failed to reset selected backdrop:', error); - Alert.alert('Error', 'Failed to reset backdrop'); - } - }; const renderBackdrop = ({ item, index }: { item: BackdropItem; index: number }) => { const imageUrl = `https://image.tmdb.org/t/p/w1280${item.file_path}`; - const isSelected = selectedBackdrop?.file_path === item.file_path; return ( - { - Alert.alert( - 'Set as Default Backdrop', - 'Use this backdrop for metadata screens and player loading?', - [ - { text: 'Cancel', style: 'cancel' }, - { text: 'Set as Default', onPress: () => saveSelectedBackdrop(item) } - ] - ); - }} - delayLongPress={500} - > + - {isSelected && ( - - - - )} {item.width} × {item.height} @@ -169,7 +105,7 @@ const BackdropGalleryScreen: React.FC = () => { {item.aspect_ratio.toFixed(2)}:1 - + ); }; @@ -189,23 +125,6 @@ const BackdropGalleryScreen: React.FC = () => { {backdrops.length} Backdrop{backdrops.length !== 1 ? 's' : ''} - {selectedBackdrop && ( - { - Alert.alert( - 'Reset Backdrop', - 'Remove custom backdrop and use default?', - [ - { text: 'Cancel', style: 'cancel' }, - { text: 'Reset', style: 'destructive', onPress: resetSelectedBackdrop } - ] - ); - }} - > - - - )} ); @@ -242,13 +161,6 @@ const BackdropGalleryScreen: React.FC = () => { {renderHeader()} - {/* Explanatory note */} - - - Long press any backdrop to set it as your default for metadata screens and player loading overlay. - - - `${item.file_path}-${index}`} @@ -317,31 +229,6 @@ const styles = StyleSheet.create({ fontSize: 12, opacity: 0.8, }, - selectedIndicator: { - position: 'absolute', - top: 12, - right: 12, - backgroundColor: 'rgba(0, 123, 255, 0.9)', - borderRadius: 12, - padding: 4, - }, - resetButton: { - padding: 8, - marginLeft: 12, - }, - noteContainer: { - paddingHorizontal: 16, - paddingVertical: 8, - backgroundColor: 'rgba(255,255,255,0.05)', - marginHorizontal: 16, - marginBottom: 8, - borderRadius: 8, - }, - noteText: { - fontSize: 12, - textAlign: 'center', - lineHeight: 16, - }, loadingContainer: { flex: 1, justifyContent: 'center', diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index df0e0a4f..53e99a66 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -46,7 +46,6 @@ import { useSettings } from '../hooks/useSettings'; import { MetadataLoadingScreen } from '../components/loading/MetadataLoadingScreen'; import { useTrailer } from '../contexts/TrailerContext'; import FastImage from '@d11/react-native-fast-image'; -import { getSelectedBackdropUrl } from '../utils/backdropStorage'; // Import our optimized components and hooks import HeroSection from '../components/metadata/HeroSection'; @@ -96,31 +95,21 @@ const MetadataScreen: React.FC = () => { const transitionOpacity = useSharedValue(1); const interactionComplete = useRef(false); + // Animation values for network/production sections + const networkSectionOpacity = useSharedValue(0); + const productionSectionOpacity = useSharedValue(0); + // Comment bottom sheet state const [commentBottomSheetVisible, setCommentBottomSheetVisible] = useState(false); const [selectedComment, setSelectedComment] = useState(null); const [revealedSpoilers, setRevealedSpoilers] = useState>(new Set()); - // Custom backdrop state - const [customBackdropUrl, setCustomBackdropUrl] = useState(null); // Debug state changes React.useEffect(() => { console.log('MetadataScreen: commentBottomSheetVisible changed to:', commentBottomSheetVisible); }, [commentBottomSheetVisible]); - // Load custom backdrop when screen comes into focus (for instant updates when returning from gallery) - useFocusEffect( - React.useCallback(() => { - const loadCustomBackdrop = async () => { - const backdropUrl = await getSelectedBackdropUrl('original'); - setCustomBackdropUrl(backdropUrl); - }; - - loadCustomBackdrop(); - }, []) - ); - React.useEffect(() => { console.log('MetadataScreen: selectedComment changed to:', selectedComment?.id); }, [selectedComment]); @@ -146,6 +135,7 @@ const MetadataScreen: React.FC = () => { tmdbId, } = useMetadata({ id, type, addonId }); + // Log useMetadata hook state changes for debugging React.useEffect(() => { console.log('🔍 [MetadataScreen] useMetadata state:', { @@ -164,6 +154,28 @@ const MetadataScreen: React.FC = () => { }); }, [loading, metadata, metadataError, cast.length, episodes.length, Object.keys(groupedEpisodes).length, imdbId, tmdbId]); + // Animate network section when data becomes available (for series) + useEffect(() => { + const hasNetworks = metadata?.networks && metadata.networks.length > 0; + const isSeries = Object.keys(groupedEpisodes).length > 0; + const shouldShow = shouldLoadSecondaryData && hasNetworks && isSeries; + + if (shouldShow && networkSectionOpacity.value === 0) { + networkSectionOpacity.value = withTiming(1, { duration: 400 }); + } + }, [metadata?.networks, Object.keys(groupedEpisodes).length, shouldLoadSecondaryData, networkSectionOpacity]); + + // Animate production section when data becomes available (for movies) + useEffect(() => { + const hasNetworks = metadata?.networks && metadata.networks.length > 0; + const isMovie = Object.keys(groupedEpisodes).length === 0; + const shouldShow = shouldLoadSecondaryData && hasNetworks && isMovie; + + if (shouldShow && productionSectionOpacity.value === 0) { + productionSectionOpacity.value = withTiming(1, { duration: 400 }); + } + }, [metadata?.networks, Object.keys(groupedEpisodes).length, shouldLoadSecondaryData, productionSectionOpacity]); + // Optimized hooks with memoization and conditional loading const watchProgressData = useWatchProgress(id, Object.keys(groupedEpisodes).length > 0 ? 'series' : type as 'movie' | 'series', episodeId, episodes); const assetData = useMetadataAssets(metadata, id, type, imdbId, settings, setMetadata); @@ -240,7 +252,16 @@ const MetadataScreen: React.FC = () => { ); return { backgroundColor: color as any }; }); - + + // Animated styles for network and production sections + const networkSectionAnimatedStyle = useAnimatedStyle(() => ({ + opacity: networkSectionOpacity.value, + })); + + const productionSectionAnimatedStyle = useAnimatedStyle(() => ({ + opacity: productionSectionOpacity.value, + })); + // For compatibility with existing code, maintain the static value as well const dynamicBackgroundColor = useMemo(() => { if (settings.useDominantBackgroundColor && dominantColor && dominantColor !== '#1a1a1a' && dominantColor !== null && dominantColor !== currentTheme.colors.darkBackground) { @@ -830,7 +851,7 @@ const MetadataScreen: React.FC = () => { {/* Hero Section - Optimized */} { {/* Production info row — shown below description and above cast for series */} {shouldLoadSecondaryData && Object.keys(groupedEpisodes).length > 0 && metadata?.networks && metadata.networks.length > 0 && ( - + Network {metadata.networks.slice(0, 6).map((net) => ( @@ -890,7 +911,7 @@ const MetadataScreen: React.FC = () => { ))} - + )} {/* Cast Section with skeleton when loading - Lazy loaded */} @@ -905,7 +926,7 @@ const MetadataScreen: React.FC = () => { {/* Production info row — shown after cast for movies */} {shouldLoadSecondaryData && Object.keys(groupedEpisodes).length === 0 && metadata?.networks && metadata.networks.length > 0 && ( - + Production {metadata.networks.slice(0, 6).map((net) => ( @@ -922,7 +943,7 @@ const MetadataScreen: React.FC = () => { ))} - + )} {/* Comments Section - Lazy loaded */} diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index 42e67d0d..3fbde6cb 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -1160,7 +1160,7 @@ export const StreamsScreen = () => { episodeId: (type === 'series' || type === 'other') && selectedEpisode ? selectedEpisode : undefined, imdbId: imdbId || undefined, availableStreams: streamsToPass, - backdrop: bannerImage || undefined, + backdrop: bannerImage, // Hint for Android ExoPlayer/react-native-video videoType: videoType, } as any); diff --git a/src/utils/backdropStorage.ts b/src/utils/backdropStorage.ts deleted file mode 100644 index 3e0c128c..00000000 --- a/src/utils/backdropStorage.ts +++ /dev/null @@ -1,31 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; - -const SELECTED_BACKDROP_KEY = 'selected_custom_backdrop'; - -export interface SelectedBackdrop { - file_path: string; - width: number; - height: number; - aspect_ratio: number; -} - -export const getSelectedBackdrop = async (): Promise => { - try { - const saved = await AsyncStorage.getItem(SELECTED_BACKDROP_KEY); - if (saved) { - return JSON.parse(saved); - } - return null; - } catch (error) { - console.error('Failed to load selected backdrop:', error); - return null; - } -}; - -export const getSelectedBackdropUrl = async (size: 'original' | 'w1280' | 'w780' = 'original'): Promise => { - const backdrop = await getSelectedBackdrop(); - if (backdrop) { - return `https://image.tmdb.org/t/p/${size}${backdrop.file_path}`; - } - return null; -};