import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { DeviceEventEmitter } from 'react-native'; import { Share } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useToast } from '../contexts/ToastContext'; import DropUpMenu from '../components/home/DropUpMenu'; import { View, Text, StyleSheet, TouchableOpacity, useColorScheme, useWindowDimensions, SafeAreaView, StatusBar, Animated as RNAnimated, ActivityIndicator, Platform, ScrollView, BackHandler, } from 'react-native'; import { FlashList } from '@shopify/flash-list'; import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { MaterialIcons, Feather } from '@expo/vector-icons'; import FastImage from '@d11/react-native-fast-image'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import { LinearGradient } from 'expo-linear-gradient'; import { catalogService } from '../services/catalogService'; import type { StreamingContent } from '../services/catalogService'; import { RootStackParamList } from '../navigation/AppNavigator'; import { logger } from '../utils/logger'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useTheme } from '../contexts/ThemeContext'; import { useTraktContext } from '../contexts/TraktContext'; import TraktIcon from '../../assets/rating-icons/trakt.svg'; import { traktService, TraktService, TraktImages } from '../services/traktService'; import { TraktLoadingSpinner } from '../components/common/TraktLoadingSpinner'; // Define interfaces for proper typing interface LibraryItem extends StreamingContent { progress?: number; lastWatched?: string; gradient: [string, string]; imdbId?: string; traktId: number; images?: TraktImages; watched?: boolean; } interface TraktDisplayItem { id: string; name: string; type: 'movie' | 'series'; poster: string; year?: number; lastWatched?: string; plays?: number; rating?: number; imdbId?: string; traktId: number; images?: TraktImages; } interface TraktFolder { id: string; name: string; icon: keyof typeof MaterialIcons.glyphMap; description: string; itemCount: number; gradient: [string, string]; } const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; // Compute responsive grid layout (more columns on tablets) function getGridLayout(screenWidth: number): { numColumns: number; itemWidth: number } { const horizontalPadding = 24; // matches listContainer padding (approx) const gutter = 16; // space between items (via space-between + marginBottom) let numColumns = 2; if (screenWidth >= 1200) numColumns = 5; else if (screenWidth >= 1000) numColumns = 4; else if (screenWidth >= 700) numColumns = 3; else numColumns = 2; const available = screenWidth - horizontalPadding - (numColumns - 1) * gutter; const itemWidth = Math.floor(available / numColumns); return { numColumns, itemWidth }; } const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item: TraktDisplayItem; width: number; navigation: any; currentTheme: any }) => { const [posterUrl, setPosterUrl] = useState(null); useEffect(() => { let isMounted = true; const fetchPoster = async () => { if (item.images) { const url = TraktService.getTraktPosterUrl(item.images); if (isMounted && url) { setPosterUrl(url); } } }; fetchPoster(); return () => { isMounted = false; }; }, [item.images]); const handlePress = useCallback(() => { if (item.imdbId) { navigation.navigate('Metadata', { id: item.imdbId, type: item.type }); } }, [navigation, item.imdbId, item.type]); return ( {posterUrl ? ( ) : ( )} {item.name} ); }); const SkeletonLoader = () => { const pulseAnim = React.useRef(new RNAnimated.Value(0)).current; const { width, height } = useWindowDimensions(); const { numColumns, itemWidth } = getGridLayout(width); const { currentTheme } = useTheme(); React.useEffect(() => { const pulse = RNAnimated.loop( RNAnimated.sequence([ RNAnimated.timing(pulseAnim, { toValue: 1, duration: 1000, useNativeDriver: true, }), RNAnimated.timing(pulseAnim, { toValue: 0, duration: 1000, useNativeDriver: true, }), ]) ); pulse.start(); return () => pulse.stop(); }, [pulseAnim]); const opacity = pulseAnim.interpolate({ inputRange: [0, 1], outputRange: [0.3, 0.7], }); const renderSkeletonItem = () => ( ); // Render enough skeletons for at least two rows const skeletonCount = numColumns * 2; return ( {Array.from({ length: skeletonCount }).map((_, index) => ( {renderSkeletonItem()} ))} ); }; const LibraryScreen = () => { const navigation = useNavigation>(); const isDarkMode = useColorScheme() === 'dark'; const { width, height } = useWindowDimensions(); const { numColumns, itemWidth } = useMemo(() => getGridLayout(width), [width]); const [loading, setLoading] = useState(true); const [libraryItems, setLibraryItems] = useState([]); const [filter, setFilter] = useState<'trakt' | 'movies' | 'series'>('movies'); const [showTraktContent, setShowTraktContent] = useState(false); const [selectedTraktFolder, setSelectedTraktFolder] = useState(null); const { showInfo, showError } = useToast(); // DropUpMenu state const [menuVisible, setMenuVisible] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const insets = useSafeAreaInsets(); const { currentTheme } = useTheme(); // Trakt integration const { isAuthenticated: traktAuthenticated, isLoading: traktLoading, watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent, loadWatchedItems, loadAllCollections } = useTraktContext(); // Force consistent status bar settings useEffect(() => { const applyStatusBarConfig = () => { StatusBar.setBarStyle('light-content'); if (Platform.OS === 'android') { StatusBar.setTranslucent(true); StatusBar.setBackgroundColor('transparent'); } }; applyStatusBarConfig(); // Re-apply on focus const unsubscribe = navigation.addListener('focus', applyStatusBarConfig); return unsubscribe; }, [navigation]); // Handle hardware back button and gesture navigation useEffect(() => { const backAction = () => { if (showTraktContent) { if (selectedTraktFolder) { // If in a specific folder, go back to folder list setSelectedTraktFolder(null); } else { // If in Trakt collections view, go back to main library setShowTraktContent(false); } return true; // Prevent default back behavior } return false; // Allow default back behavior (navigate back) }; const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction); return () => backHandler.remove(); }, [showTraktContent, selectedTraktFolder]); useEffect(() => { const loadLibrary = async () => { setLoading(true); try { const items = await catalogService.getLibraryItems(); // Load watched status for each item from AsyncStorage const updatedItems = await Promise.all(items.map(async (item) => { // Map StreamingContent to LibraryItem shape const libraryItem: LibraryItem = { ...item, gradient: Array.isArray((item as any).gradient) ? (item as any).gradient : ['#222', '#444'], traktId: typeof (item as any).traktId === 'number' ? (item as any).traktId : 0, }; const key = `watched:${item.type}:${item.id}`; const watched = await AsyncStorage.getItem(key); return { ...libraryItem, watched: watched === 'true' }; })); setLibraryItems(updatedItems); } catch (error) { logger.error('Failed to load library:', error); } finally { setLoading(false); } }; loadLibrary(); // Subscribe to library updates const unsubscribe = catalogService.subscribeToLibraryUpdates(async (items) => { // Sync watched status on update const updatedItems = await Promise.all(items.map(async (item) => { // Map StreamingContent to LibraryItem shape const libraryItem: LibraryItem = { ...item, gradient: Array.isArray((item as any).gradient) ? (item as any).gradient : ['#222', '#444'], traktId: typeof (item as any).traktId === 'number' ? (item as any).traktId : 0, }; const key = `watched:${item.type}:${item.id}`; const watched = await AsyncStorage.getItem(key); return { ...libraryItem, watched: watched === 'true' }; })); setLibraryItems(updatedItems); }); // Listen for watched status changes const watchedSub = DeviceEventEmitter.addListener('watchedStatusChanged', loadLibrary); // Refresh when screen regains focus const focusSub = navigation.addListener('focus', loadLibrary); return () => { unsubscribe(); watchedSub.remove(); focusSub(); }; }, [navigation]); const filteredItems = libraryItems.filter(item => { if (filter === 'movies') return item.type === 'movie'; if (filter === 'series') return item.type === 'series'; return true; }); // Generate Trakt collection folders const traktFolders = useMemo((): TraktFolder[] => { if (!traktAuthenticated) return []; const folders: TraktFolder[] = [ { id: 'watched', name: 'Watched', icon: 'visibility', description: 'Your watched content', itemCount: (watchedMovies?.length || 0) + (watchedShows?.length || 0), gradient: ['#2C3E50', '#34495E'] }, { id: 'continue-watching', name: 'Continue Watching', icon: 'play-circle-outline', description: 'Resume your progress', itemCount: continueWatching?.length || 0, gradient: ['#2980B9', '#3498DB'] }, { id: 'watchlist', name: 'Watchlist', icon: 'bookmark', description: 'Want to watch', itemCount: (watchlistMovies?.length || 0) + (watchlistShows?.length || 0), gradient: ['#6C3483', '#9B59B6'] }, { id: 'collection', name: 'Collection', icon: 'library-add', description: 'Your collection', itemCount: (collectionMovies?.length || 0) + (collectionShows?.length || 0), gradient: ['#1B2631', '#283747'] }, { id: 'ratings', name: 'Rated', icon: 'star', description: 'Your ratings', itemCount: ratedContent?.length || 0, gradient: ['#5D6D7E', '#85929E'] } ]; // Only return folders that have content return folders.filter(folder => folder.itemCount > 0); }, [traktAuthenticated, watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent]); const renderItem = ({ item }: { item: LibraryItem }) => ( navigation.navigate('Metadata', { id: item.id, type: item.type })} onLongPress={() => { setSelectedItem(item); setMenuVisible(true); }} activeOpacity={0.7} > {item.watched && ( )} {item.progress !== undefined && item.progress < 1 && ( )} {item.name} ); // Render individual Trakt collection folder const renderTraktCollectionFolder = ({ folder }: { folder: TraktFolder }) => ( { setSelectedTraktFolder(folder.id); loadAllCollections(); // Load all collections when entering a specific folder }} activeOpacity={0.7} > {folder.name} {folder.itemCount} items {folder.description} ); const renderTraktFolder = () => ( { if (!traktAuthenticated) { navigation.navigate('TraktSettings'); } else { setShowTraktContent(true); setSelectedTraktFolder(null); // Reset to folder view loadAllCollections(); // Load all collections when opening } }} activeOpacity={0.7} > Trakt {traktAuthenticated && traktFolders.length > 0 && ( {traktFolders.length} items )} {!traktAuthenticated && ( Tap to connect )} Trakt collections ); const renderTraktItem = useCallback(({ item }: { item: TraktDisplayItem }) => { return ; }, [itemWidth, navigation, currentTheme]); // Get items for a specific Trakt folder const getTraktFolderItems = useCallback((folderId: string): TraktDisplayItem[] => { const items: TraktDisplayItem[] = []; switch (folderId) { case 'watched': // Add watched movies if (watchedMovies) { for (const watchedMovie of watchedMovies) { const movie = watchedMovie.movie; if (movie) { items.push({ id: String(movie.ids.trakt), name: movie.title, type: 'movie', poster: 'placeholder', year: movie.year, lastWatched: watchedMovie.last_watched_at, // Store raw timestamp for sorting plays: watchedMovie.plays, imdbId: movie.ids.imdb, traktId: movie.ids.trakt, images: movie.images, }); } } } // Add watched shows if (watchedShows) { for (const watchedShow of watchedShows) { const show = watchedShow.show; if (show) { items.push({ id: String(show.ids.trakt), name: show.title, type: 'series', poster: 'placeholder', year: show.year, lastWatched: watchedShow.last_watched_at, // Store raw timestamp for sorting plays: watchedShow.plays, imdbId: show.ids.imdb, traktId: show.ids.trakt, images: show.images, }); } } } break; case 'continue-watching': // Add continue watching items if (continueWatching) { for (const item of continueWatching) { if (item.type === 'movie' && item.movie) { items.push({ id: String(item.movie.ids.trakt), name: item.movie.title, type: 'movie', poster: 'placeholder', year: item.movie.year, lastWatched: item.paused_at, // Store raw timestamp for sorting imdbId: item.movie.ids.imdb, traktId: item.movie.ids.trakt, images: item.movie.images, }); } else if (item.type === 'episode' && item.show && item.episode) { items.push({ id: `${item.show.ids.trakt}:${item.episode.season}:${item.episode.number}`, name: `${item.show.title} S${item.episode.season}E${item.episode.number}`, type: 'series', poster: 'placeholder', year: item.show.year, lastWatched: item.paused_at, // Store raw timestamp for sorting imdbId: item.show.ids.imdb, traktId: item.show.ids.trakt, images: item.show.images, }); } } } break; case 'watchlist': // Add watchlist movies if (watchlistMovies) { for (const watchlistMovie of watchlistMovies) { const movie = watchlistMovie.movie; if (movie) { items.push({ id: String(movie.ids.trakt), name: movie.title, type: 'movie', poster: 'placeholder', year: movie.year, lastWatched: watchlistMovie.listed_at, // Store raw timestamp for sorting imdbId: movie.ids.imdb, traktId: movie.ids.trakt, images: movie.images, }); } } } // Add watchlist shows if (watchlistShows) { for (const watchlistShow of watchlistShows) { const show = watchlistShow.show; if (show) { items.push({ id: String(show.ids.trakt), name: show.title, type: 'series', poster: 'placeholder', year: show.year, lastWatched: watchlistShow.listed_at, // Store raw timestamp for sorting imdbId: show.ids.imdb, traktId: show.ids.trakt, images: show.images, }); } } } break; case 'collection': // Add collection movies if (collectionMovies) { for (const collectionMovie of collectionMovies) { const movie = collectionMovie.movie; if (movie) { items.push({ id: String(movie.ids.trakt), name: movie.title, type: 'movie', poster: 'placeholder', year: movie.year, lastWatched: collectionMovie.collected_at, // Store raw timestamp for sorting imdbId: movie.ids.imdb, traktId: movie.ids.trakt, images: movie.images, }); } } } // Add collection shows if (collectionShows) { for (const collectionShow of collectionShows) { const show = collectionShow.show; if (show) { items.push({ id: String(show.ids.trakt), name: show.title, type: 'series', poster: 'placeholder', year: show.year, lastWatched: collectionShow.collected_at, // Store raw timestamp for sorting imdbId: show.ids.imdb, traktId: show.ids.trakt, images: show.images, }); } } } break; case 'ratings': // Add rated content if (ratedContent) { for (const ratedItem of ratedContent) { if (ratedItem.movie) { const movie = ratedItem.movie; items.push({ id: String(movie.ids.trakt), name: movie.title, type: 'movie', poster: 'placeholder', year: movie.year, lastWatched: ratedItem.rated_at, // Store raw timestamp for sorting rating: ratedItem.rating, imdbId: movie.ids.imdb, traktId: movie.ids.trakt, images: movie.images, }); } else if (ratedItem.show) { const show = ratedItem.show; items.push({ id: String(show.ids.trakt), name: show.title, type: 'series', poster: 'placeholder', year: show.year, lastWatched: ratedItem.rated_at, // Store raw timestamp for sorting rating: ratedItem.rating, imdbId: show.ids.imdb, traktId: show.ids.trakt, images: show.images, }); } } } break; } // Sort by last watched/added date (most recent first) using raw timestamps return items.sort((a, b) => { const dateA = a.lastWatched ? new Date(a.lastWatched).getTime() : 0; const dateB = b.lastWatched ? new Date(b.lastWatched).getTime() : 0; return dateB - dateA; }); }, [watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent]); const renderTraktContent = () => { if (traktLoading) { return ; } // If no specific folder is selected, show the folder structure if (!selectedTraktFolder) { if (traktFolders.length === 0) { return ( No Trakt collections Your Trakt collections will appear here once you start using Trakt { loadAllCollections(); }} activeOpacity={0.7} > Load Collections ); } // Show collection folders return ( renderTraktCollectionFolder({ folder: item })} keyExtractor={item => item.id} numColumns={numColumns} contentContainerStyle={styles.listContainer} showsVerticalScrollIndicator={false} onEndReachedThreshold={0.7} onEndReached={() => {}} /> ); } // Show content for specific folder const folderItems = getTraktFolderItems(selectedTraktFolder); if (folderItems.length === 0) { const folderName = traktFolders.find(f => f.id === selectedTraktFolder)?.name || 'Collection'; return ( No content in {folderName} This collection is empty { loadAllCollections(); }} activeOpacity={0.7} > Refresh ); } return ( renderTraktItem({ item })} keyExtractor={(item) => `${item.type}-${item.id}`} numColumns={numColumns} style={styles.traktContainer} contentContainerStyle={{ paddingBottom: insets.bottom + 80 }} showsVerticalScrollIndicator={false} onEndReachedThreshold={0.7} onEndReached={() => {}} /> ); }; const renderFilter = (filterType: 'trakt' | 'movies' | 'series', label: string, iconName: keyof typeof MaterialIcons.glyphMap) => { const isActive = filter === filterType; return ( { if (filterType === 'trakt') { if (!traktAuthenticated) { navigation.navigate('TraktSettings'); } else { setShowTraktContent(true); setSelectedTraktFolder(null); loadAllCollections(); } return; } setFilter(filterType); }} activeOpacity={0.7} > {filterType === 'trakt' ? ( ) : ( )} {label} ); }; const renderContent = () => { if (loading) { return ; } if (filteredItems.length === 0) { const emptyTitle = filter === 'movies' ? 'No movies yet' : filter === 'series' ? 'No TV shows yet' : 'No content yet'; const emptySubtitle = 'Add some content to your library to see it here'; return ( {emptyTitle} {emptySubtitle} navigation.navigate('Search')} activeOpacity={0.7} > Find something to watch ); } return ( renderItem({ item: item as LibraryItem })} keyExtractor={item => item.id} numColumns={numColumns} contentContainerStyle={styles.listContainer} showsVerticalScrollIndicator={false} onEndReachedThreshold={0.7} onEndReached={() => {}} /> ); }; const headerBaseHeight = Platform.OS === 'android' ? 80 : 60; // Tablet detection aligned with navigation tablet logic const isTablet = useMemo(() => { const smallestDimension = Math.min(width, height); return (Platform.OS === 'ios' ? (Platform as any).isPad === true : smallestDimension >= 768); }, [width, height]); // Keep header below floating top navigator on tablets const tabletNavOffset = isTablet ? 64 : 0; const topSpacing = (Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : insets.top) + tabletNavOffset; const headerHeight = headerBaseHeight + topSpacing; return ( {/* Fixed position header background to prevent shifts */} {/* Header Section with proper top spacing */} {showTraktContent ? ( <> { if (selectedTraktFolder) { setSelectedTraktFolder(null); } else { setShowTraktContent(false); } }} activeOpacity={0.7} > {selectedTraktFolder ? traktFolders.find(f => f.id === selectedTraktFolder)?.name || 'Collection' : 'Trakt Collection' } ) : ( <> Library navigation.navigate('Calendar')} activeOpacity={0.7} > )} {/* Content Container */} {!showTraktContent && ( {renderFilter('trakt', 'Trakt', 'pan-tool')} {renderFilter('movies', 'Movies', 'movie')} {renderFilter('series', 'TV Shows', 'live-tv')} )} {showTraktContent ? renderTraktContent() : renderContent()} {/* DropUpMenu integration */} {selectedItem && ( setMenuVisible(false)} item={selectedItem} isWatched={!!selectedItem.watched} isSaved={true} // Since this is from library, it's always saved onOptionSelect={async (option) => { if (!selectedItem) return; switch (option) { case 'library': { try { await catalogService.removeFromLibrary(selectedItem.type, selectedItem.id); showInfo('Removed from Library', 'Item removed from your library'); setLibraryItems(prev => prev.filter(item => !(item.id === selectedItem.id && item.type === selectedItem.type))); setMenuVisible(false); } catch (error) { showError('Failed to update Library', 'Unable to remove item from library'); } break; } case 'watched': { try { // Use AsyncStorage to store watched status by key const key = `watched:${selectedItem.type}:${selectedItem.id}`; const newWatched = !selectedItem.watched; await AsyncStorage.setItem(key, newWatched ? 'true' : 'false'); showInfo(newWatched ? 'Marked as Watched' : 'Marked as Unwatched', newWatched ? 'Item marked as watched' : 'Item marked as unwatched'); // Instantly update local state setLibraryItems(prev => prev.map(item => item.id === selectedItem.id && item.type === selectedItem.type ? { ...item, watched: newWatched } : item )); } catch (error) { showError('Failed to update watched status', 'Unable to update watched status'); } break; } case 'share': { let url = ''; if (selectedItem.id) { url = `https://www.imdb.com/title/${selectedItem.id}/`; } const message = `${selectedItem.name}\n${url}`; Share.share({ message, url, title: selectedItem.name }); break; } default: break; } }} /> )} ); }; const styles = StyleSheet.create({ container: { flex: 1, }, headerBackground: { position: 'absolute', top: 0, left: 0, right: 0, zIndex: 1, }, watchedIndicator: { position: 'absolute', top: 8, right: 8, borderRadius: 12, padding: 2, zIndex: 2, }, contentContainer: { flex: 1, }, header: { paddingHorizontal: 20, justifyContent: 'flex-end', paddingBottom: 8, backgroundColor: 'transparent', zIndex: 2, }, headerContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, headerTitle: { fontSize: 32, fontWeight: '800', letterSpacing: 0.3, }, filtersContainer: { flexDirection: 'row', paddingHorizontal: 16, paddingBottom: 16, paddingTop: 8, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.05)', zIndex: 10, }, filterButton: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, paddingHorizontal: 16, marginHorizontal: 4, borderRadius: 24, backgroundColor: 'rgba(255,255,255,0.05)', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, filterIcon: { marginRight: 8, }, filterText: { fontSize: 15, fontWeight: '500', }, listContainer: { paddingHorizontal: 12, paddingVertical: 16, paddingBottom: 90, }, columnWrapper: { justifyContent: 'space-between', marginBottom: 16, }, skeletonContainer: { flexDirection: 'row', flexWrap: 'wrap', paddingHorizontal: 12, paddingTop: 16, justifyContent: 'space-between', }, itemContainer: { marginBottom: 16, }, posterContainer: { borderRadius: 12, overflow: 'hidden', backgroundColor: 'rgba(255,255,255,0.03)', aspectRatio: 2/3, elevation: 5, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 8, }, poster: { width: '100%', height: '100%', borderRadius: 12, }, posterGradient: { position: 'absolute', bottom: 0, left: 0, right: 0, padding: 16, justifyContent: 'flex-end', height: '45%', }, progressBarContainer: { position: 'absolute', bottom: 0, left: 0, right: 0, height: 4, backgroundColor: 'rgba(0,0,0,0.5)', }, progressBar: { height: '100%', }, badgeContainer: { position: 'absolute', top: 10, right: 10, backgroundColor: 'rgba(0,0,0,0.7)', borderRadius: 12, paddingHorizontal: 8, paddingVertical: 4, flexDirection: 'row', alignItems: 'center', }, badgeText: { fontSize: 10, fontWeight: '600', }, itemTitle: { fontSize: 15, fontWeight: '700', marginBottom: 4, textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, letterSpacing: 0.3, }, cardTitle: { fontSize: 14, fontWeight: '600', marginTop: 8, textAlign: 'center', paddingHorizontal: 4, }, lastWatched: { fontSize: 12, color: 'rgba(255,255,255,0.7)', textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, skeletonTitle: { height: 14, marginTop: 8, borderRadius: 4, }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, paddingBottom: 90, }, emptyText: { fontSize: 20, fontWeight: '700', marginTop: 16, marginBottom: 8, }, emptySubtext: { fontSize: 15, textAlign: 'center', marginBottom: 24, }, exploreButton: { paddingVertical: 12, paddingHorizontal: 24, borderRadius: 24, elevation: 3, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, }, exploreButtonText: { fontSize: 16, fontWeight: '600', }, playsCount: { fontSize: 11, color: 'rgba(255,255,255,0.6)', textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, marginTop: 2, }, filtersScrollView: { flexGrow: 0, }, folderContainer: { borderRadius: 8, overflow: 'hidden', backgroundColor: 'rgba(255,255,255,0.03)', aspectRatio: 2/3, elevation: 5, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 8, }, folderGradient: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, padding: 16, justifyContent: 'center', alignItems: 'center', height: '100%', }, folderTitle: { fontSize: 18, fontWeight: '700', marginBottom: 4, textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, letterSpacing: 0.3, }, folderCount: { fontSize: 12, color: 'rgba(255,255,255,0.7)', textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, folderSubtitle: { fontSize: 12, color: 'rgba(255,255,255,0.7)', textShadowColor: 'rgba(0, 0, 0, 0.75)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, backButton: { padding: 8, }, sectionsContainer: { flex: 1, }, sectionsContent: { paddingBottom: 90, }, section: { marginBottom: 24, }, sectionHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 12, paddingHorizontal: 16, }, sectionIcon: { marginRight: 8, }, sectionTitle: { fontSize: 20, fontWeight: '700', letterSpacing: 0.3, }, horizontalScrollContent: { paddingLeft: 16, paddingRight: 4, }, headerTitleContainer: { flex: 1, alignItems: 'center', justifyContent: 'center', }, headerSpacer: { width: 44, // Match the back button width }, traktContainer: { flex: 1, }, emptyListText: { fontSize: 16, fontWeight: '500', }, row: { justifyContent: 'space-between', paddingHorizontal: 16, }, calendarButton: { width: 44, height: 44, justifyContent: 'center', alignItems: 'center', }, }); export default LibraryScreen;