diff --git a/src/screens/LibraryScreen.tsx b/src/screens/LibraryScreen.tsx index 6ace2a6c..83d11fd7 100644 --- a/src/screens/LibraryScreen.tsx +++ b/src/screens/LibraryScreen.tsx @@ -37,8 +37,8 @@ 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'; +import { useSettings } from '../hooks/useSettings'; -// Define interfaces for proper typing interface LibraryItem extends StreamingContent { progress?: number; lastWatched?: string; @@ -72,10 +72,9 @@ interface TraktFolder { 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 = 16; // matches listContainer padding (approx) - const gutter = 12; // space between items (via space-between + marginBottom) + const horizontalPadding = 16; + const gutter = 12; let numColumns = 3; if (screenWidth >= 1200) numColumns = 5; else if (screenWidth >= 1000) numColumns = 4; @@ -86,7 +85,19 @@ function getGridLayout(screenWidth: number): { numColumns: number; itemWidth: nu return { numColumns, itemWidth }; } -const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item: TraktDisplayItem; width: number; navigation: any; currentTheme: any }) => { +const TraktItem = React.memo(({ + item, + width, + navigation, + currentTheme, + showTitles +}: { + item: TraktDisplayItem; + width: number; + navigation: any; + currentTheme: any; + showTitles: boolean; +}) => { const [posterUrl, setPosterUrl] = useState(null); useEffect(() => { @@ -129,9 +140,11 @@ const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item: )} - - {item.name} - + {showTitles && ( + + {item.name} + + )} ); @@ -184,7 +197,6 @@ const SkeletonLoader = () => { ); - // Render enough skeletons for at least two rows const skeletonCount = numColumns * 2; return ( @@ -208,13 +220,12 @@ const LibraryScreen = () => { 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(); + const { settings } = useSettings(); - // Trakt integration const { isAuthenticated: traktAuthenticated, isLoading: traktLoading, @@ -230,7 +241,6 @@ const LibraryScreen = () => { loadAllCollections } = useTraktContext(); - // Force consistent status bar settings useEffect(() => { const applyStatusBarConfig = () => { StatusBar.setBarStyle('light-content'); @@ -241,30 +251,24 @@ const LibraryScreen = () => { }; 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 true; } - return false; // Allow default back behavior (navigate back) + return false; }; const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction); - return () => backHandler.remove(); }, [showTraktContent, selectedTraktFolder]); @@ -274,16 +278,13 @@ const LibraryScreen = () => { try { const items = await catalogService.getLibraryItems(); - // Sort by date added (most recent first) const sortedItems = items.sort((a, b) => { const timeA = (a as any).addedToLibraryAt || 0; const timeB = (b as any).addedToLibraryAt || 0; - return timeB - timeA; // Descending order (newest first) + return timeB - timeA; }); - // Load watched status for each item from AsyncStorage const updatedItems = await Promise.all(sortedItems.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'], @@ -306,18 +307,14 @@ const LibraryScreen = () => { loadLibrary(); - // Subscribe to library updates const unsubscribe = catalogService.subscribeToLibraryUpdates(async (items) => { - // Sort by date added (most recent first) const sortedItems = items.sort((a, b) => { const timeA = (a as any).addedToLibraryAt || 0; const timeB = (b as any).addedToLibraryAt || 0; - return timeB - timeA; // Descending order (newest first) + return timeB - timeA; }); - // Sync watched status on update const updatedItems = await Promise.all(sortedItems.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'], @@ -333,10 +330,7 @@ const LibraryScreen = () => { 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 () => { @@ -352,7 +346,6 @@ const LibraryScreen = () => { return true; }); - // Generate Trakt collection folders const traktFolders = useMemo((): TraktFolder[] => { if (!traktAuthenticated) return []; @@ -389,61 +382,57 @@ const LibraryScreen = () => { } ]; - // 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 }) => { - const aspectRatio = item.posterShape === 'landscape' ? 16 / 9 : (item.posterShape === 'square' ? 1 : 2 / 3); - - return ( - 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 && ( - - - - )} - + 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 && ( + + + + )} + + {settings.showPosterTitles && ( {item.name} - - - ); - }; + )} + + + ); - // Render individual Trakt collection folder const renderTraktCollectionFolder = ({ folder }: { folder: TraktFolder }) => ( { setSelectedTraktFolder(folder.id); - loadAllCollections(); // Load all collections when entering a specific folder + loadAllCollections(); }} activeOpacity={0.7} > @@ -474,8 +463,8 @@ const LibraryScreen = () => { navigation.navigate('TraktSettings'); } else { setShowTraktContent(true); - setSelectedTraktFolder(null); // Reset to folder view - loadAllCollections(); // Load all collections when opening + setSelectedTraktFolder(null); + loadAllCollections(); } }} activeOpacity={0.7} @@ -494,24 +483,30 @@ const LibraryScreen = () => { )} - - Trakt collections - + {settings.showPosterTitles && ( + + Trakt collections + + )} ); const renderTraktItem = useCallback(({ item }: { item: TraktDisplayItem }) => { - return ; - }, [itemWidth, navigation, currentTheme]); + return ; + }, [itemWidth, navigation, currentTheme, settings.showPosterTitles]); - // 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; @@ -522,7 +517,7 @@ const LibraryScreen = () => { type: 'movie', poster: 'placeholder', year: movie.year, - lastWatched: watchedMovie.last_watched_at, // Store raw timestamp for sorting + lastWatched: watchedMovie.last_watched_at, plays: watchedMovie.plays, imdbId: movie.ids.imdb, traktId: movie.ids.trakt, @@ -531,7 +526,6 @@ const LibraryScreen = () => { } } } - // Add watched shows if (watchedShows) { for (const watchedShow of watchedShows) { const show = watchedShow.show; @@ -542,7 +536,7 @@ const LibraryScreen = () => { type: 'series', poster: 'placeholder', year: show.year, - lastWatched: watchedShow.last_watched_at, // Store raw timestamp for sorting + lastWatched: watchedShow.last_watched_at, plays: watchedShow.plays, imdbId: show.ids.imdb, traktId: show.ids.trakt, @@ -554,7 +548,6 @@ const LibraryScreen = () => { break; case 'continue-watching': - // Add continue watching items if (continueWatching) { for (const item of continueWatching) { if (item.type === 'movie' && item.movie) { @@ -564,7 +557,7 @@ const LibraryScreen = () => { type: 'movie', poster: 'placeholder', year: item.movie.year, - lastWatched: item.paused_at, // Store raw timestamp for sorting + lastWatched: item.paused_at, imdbId: item.movie.ids.imdb, traktId: item.movie.ids.trakt, images: item.movie.images, @@ -576,7 +569,7 @@ const LibraryScreen = () => { type: 'series', poster: 'placeholder', year: item.show.year, - lastWatched: item.paused_at, // Store raw timestamp for sorting + lastWatched: item.paused_at, imdbId: item.show.ids.imdb, traktId: item.show.ids.trakt, images: item.show.images, @@ -587,7 +580,6 @@ const LibraryScreen = () => { break; case 'watchlist': - // Add watchlist movies if (watchlistMovies) { for (const watchlistMovie of watchlistMovies) { const movie = watchlistMovie.movie; @@ -598,7 +590,7 @@ const LibraryScreen = () => { type: 'movie', poster: 'placeholder', year: movie.year, - lastWatched: watchlistMovie.listed_at, // Store raw timestamp for sorting + lastWatched: watchlistMovie.listed_at, imdbId: movie.ids.imdb, traktId: movie.ids.trakt, images: movie.images, @@ -606,7 +598,6 @@ const LibraryScreen = () => { } } } - // Add watchlist shows if (watchlistShows) { for (const watchlistShow of watchlistShows) { const show = watchlistShow.show; @@ -617,7 +608,7 @@ const LibraryScreen = () => { type: 'series', poster: 'placeholder', year: show.year, - lastWatched: watchlistShow.listed_at, // Store raw timestamp for sorting + lastWatched: watchlistShow.listed_at, imdbId: show.ids.imdb, traktId: show.ids.trakt, images: show.images, @@ -628,7 +619,6 @@ const LibraryScreen = () => { break; case 'collection': - // Add collection movies if (collectionMovies) { for (const collectionMovie of collectionMovies) { const movie = collectionMovie.movie; @@ -639,7 +629,7 @@ const LibraryScreen = () => { type: 'movie', poster: 'placeholder', year: movie.year, - lastWatched: collectionMovie.collected_at, // Store raw timestamp for sorting + lastWatched: collectionMovie.collected_at, imdbId: movie.ids.imdb, traktId: movie.ids.trakt, images: movie.images, @@ -647,7 +637,6 @@ const LibraryScreen = () => { } } } - // Add collection shows if (collectionShows) { for (const collectionShow of collectionShows) { const show = collectionShow.show; @@ -658,7 +647,7 @@ const LibraryScreen = () => { type: 'series', poster: 'placeholder', year: show.year, - lastWatched: collectionShow.collected_at, // Store raw timestamp for sorting + lastWatched: collectionShow.collected_at, imdbId: show.ids.imdb, traktId: show.ids.trakt, images: show.images, @@ -669,7 +658,6 @@ const LibraryScreen = () => { break; case 'ratings': - // Add rated content if (ratedContent) { for (const ratedItem of ratedContent) { if (ratedItem.movie) { @@ -680,7 +668,7 @@ const LibraryScreen = () => { type: 'movie', poster: 'placeholder', year: movie.year, - lastWatched: ratedItem.rated_at, // Store raw timestamp for sorting + lastWatched: ratedItem.rated_at, rating: ratedItem.rating, imdbId: movie.ids.imdb, traktId: movie.ids.trakt, @@ -694,7 +682,7 @@ const LibraryScreen = () => { type: 'series', poster: 'placeholder', year: show.year, - lastWatched: ratedItem.rated_at, // Store raw timestamp for sorting + lastWatched: ratedItem.rated_at, rating: ratedItem.rating, imdbId: show.ids.imdb, traktId: show.ids.trakt, @@ -706,7 +694,6 @@ const LibraryScreen = () => { 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; @@ -719,7 +706,6 @@ const LibraryScreen = () => { return ; } - // If no specific folder is selected, show the folder structure if (!selectedTraktFolder) { if (traktFolders.length === 0) { return ( @@ -745,7 +731,6 @@ const LibraryScreen = () => { ); } - // Show collection folders return ( { ); } - // Show content for specific folder const folderItems = getTraktFolderItems(selectedTraktFolder); if (folderItems.length === 0) { @@ -902,7 +886,6 @@ const LibraryScreen = () => { ); }; - // 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); @@ -910,7 +893,6 @@ const LibraryScreen = () => { return ( - {/* ScreenHeader Component */} { isTablet={isTablet} /> - {/* Content Container */} {!showTraktContent && ( @@ -945,14 +926,13 @@ const LibraryScreen = () => { {showTraktContent ? renderTraktContent() : renderContent()} - {/* DropUpMenu integration */} {selectedItem && ( setMenuVisible(false)} item={selectedItem} isWatched={!!selectedItem.watched} - isSaved={true} // Since this is from library, it's always saved + isSaved={true} onOptionSelect={async (option) => { if (!selectedItem) return; switch (option) { @@ -969,12 +949,10 @@ const LibraryScreen = () => { } case 'watched': { try { - // Use AsyncStorage to store watched status by key const key = `watched:${selectedItem.type}:${selectedItem.id}`; const newWatched = !selectedItem.watched; await mmkvStorage.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 } @@ -1273,7 +1251,7 @@ const styles = StyleSheet.create({ justifyContent: 'center', }, headerSpacer: { - width: 44, // Match the back button width + width: 44, }, traktContainer: { flex: 1, @@ -1294,4 +1272,4 @@ const styles = StyleSheet.create({ }, }); -export default LibraryScreen; +export default LibraryScreen;