mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-26 19:12:54 +00:00
Merge branch 'main' of https://github.com/tapframe/NuvioStreaming
This commit is contained in:
commit
b2a9708856
1 changed files with 93 additions and 115 deletions
|
|
@ -37,8 +37,8 @@ import { useTraktContext } from '../contexts/TraktContext';
|
||||||
import TraktIcon from '../../assets/rating-icons/trakt.svg';
|
import TraktIcon from '../../assets/rating-icons/trakt.svg';
|
||||||
import { traktService, TraktService, TraktImages } from '../services/traktService';
|
import { traktService, TraktService, TraktImages } from '../services/traktService';
|
||||||
import { TraktLoadingSpinner } from '../components/common/TraktLoadingSpinner';
|
import { TraktLoadingSpinner } from '../components/common/TraktLoadingSpinner';
|
||||||
|
import { useSettings } from '../hooks/useSettings';
|
||||||
|
|
||||||
// Define interfaces for proper typing
|
|
||||||
interface LibraryItem extends StreamingContent {
|
interface LibraryItem extends StreamingContent {
|
||||||
progress?: number;
|
progress?: number;
|
||||||
lastWatched?: string;
|
lastWatched?: string;
|
||||||
|
|
@ -72,10 +72,9 @@ interface TraktFolder {
|
||||||
|
|
||||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||||
|
|
||||||
// Compute responsive grid layout (more columns on tablets)
|
|
||||||
function getGridLayout(screenWidth: number): { numColumns: number; itemWidth: number } {
|
function getGridLayout(screenWidth: number): { numColumns: number; itemWidth: number } {
|
||||||
const horizontalPadding = 16; // matches listContainer padding (approx)
|
const horizontalPadding = 16;
|
||||||
const gutter = 12; // space between items (via space-between + marginBottom)
|
const gutter = 12;
|
||||||
let numColumns = 3;
|
let numColumns = 3;
|
||||||
if (screenWidth >= 1200) numColumns = 5;
|
if (screenWidth >= 1200) numColumns = 5;
|
||||||
else if (screenWidth >= 1000) numColumns = 4;
|
else if (screenWidth >= 1000) numColumns = 4;
|
||||||
|
|
@ -86,7 +85,19 @@ function getGridLayout(screenWidth: number): { numColumns: number; itemWidth: nu
|
||||||
return { numColumns, itemWidth };
|
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 [posterUrl, setPosterUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -129,9 +140,11 @@ const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item:
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
|
{showTitles && (
|
||||||
{item.name}
|
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
</Text>
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
@ -184,7 +197,6 @@ const SkeletonLoader = () => {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Render enough skeletons for at least two rows
|
|
||||||
const skeletonCount = numColumns * 2;
|
const skeletonCount = numColumns * 2;
|
||||||
return (
|
return (
|
||||||
<View style={styles.skeletonContainer}>
|
<View style={styles.skeletonContainer}>
|
||||||
|
|
@ -208,13 +220,12 @@ const LibraryScreen = () => {
|
||||||
const [showTraktContent, setShowTraktContent] = useState(false);
|
const [showTraktContent, setShowTraktContent] = useState(false);
|
||||||
const [selectedTraktFolder, setSelectedTraktFolder] = useState<string | null>(null);
|
const [selectedTraktFolder, setSelectedTraktFolder] = useState<string | null>(null);
|
||||||
const { showInfo, showError } = useToast();
|
const { showInfo, showError } = useToast();
|
||||||
// DropUpMenu state
|
|
||||||
const [menuVisible, setMenuVisible] = useState(false);
|
const [menuVisible, setMenuVisible] = useState(false);
|
||||||
const [selectedItem, setSelectedItem] = useState<LibraryItem | null>(null);
|
const [selectedItem, setSelectedItem] = useState<LibraryItem | null>(null);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
const { settings } = useSettings();
|
||||||
|
|
||||||
// Trakt integration
|
|
||||||
const {
|
const {
|
||||||
isAuthenticated: traktAuthenticated,
|
isAuthenticated: traktAuthenticated,
|
||||||
isLoading: traktLoading,
|
isLoading: traktLoading,
|
||||||
|
|
@ -230,7 +241,6 @@ const LibraryScreen = () => {
|
||||||
loadAllCollections
|
loadAllCollections
|
||||||
} = useTraktContext();
|
} = useTraktContext();
|
||||||
|
|
||||||
// Force consistent status bar settings
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const applyStatusBarConfig = () => {
|
const applyStatusBarConfig = () => {
|
||||||
StatusBar.setBarStyle('light-content');
|
StatusBar.setBarStyle('light-content');
|
||||||
|
|
@ -241,30 +251,24 @@ const LibraryScreen = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
applyStatusBarConfig();
|
applyStatusBarConfig();
|
||||||
|
|
||||||
// Re-apply on focus
|
|
||||||
const unsubscribe = navigation.addListener('focus', applyStatusBarConfig);
|
const unsubscribe = navigation.addListener('focus', applyStatusBarConfig);
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|
||||||
// Handle hardware back button and gesture navigation
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const backAction = () => {
|
const backAction = () => {
|
||||||
if (showTraktContent) {
|
if (showTraktContent) {
|
||||||
if (selectedTraktFolder) {
|
if (selectedTraktFolder) {
|
||||||
// If in a specific folder, go back to folder list
|
|
||||||
setSelectedTraktFolder(null);
|
setSelectedTraktFolder(null);
|
||||||
} else {
|
} else {
|
||||||
// If in Trakt collections view, go back to main library
|
|
||||||
setShowTraktContent(false);
|
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);
|
const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
|
||||||
|
|
||||||
return () => backHandler.remove();
|
return () => backHandler.remove();
|
||||||
}, [showTraktContent, selectedTraktFolder]);
|
}, [showTraktContent, selectedTraktFolder]);
|
||||||
|
|
||||||
|
|
@ -274,16 +278,13 @@ const LibraryScreen = () => {
|
||||||
try {
|
try {
|
||||||
const items = await catalogService.getLibraryItems();
|
const items = await catalogService.getLibraryItems();
|
||||||
|
|
||||||
// Sort by date added (most recent first)
|
|
||||||
const sortedItems = items.sort((a, b) => {
|
const sortedItems = items.sort((a, b) => {
|
||||||
const timeA = (a as any).addedToLibraryAt || 0;
|
const timeA = (a as any).addedToLibraryAt || 0;
|
||||||
const timeB = (b 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) => {
|
const updatedItems = await Promise.all(sortedItems.map(async (item) => {
|
||||||
// Map StreamingContent to LibraryItem shape
|
|
||||||
const libraryItem: LibraryItem = {
|
const libraryItem: LibraryItem = {
|
||||||
...item,
|
...item,
|
||||||
gradient: Array.isArray((item as any).gradient) ? (item as any).gradient : ['#222', '#444'],
|
gradient: Array.isArray((item as any).gradient) ? (item as any).gradient : ['#222', '#444'],
|
||||||
|
|
@ -306,18 +307,14 @@ const LibraryScreen = () => {
|
||||||
|
|
||||||
loadLibrary();
|
loadLibrary();
|
||||||
|
|
||||||
// Subscribe to library updates
|
|
||||||
const unsubscribe = catalogService.subscribeToLibraryUpdates(async (items) => {
|
const unsubscribe = catalogService.subscribeToLibraryUpdates(async (items) => {
|
||||||
// Sort by date added (most recent first)
|
|
||||||
const sortedItems = items.sort((a, b) => {
|
const sortedItems = items.sort((a, b) => {
|
||||||
const timeA = (a as any).addedToLibraryAt || 0;
|
const timeA = (a as any).addedToLibraryAt || 0;
|
||||||
const timeB = (b 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) => {
|
const updatedItems = await Promise.all(sortedItems.map(async (item) => {
|
||||||
// Map StreamingContent to LibraryItem shape
|
|
||||||
const libraryItem: LibraryItem = {
|
const libraryItem: LibraryItem = {
|
||||||
...item,
|
...item,
|
||||||
gradient: Array.isArray((item as any).gradient) ? (item as any).gradient : ['#222', '#444'],
|
gradient: Array.isArray((item as any).gradient) ? (item as any).gradient : ['#222', '#444'],
|
||||||
|
|
@ -333,10 +330,7 @@ const LibraryScreen = () => {
|
||||||
setLibraryItems(updatedItems);
|
setLibraryItems(updatedItems);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for watched status changes
|
|
||||||
const watchedSub = DeviceEventEmitter.addListener('watchedStatusChanged', loadLibrary);
|
const watchedSub = DeviceEventEmitter.addListener('watchedStatusChanged', loadLibrary);
|
||||||
|
|
||||||
// Refresh when screen regains focus
|
|
||||||
const focusSub = navigation.addListener('focus', loadLibrary);
|
const focusSub = navigation.addListener('focus', loadLibrary);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -352,7 +346,6 @@ const LibraryScreen = () => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate Trakt collection folders
|
|
||||||
const traktFolders = useMemo((): TraktFolder[] => {
|
const traktFolders = useMemo((): TraktFolder[] => {
|
||||||
if (!traktAuthenticated) return [];
|
if (!traktAuthenticated) return [];
|
||||||
|
|
||||||
|
|
@ -389,61 +382,57 @@ const LibraryScreen = () => {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Only return folders that have content
|
|
||||||
return folders.filter(folder => folder.itemCount > 0);
|
return folders.filter(folder => folder.itemCount > 0);
|
||||||
}, [traktAuthenticated, watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent]);
|
}, [traktAuthenticated, watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent]);
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: LibraryItem }) => {
|
const renderItem = ({ item }: { item: LibraryItem }) => (
|
||||||
const aspectRatio = item.posterShape === 'landscape' ? 16 / 9 : (item.posterShape === 'square' ? 1 : 2 / 3);
|
<TouchableOpacity
|
||||||
|
style={[styles.itemContainer, { width: itemWidth }]}
|
||||||
return (
|
onPress={() => navigation.navigate('Metadata', { id: item.id, type: item.type })}
|
||||||
<TouchableOpacity
|
onLongPress={() => {
|
||||||
style={[styles.itemContainer, { width: itemWidth }]}
|
setSelectedItem(item);
|
||||||
onPress={() => navigation.navigate('Metadata', { id: item.id, type: item.type })}
|
setMenuVisible(true);
|
||||||
onLongPress={() => {
|
}}
|
||||||
setSelectedItem(item);
|
activeOpacity={0.7}
|
||||||
setMenuVisible(true);
|
>
|
||||||
}}
|
<View>
|
||||||
activeOpacity={0.7}
|
<View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black }]}>
|
||||||
>
|
<FastImage
|
||||||
<View>
|
source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }}
|
||||||
<View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black, aspectRatio }]}>
|
style={styles.poster}
|
||||||
<FastImage
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }}
|
/>
|
||||||
style={styles.poster}
|
{item.watched && (
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
<View style={styles.watchedIndicator}>
|
||||||
/>
|
<MaterialIcons name="check-circle" size={22} color={currentTheme.colors.success || '#4CAF50'} />
|
||||||
{item.watched && (
|
</View>
|
||||||
<View style={styles.watchedIndicator}>
|
)}
|
||||||
<MaterialIcons name="check-circle" size={22} color={currentTheme.colors.success || '#4CAF50'} />
|
{item.progress !== undefined && item.progress < 1 && (
|
||||||
</View>
|
<View style={styles.progressBarContainer}>
|
||||||
)}
|
<View
|
||||||
{item.progress !== undefined && item.progress < 1 && (
|
style={[
|
||||||
<View style={styles.progressBarContainer}>
|
styles.progressBar,
|
||||||
<View
|
{ width: `${item.progress * 100}%`, backgroundColor: currentTheme.colors.primary }
|
||||||
style={[
|
]}
|
||||||
styles.progressBar,
|
/>
|
||||||
{ width: `${item.progress * 100}%`, backgroundColor: currentTheme.colors.primary }
|
</View>
|
||||||
]}
|
)}
|
||||||
/>
|
</View>
|
||||||
</View>
|
{settings.showPosterTitles && (
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
|
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
)}
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
);
|
</TouchableOpacity>
|
||||||
};
|
);
|
||||||
|
|
||||||
// Render individual Trakt collection folder
|
|
||||||
const renderTraktCollectionFolder = ({ folder }: { folder: TraktFolder }) => (
|
const renderTraktCollectionFolder = ({ folder }: { folder: TraktFolder }) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.itemContainer, { width: itemWidth }]}
|
style={[styles.itemContainer, { width: itemWidth }]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setSelectedTraktFolder(folder.id);
|
setSelectedTraktFolder(folder.id);
|
||||||
loadAllCollections(); // Load all collections when entering a specific folder
|
loadAllCollections();
|
||||||
}}
|
}}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
|
|
@ -474,8 +463,8 @@ const LibraryScreen = () => {
|
||||||
navigation.navigate('TraktSettings');
|
navigation.navigate('TraktSettings');
|
||||||
} else {
|
} else {
|
||||||
setShowTraktContent(true);
|
setShowTraktContent(true);
|
||||||
setSelectedTraktFolder(null); // Reset to folder view
|
setSelectedTraktFolder(null);
|
||||||
loadAllCollections(); // Load all collections when opening
|
loadAllCollections();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
|
|
@ -494,24 +483,30 @@ const LibraryScreen = () => {
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
|
{settings.showPosterTitles && (
|
||||||
Trakt collections
|
<Text style={[styles.cardTitle, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
</Text>
|
Trakt collections
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderTraktItem = useCallback(({ item }: { item: TraktDisplayItem }) => {
|
const renderTraktItem = useCallback(({ item }: { item: TraktDisplayItem }) => {
|
||||||
return <TraktItem item={item} width={itemWidth} navigation={navigation} currentTheme={currentTheme} />;
|
return <TraktItem
|
||||||
}, [itemWidth, navigation, currentTheme]);
|
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 getTraktFolderItems = useCallback((folderId: string): TraktDisplayItem[] => {
|
||||||
const items: TraktDisplayItem[] = [];
|
const items: TraktDisplayItem[] = [];
|
||||||
|
|
||||||
switch (folderId) {
|
switch (folderId) {
|
||||||
case 'watched':
|
case 'watched':
|
||||||
// Add watched movies
|
|
||||||
if (watchedMovies) {
|
if (watchedMovies) {
|
||||||
for (const watchedMovie of watchedMovies) {
|
for (const watchedMovie of watchedMovies) {
|
||||||
const movie = watchedMovie.movie;
|
const movie = watchedMovie.movie;
|
||||||
|
|
@ -522,7 +517,7 @@ const LibraryScreen = () => {
|
||||||
type: 'movie',
|
type: 'movie',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: movie.year,
|
year: movie.year,
|
||||||
lastWatched: watchedMovie.last_watched_at, // Store raw timestamp for sorting
|
lastWatched: watchedMovie.last_watched_at,
|
||||||
plays: watchedMovie.plays,
|
plays: watchedMovie.plays,
|
||||||
imdbId: movie.ids.imdb,
|
imdbId: movie.ids.imdb,
|
||||||
traktId: movie.ids.trakt,
|
traktId: movie.ids.trakt,
|
||||||
|
|
@ -531,7 +526,6 @@ const LibraryScreen = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add watched shows
|
|
||||||
if (watchedShows) {
|
if (watchedShows) {
|
||||||
for (const watchedShow of watchedShows) {
|
for (const watchedShow of watchedShows) {
|
||||||
const show = watchedShow.show;
|
const show = watchedShow.show;
|
||||||
|
|
@ -542,7 +536,7 @@ const LibraryScreen = () => {
|
||||||
type: 'series',
|
type: 'series',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: show.year,
|
year: show.year,
|
||||||
lastWatched: watchedShow.last_watched_at, // Store raw timestamp for sorting
|
lastWatched: watchedShow.last_watched_at,
|
||||||
plays: watchedShow.plays,
|
plays: watchedShow.plays,
|
||||||
imdbId: show.ids.imdb,
|
imdbId: show.ids.imdb,
|
||||||
traktId: show.ids.trakt,
|
traktId: show.ids.trakt,
|
||||||
|
|
@ -554,7 +548,6 @@ const LibraryScreen = () => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'continue-watching':
|
case 'continue-watching':
|
||||||
// Add continue watching items
|
|
||||||
if (continueWatching) {
|
if (continueWatching) {
|
||||||
for (const item of continueWatching) {
|
for (const item of continueWatching) {
|
||||||
if (item.type === 'movie' && item.movie) {
|
if (item.type === 'movie' && item.movie) {
|
||||||
|
|
@ -564,7 +557,7 @@ const LibraryScreen = () => {
|
||||||
type: 'movie',
|
type: 'movie',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: item.movie.year,
|
year: item.movie.year,
|
||||||
lastWatched: item.paused_at, // Store raw timestamp for sorting
|
lastWatched: item.paused_at,
|
||||||
imdbId: item.movie.ids.imdb,
|
imdbId: item.movie.ids.imdb,
|
||||||
traktId: item.movie.ids.trakt,
|
traktId: item.movie.ids.trakt,
|
||||||
images: item.movie.images,
|
images: item.movie.images,
|
||||||
|
|
@ -576,7 +569,7 @@ const LibraryScreen = () => {
|
||||||
type: 'series',
|
type: 'series',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: item.show.year,
|
year: item.show.year,
|
||||||
lastWatched: item.paused_at, // Store raw timestamp for sorting
|
lastWatched: item.paused_at,
|
||||||
imdbId: item.show.ids.imdb,
|
imdbId: item.show.ids.imdb,
|
||||||
traktId: item.show.ids.trakt,
|
traktId: item.show.ids.trakt,
|
||||||
images: item.show.images,
|
images: item.show.images,
|
||||||
|
|
@ -587,7 +580,6 @@ const LibraryScreen = () => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'watchlist':
|
case 'watchlist':
|
||||||
// Add watchlist movies
|
|
||||||
if (watchlistMovies) {
|
if (watchlistMovies) {
|
||||||
for (const watchlistMovie of watchlistMovies) {
|
for (const watchlistMovie of watchlistMovies) {
|
||||||
const movie = watchlistMovie.movie;
|
const movie = watchlistMovie.movie;
|
||||||
|
|
@ -598,7 +590,7 @@ const LibraryScreen = () => {
|
||||||
type: 'movie',
|
type: 'movie',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: movie.year,
|
year: movie.year,
|
||||||
lastWatched: watchlistMovie.listed_at, // Store raw timestamp for sorting
|
lastWatched: watchlistMovie.listed_at,
|
||||||
imdbId: movie.ids.imdb,
|
imdbId: movie.ids.imdb,
|
||||||
traktId: movie.ids.trakt,
|
traktId: movie.ids.trakt,
|
||||||
images: movie.images,
|
images: movie.images,
|
||||||
|
|
@ -606,7 +598,6 @@ const LibraryScreen = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add watchlist shows
|
|
||||||
if (watchlistShows) {
|
if (watchlistShows) {
|
||||||
for (const watchlistShow of watchlistShows) {
|
for (const watchlistShow of watchlistShows) {
|
||||||
const show = watchlistShow.show;
|
const show = watchlistShow.show;
|
||||||
|
|
@ -617,7 +608,7 @@ const LibraryScreen = () => {
|
||||||
type: 'series',
|
type: 'series',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: show.year,
|
year: show.year,
|
||||||
lastWatched: watchlistShow.listed_at, // Store raw timestamp for sorting
|
lastWatched: watchlistShow.listed_at,
|
||||||
imdbId: show.ids.imdb,
|
imdbId: show.ids.imdb,
|
||||||
traktId: show.ids.trakt,
|
traktId: show.ids.trakt,
|
||||||
images: show.images,
|
images: show.images,
|
||||||
|
|
@ -628,7 +619,6 @@ const LibraryScreen = () => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'collection':
|
case 'collection':
|
||||||
// Add collection movies
|
|
||||||
if (collectionMovies) {
|
if (collectionMovies) {
|
||||||
for (const collectionMovie of collectionMovies) {
|
for (const collectionMovie of collectionMovies) {
|
||||||
const movie = collectionMovie.movie;
|
const movie = collectionMovie.movie;
|
||||||
|
|
@ -639,7 +629,7 @@ const LibraryScreen = () => {
|
||||||
type: 'movie',
|
type: 'movie',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: movie.year,
|
year: movie.year,
|
||||||
lastWatched: collectionMovie.collected_at, // Store raw timestamp for sorting
|
lastWatched: collectionMovie.collected_at,
|
||||||
imdbId: movie.ids.imdb,
|
imdbId: movie.ids.imdb,
|
||||||
traktId: movie.ids.trakt,
|
traktId: movie.ids.trakt,
|
||||||
images: movie.images,
|
images: movie.images,
|
||||||
|
|
@ -647,7 +637,6 @@ const LibraryScreen = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add collection shows
|
|
||||||
if (collectionShows) {
|
if (collectionShows) {
|
||||||
for (const collectionShow of collectionShows) {
|
for (const collectionShow of collectionShows) {
|
||||||
const show = collectionShow.show;
|
const show = collectionShow.show;
|
||||||
|
|
@ -658,7 +647,7 @@ const LibraryScreen = () => {
|
||||||
type: 'series',
|
type: 'series',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: show.year,
|
year: show.year,
|
||||||
lastWatched: collectionShow.collected_at, // Store raw timestamp for sorting
|
lastWatched: collectionShow.collected_at,
|
||||||
imdbId: show.ids.imdb,
|
imdbId: show.ids.imdb,
|
||||||
traktId: show.ids.trakt,
|
traktId: show.ids.trakt,
|
||||||
images: show.images,
|
images: show.images,
|
||||||
|
|
@ -669,7 +658,6 @@ const LibraryScreen = () => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ratings':
|
case 'ratings':
|
||||||
// Add rated content
|
|
||||||
if (ratedContent) {
|
if (ratedContent) {
|
||||||
for (const ratedItem of ratedContent) {
|
for (const ratedItem of ratedContent) {
|
||||||
if (ratedItem.movie) {
|
if (ratedItem.movie) {
|
||||||
|
|
@ -680,7 +668,7 @@ const LibraryScreen = () => {
|
||||||
type: 'movie',
|
type: 'movie',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: movie.year,
|
year: movie.year,
|
||||||
lastWatched: ratedItem.rated_at, // Store raw timestamp for sorting
|
lastWatched: ratedItem.rated_at,
|
||||||
rating: ratedItem.rating,
|
rating: ratedItem.rating,
|
||||||
imdbId: movie.ids.imdb,
|
imdbId: movie.ids.imdb,
|
||||||
traktId: movie.ids.trakt,
|
traktId: movie.ids.trakt,
|
||||||
|
|
@ -694,7 +682,7 @@ const LibraryScreen = () => {
|
||||||
type: 'series',
|
type: 'series',
|
||||||
poster: 'placeholder',
|
poster: 'placeholder',
|
||||||
year: show.year,
|
year: show.year,
|
||||||
lastWatched: ratedItem.rated_at, // Store raw timestamp for sorting
|
lastWatched: ratedItem.rated_at,
|
||||||
rating: ratedItem.rating,
|
rating: ratedItem.rating,
|
||||||
imdbId: show.ids.imdb,
|
imdbId: show.ids.imdb,
|
||||||
traktId: show.ids.trakt,
|
traktId: show.ids.trakt,
|
||||||
|
|
@ -706,7 +694,6 @@ const LibraryScreen = () => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by last watched/added date (most recent first) using raw timestamps
|
|
||||||
return items.sort((a, b) => {
|
return items.sort((a, b) => {
|
||||||
const dateA = a.lastWatched ? new Date(a.lastWatched).getTime() : 0;
|
const dateA = a.lastWatched ? new Date(a.lastWatched).getTime() : 0;
|
||||||
const dateB = b.lastWatched ? new Date(b.lastWatched).getTime() : 0;
|
const dateB = b.lastWatched ? new Date(b.lastWatched).getTime() : 0;
|
||||||
|
|
@ -719,7 +706,6 @@ const LibraryScreen = () => {
|
||||||
return <TraktLoadingSpinner />;
|
return <TraktLoadingSpinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no specific folder is selected, show the folder structure
|
|
||||||
if (!selectedTraktFolder) {
|
if (!selectedTraktFolder) {
|
||||||
if (traktFolders.length === 0) {
|
if (traktFolders.length === 0) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -745,7 +731,6 @@ const LibraryScreen = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show collection folders
|
|
||||||
return (
|
return (
|
||||||
<FlashList
|
<FlashList
|
||||||
data={traktFolders}
|
data={traktFolders}
|
||||||
|
|
@ -760,7 +745,6 @@ const LibraryScreen = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show content for specific folder
|
|
||||||
const folderItems = getTraktFolderItems(selectedTraktFolder);
|
const folderItems = getTraktFolderItems(selectedTraktFolder);
|
||||||
|
|
||||||
if (folderItems.length === 0) {
|
if (folderItems.length === 0) {
|
||||||
|
|
@ -902,7 +886,6 @@ const LibraryScreen = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tablet detection aligned with navigation tablet logic
|
|
||||||
const isTablet = useMemo(() => {
|
const isTablet = useMemo(() => {
|
||||||
const smallestDimension = Math.min(width, height);
|
const smallestDimension = Math.min(width, height);
|
||||||
return (Platform.OS === 'ios' ? (Platform as any).isPad === true : smallestDimension >= 768);
|
return (Platform.OS === 'ios' ? (Platform as any).isPad === true : smallestDimension >= 768);
|
||||||
|
|
@ -910,7 +893,6 @@ const LibraryScreen = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||||
{/* ScreenHeader Component */}
|
|
||||||
<ScreenHeader
|
<ScreenHeader
|
||||||
title={showTraktContent
|
title={showTraktContent
|
||||||
? (selectedTraktFolder
|
? (selectedTraktFolder
|
||||||
|
|
@ -932,7 +914,6 @@ const LibraryScreen = () => {
|
||||||
isTablet={isTablet}
|
isTablet={isTablet}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Content Container */}
|
|
||||||
<View style={[styles.contentContainer, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
<View style={[styles.contentContainer, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||||
{!showTraktContent && (
|
{!showTraktContent && (
|
||||||
<View style={styles.filtersContainer}>
|
<View style={styles.filtersContainer}>
|
||||||
|
|
@ -945,14 +926,13 @@ const LibraryScreen = () => {
|
||||||
{showTraktContent ? renderTraktContent() : renderContent()}
|
{showTraktContent ? renderTraktContent() : renderContent()}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* DropUpMenu integration */}
|
|
||||||
{selectedItem && (
|
{selectedItem && (
|
||||||
<DropUpMenu
|
<DropUpMenu
|
||||||
visible={menuVisible}
|
visible={menuVisible}
|
||||||
onClose={() => setMenuVisible(false)}
|
onClose={() => setMenuVisible(false)}
|
||||||
item={selectedItem}
|
item={selectedItem}
|
||||||
isWatched={!!selectedItem.watched}
|
isWatched={!!selectedItem.watched}
|
||||||
isSaved={true} // Since this is from library, it's always saved
|
isSaved={true}
|
||||||
onOptionSelect={async (option) => {
|
onOptionSelect={async (option) => {
|
||||||
if (!selectedItem) return;
|
if (!selectedItem) return;
|
||||||
switch (option) {
|
switch (option) {
|
||||||
|
|
@ -969,12 +949,10 @@ const LibraryScreen = () => {
|
||||||
}
|
}
|
||||||
case 'watched': {
|
case 'watched': {
|
||||||
try {
|
try {
|
||||||
// Use AsyncStorage to store watched status by key
|
|
||||||
const key = `watched:${selectedItem.type}:${selectedItem.id}`;
|
const key = `watched:${selectedItem.type}:${selectedItem.id}`;
|
||||||
const newWatched = !selectedItem.watched;
|
const newWatched = !selectedItem.watched;
|
||||||
await mmkvStorage.setItem(key, newWatched ? 'true' : 'false');
|
await mmkvStorage.setItem(key, newWatched ? 'true' : 'false');
|
||||||
showInfo(newWatched ? 'Marked as Watched' : 'Marked as Unwatched', newWatched ? 'Item marked as watched' : 'Item marked as unwatched');
|
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 =>
|
setLibraryItems(prev => prev.map(item =>
|
||||||
item.id === selectedItem.id && item.type === selectedItem.type
|
item.id === selectedItem.id && item.type === selectedItem.type
|
||||||
? { ...item, watched: newWatched }
|
? { ...item, watched: newWatched }
|
||||||
|
|
@ -1273,7 +1251,7 @@ const styles = StyleSheet.create({
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
headerSpacer: {
|
headerSpacer: {
|
||||||
width: 44, // Match the back button width
|
width: 44,
|
||||||
},
|
},
|
||||||
traktContainer: {
|
traktContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue