Refactor LibraryScreen to include showTitles prop

This commit is contained in:
AdityasahuX07 2025-12-14 15:31:10 +05:30 committed by GitHub
parent 53dd480231
commit 1c53e65b26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -37,9 +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'; // ADD THIS IMPORT
import { useSettings } from '../hooks/useSettings';
// Define interfaces for proper typing
interface LibraryItem extends StreamingContent {
progress?: number;
lastWatched?: string;
@ -73,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;
@ -87,9 +85,20 @@ 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<string | null>(null);
const { settings } = useSettings(); // ADD THIS
useEffect(() => {
let isMounted = true;
@ -131,8 +140,7 @@ const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item:
</View>
)}
</View>
{/* Conditionally render title based on settings.showPosterTitles */}
{settings.showPosterTitles && (
{showTitles && (
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
{item.name}
</Text>
@ -189,7 +197,6 @@ const SkeletonLoader = () => {
</View>
);
// Render enough skeletons for at least two rows
const skeletonCount = numColumns * 2;
return (
<View style={styles.skeletonContainer}>
@ -213,14 +220,12 @@ const LibraryScreen = () => {
const [showTraktContent, setShowTraktContent] = useState(false);
const [selectedTraktFolder, setSelectedTraktFolder] = useState<string | null>(null);
const { showInfo, showError } = useToast();
// DropUpMenu state
const [menuVisible, setMenuVisible] = useState(false);
const [selectedItem, setSelectedItem] = useState<LibraryItem | null>(null);
const insets = useSafeAreaInsets();
const { currentTheme } = useTheme();
const { settings } = useSettings();
// Trakt integration
const {
isAuthenticated: traktAuthenticated,
isLoading: traktLoading,
@ -236,7 +241,6 @@ const LibraryScreen = () => {
loadAllCollections
} = useTraktContext();
// Force consistent status bar settings
useEffect(() => {
const applyStatusBarConfig = () => {
StatusBar.setBarStyle('light-content');
@ -247,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]);
@ -280,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'],
@ -312,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'],
@ -339,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 () => {
@ -358,7 +346,6 @@ const LibraryScreen = () => {
return true;
});
// Generate Trakt collection folders
const traktFolders = useMemo((): TraktFolder[] => {
if (!traktAuthenticated) return [];
@ -395,7 +382,6 @@ const LibraryScreen = () => {
}
];
// Only return folders that have content
return folders.filter(folder => folder.itemCount > 0);
}, [traktAuthenticated, watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent]);
@ -411,7 +397,7 @@ const LibraryScreen = () => {
>
<View>
<View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black }]}>
<FastImage
<FastImage
source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }}
style={styles.poster}
resizeMode={FastImage.resizeMode.cover}
@ -432,7 +418,6 @@ const LibraryScreen = () => {
</View>
)}
</View>
{/* Conditionally render title based on settings.showPosterTitles */}
{settings.showPosterTitles && (
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
{item.name}
@ -442,13 +427,12 @@ const LibraryScreen = () => {
</TouchableOpacity>
);
// Render individual Trakt collection folder
const renderTraktCollectionFolder = ({ folder }: { folder: TraktFolder }) => (
<TouchableOpacity
style={[styles.itemContainer, { width: itemWidth }]}
onPress={() => {
setSelectedTraktFolder(folder.id);
loadAllCollections(); // Load all collections when entering a specific folder
loadAllCollections();
}}
activeOpacity={0.7}
>
@ -468,12 +452,6 @@ const LibraryScreen = () => {
</Text>
</View>
</View>
{/* Conditionally render folder title based on settings.showPosterTitles */}
{settings.showPosterTitles && (
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
{folder.name}
</Text>
)}
</TouchableOpacity>
);
@ -485,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}
@ -505,7 +483,6 @@ const LibraryScreen = () => {
)}
</View>
</View>
{/* Conditionally render title based on settings.showPosterTitles */}
{settings.showPosterTitles && (
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
Trakt collections
@ -516,16 +493,20 @@ const LibraryScreen = () => {
);
const renderTraktItem = useCallback(({ item }: { item: TraktDisplayItem }) => {
return <TraktItem item={item} width={itemWidth} navigation={navigation} currentTheme={currentTheme} />;
}, [itemWidth, navigation, currentTheme]);
return <TraktItem
item={item}
width={itemWidth}
navigation={navigation}
currentTheme={currentTheme}
showTitles={settings.showPosterTitles}
/>;
}, [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;
@ -536,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,
@ -545,7 +526,6 @@ const LibraryScreen = () => {
}
}
}
// Add watched shows
if (watchedShows) {
for (const watchedShow of watchedShows) {
const show = watchedShow.show;
@ -556,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,
@ -568,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) {
@ -578,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,
@ -590,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,
@ -601,7 +580,6 @@ const LibraryScreen = () => {
break;
case 'watchlist':
// Add watchlist movies
if (watchlistMovies) {
for (const watchlistMovie of watchlistMovies) {
const movie = watchlistMovie.movie;
@ -612,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,
@ -620,7 +598,6 @@ const LibraryScreen = () => {
}
}
}
// Add watchlist shows
if (watchlistShows) {
for (const watchlistShow of watchlistShows) {
const show = watchlistShow.show;
@ -631,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,
@ -642,7 +619,6 @@ const LibraryScreen = () => {
break;
case 'collection':
// Add collection movies
if (collectionMovies) {
for (const collectionMovie of collectionMovies) {
const movie = collectionMovie.movie;
@ -653,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,
@ -661,7 +637,6 @@ const LibraryScreen = () => {
}
}
}
// Add collection shows
if (collectionShows) {
for (const collectionShow of collectionShows) {
const show = collectionShow.show;
@ -672,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,
@ -683,7 +658,6 @@ const LibraryScreen = () => {
break;
case 'ratings':
// Add rated content
if (ratedContent) {
for (const ratedItem of ratedContent) {
if (ratedItem.movie) {
@ -694,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,
@ -708,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,
@ -720,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;
@ -733,7 +706,6 @@ const LibraryScreen = () => {
return <TraktLoadingSpinner />;
}
// If no specific folder is selected, show the folder structure
if (!selectedTraktFolder) {
if (traktFolders.length === 0) {
return (
@ -759,7 +731,6 @@ const LibraryScreen = () => {
);
}
// Show collection folders
return (
<FlashList
data={traktFolders}
@ -774,7 +745,6 @@ const LibraryScreen = () => {
);
}
// Show content for specific folder
const folderItems = getTraktFolderItems(selectedTraktFolder);
if (folderItems.length === 0) {
@ -916,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);
@ -924,72 +893,27 @@ const LibraryScreen = () => {
return (
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
{/* Fixed position header background to prevent shifts */}
<View style={[styles.headerBackground, { height: headerHeight, backgroundColor: currentTheme.colors.darkBackground }]} />
<ScreenHeader
title={showTraktContent
? (selectedTraktFolder
? traktFolders.find(f => f.id === selectedTraktFolder)?.name || 'Collection'
: 'Trakt Collection')
: 'Library'
}
showBackButton={showTraktContent}
onBackPress={showTraktContent ? () => {
if (selectedTraktFolder) {
setSelectedTraktFolder(null);
} else {
setShowTraktContent(false);
}
} : undefined}
useMaterialIcons={showTraktContent}
rightActionIcon={!showTraktContent ? 'calendar' : undefined}
onRightActionPress={!showTraktContent ? () => navigation.navigate('Calendar') : undefined}
isTablet={isTablet}
/>
<View style={{ flex: 1 }}>
{/* Header Section with proper top spacing */}
<View style={[styles.header, { height: headerHeight, paddingTop: topSpacing }]}>
<View style={[styles.headerContent, showTraktContent && { justifyContent: 'flex-start' }]}>
{showTraktContent ? (
<>
<TouchableOpacity
style={styles.backButton}
onPress={() => {
if (selectedTraktFolder) {
setSelectedTraktFolder(null);
} else {
setShowTraktContent(false);
}
}}
activeOpacity={0.7}
>
<MaterialIcons
name="arrow-back"
size={28}
color={currentTheme.colors.white}
/>
</TouchableOpacity>
<Text style={[styles.headerTitle, { color: currentTheme.colors.white, fontSize: 24, marginLeft: 16 }]}>
{selectedTraktFolder
? traktFolders.find(f => f.id === selectedTraktFolder)?.name || 'Collection'
: 'Trakt Collection'
}
</Text>
</>
) : (
<>
<Text style={[styles.headerTitle, { color: currentTheme.colors.white }]}>Library</Text>
<TouchableOpacity
style={styles.calendarButton}
onPress={() => navigation.navigate('Calendar')}
activeOpacity={0.7}
>
<Feather
name="calendar"
size={24}
color={currentTheme.colors.white}
/>
</TouchableOpacity>
</>
)}
</View>
</View>
{/* Content Container */}
<View style={[styles.contentContainer, { backgroundColor: currentTheme.colors.darkBackground }]}>
{!showTraktContent && (
// Replaced ScrollView with View and used the modified style
<View
style={styles.filtersContainer}
>
{renderFilter('trakt', 'Trakt', 'pan-tool')}
{renderFilter('movies', 'Movies', 'movie')}
{renderFilter('series', 'TV Shows', 'live-tv')}
</View>
)}
{/* Content Container */}
<View style={[styles.contentContainer, { backgroundColor: currentTheme.colors.darkBackground }]}>
{!showTraktContent && (
<View style={styles.filtersContainer}>
@ -1002,14 +926,13 @@ const LibraryScreen = () => {
{showTraktContent ? renderTraktContent() : renderContent()}
</View>
{/* DropUpMenu integration */}
{selectedItem && (
<DropUpMenu
visible={menuVisible}
onClose={() => 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) {
@ -1026,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 }
@ -1330,7 +1251,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
headerSpacer: {
width: 44, // Match the back button width
width: 44,
},
traktContainer: {
flex: 1,