mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
Enhance Trakt integration with support for watchlist, collection, and ratings
This update expands the Trakt integration by adding functionality to manage watchlist items, collections, and user ratings. New interfaces for TraktWatchlistItem, TraktCollectionItem, and TraktRatingItem have been introduced, along with corresponding methods in the TraktService to fetch this data. The useTraktIntegration hook has been updated to load all collections, and the LibraryScreen now displays these new categories, improving the overall user experience and content organization.
This commit is contained in:
parent
17dec56bc9
commit
237c1fae3d
4 changed files with 563 additions and 17 deletions
|
|
@ -1,6 +1,13 @@
|
|||
import React, { createContext, useContext, ReactNode } from 'react';
|
||||
import { useTraktIntegration } from '../hooks/useTraktIntegration';
|
||||
import { TraktUser, TraktWatchedItem } from '../services/traktService';
|
||||
import {
|
||||
TraktUser,
|
||||
TraktWatchedItem,
|
||||
TraktWatchlistItem,
|
||||
TraktCollectionItem,
|
||||
TraktRatingItem,
|
||||
TraktPlaybackItem
|
||||
} from '../services/traktService';
|
||||
|
||||
interface TraktContextProps {
|
||||
isAuthenticated: boolean;
|
||||
|
|
@ -8,9 +15,16 @@ interface TraktContextProps {
|
|||
userProfile: TraktUser | null;
|
||||
watchedMovies: TraktWatchedItem[];
|
||||
watchedShows: TraktWatchedItem[];
|
||||
watchlistMovies: TraktWatchlistItem[];
|
||||
watchlistShows: TraktWatchlistItem[];
|
||||
collectionMovies: TraktCollectionItem[];
|
||||
collectionShows: TraktCollectionItem[];
|
||||
continueWatching: TraktPlaybackItem[];
|
||||
ratedContent: TraktRatingItem[];
|
||||
checkAuthStatus: () => Promise<void>;
|
||||
refreshAuthStatus: () => Promise<void>;
|
||||
loadWatchedItems: () => Promise<void>;
|
||||
loadAllCollections: () => Promise<void>;
|
||||
isMovieWatched: (imdbId: string) => Promise<boolean>;
|
||||
isEpisodeWatched: (imdbId: string, season: number, episode: number) => Promise<boolean>;
|
||||
markMovieAsWatched: (imdbId: string, watchedAt?: Date) => Promise<boolean>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
import { traktService, TraktUser, TraktWatchedItem, TraktContentData, TraktPlaybackItem } from '../services/traktService';
|
||||
import {
|
||||
traktService,
|
||||
TraktUser,
|
||||
TraktWatchedItem,
|
||||
TraktWatchlistItem,
|
||||
TraktCollectionItem,
|
||||
TraktRatingItem,
|
||||
TraktContentData,
|
||||
TraktPlaybackItem
|
||||
} from '../services/traktService';
|
||||
import { storageService } from '../services/storageService';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
|
|
@ -10,6 +19,12 @@ export function useTraktIntegration() {
|
|||
const [userProfile, setUserProfile] = useState<TraktUser | null>(null);
|
||||
const [watchedMovies, setWatchedMovies] = useState<TraktWatchedItem[]>([]);
|
||||
const [watchedShows, setWatchedShows] = useState<TraktWatchedItem[]>([]);
|
||||
const [watchlistMovies, setWatchlistMovies] = useState<TraktWatchlistItem[]>([]);
|
||||
const [watchlistShows, setWatchlistShows] = useState<TraktWatchlistItem[]>([]);
|
||||
const [collectionMovies, setCollectionMovies] = useState<TraktCollectionItem[]>([]);
|
||||
const [collectionShows, setCollectionShows] = useState<TraktCollectionItem[]>([]);
|
||||
const [continueWatching, setContinueWatching] = useState<TraktPlaybackItem[]>([]);
|
||||
const [ratedContent, setRatedContent] = useState<TraktRatingItem[]>([]);
|
||||
const [lastAuthCheck, setLastAuthCheck] = useState<number>(Date.now());
|
||||
|
||||
// Check authentication status
|
||||
|
|
@ -65,6 +80,41 @@ export function useTraktIntegration() {
|
|||
}
|
||||
}, [isAuthenticated]);
|
||||
|
||||
// Load all collections (watchlist, collection, continue watching, ratings)
|
||||
const loadAllCollections = useCallback(async () => {
|
||||
if (!isAuthenticated) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const [
|
||||
watchlistMovies,
|
||||
watchlistShows,
|
||||
collectionMovies,
|
||||
collectionShows,
|
||||
continueWatching,
|
||||
ratings
|
||||
] = await Promise.all([
|
||||
traktService.getWatchlistMovies(),
|
||||
traktService.getWatchlistShows(),
|
||||
traktService.getCollectionMovies(),
|
||||
traktService.getCollectionShows(),
|
||||
traktService.getPlaybackProgress(),
|
||||
traktService.getRatings()
|
||||
]);
|
||||
|
||||
setWatchlistMovies(watchlistMovies);
|
||||
setWatchlistShows(watchlistShows);
|
||||
setCollectionMovies(collectionMovies);
|
||||
setCollectionShows(collectionShows);
|
||||
setContinueWatching(continueWatching);
|
||||
setRatedContent(ratings);
|
||||
} catch (error) {
|
||||
logger.error('[useTraktIntegration] Error loading all collections:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
|
||||
// Check if a movie is watched
|
||||
const isMovieWatched = useCallback(async (imdbId: string): Promise<boolean> => {
|
||||
if (!isAuthenticated) return false;
|
||||
|
|
@ -436,8 +486,15 @@ export function useTraktIntegration() {
|
|||
userProfile,
|
||||
watchedMovies,
|
||||
watchedShows,
|
||||
watchlistMovies,
|
||||
watchlistShows,
|
||||
collectionMovies,
|
||||
collectionShows,
|
||||
continueWatching,
|
||||
ratedContent,
|
||||
checkAuthStatus,
|
||||
loadWatchedItems,
|
||||
loadAllCollections,
|
||||
isMovieWatched,
|
||||
isEpisodeWatched,
|
||||
markMovieAsWatched,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
|
|
@ -42,12 +42,22 @@ interface TraktDisplayItem {
|
|||
type: 'movie' | 'series';
|
||||
poster: string;
|
||||
year?: number;
|
||||
lastWatched: string;
|
||||
plays: number;
|
||||
lastWatched?: string;
|
||||
plays?: number;
|
||||
rating?: number;
|
||||
imdbId?: string;
|
||||
traktId: number;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const SkeletonLoader = () => {
|
||||
|
|
@ -116,6 +126,7 @@ const LibraryScreen = () => {
|
|||
const [libraryItems, setLibraryItems] = useState<LibraryItem[]>([]);
|
||||
const [filter, setFilter] = useState<'all' | 'movies' | 'series'>('all');
|
||||
const [showTraktContent, setShowTraktContent] = useState(false);
|
||||
const [selectedTraktFolder, setSelectedTraktFolder] = useState<string | null>(null);
|
||||
const insets = useSafeAreaInsets();
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
|
|
@ -125,7 +136,14 @@ const LibraryScreen = () => {
|
|||
isLoading: traktLoading,
|
||||
watchedMovies,
|
||||
watchedShows,
|
||||
loadWatchedItems
|
||||
watchlistMovies,
|
||||
watchlistShows,
|
||||
collectionMovies,
|
||||
collectionShows,
|
||||
continueWatching,
|
||||
ratedContent,
|
||||
loadWatchedItems,
|
||||
loadAllCollections
|
||||
} = useTraktContext();
|
||||
|
||||
// Force consistent status bar settings
|
||||
|
|
@ -177,6 +195,57 @@ const LibraryScreen = () => {
|
|||
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: ['#4CAF50', '#2E7D32']
|
||||
},
|
||||
{
|
||||
id: 'continue-watching',
|
||||
name: 'Continue Watching',
|
||||
icon: 'play-circle-outline',
|
||||
description: 'Resume your progress',
|
||||
itemCount: continueWatching?.length || 0,
|
||||
gradient: ['#FF9800', '#F57C00']
|
||||
},
|
||||
{
|
||||
id: 'watchlist',
|
||||
name: 'Watchlist',
|
||||
icon: 'bookmark',
|
||||
description: 'Want to watch',
|
||||
itemCount: (watchlistMovies?.length || 0) + (watchlistShows?.length || 0),
|
||||
gradient: ['#2196F3', '#1976D2']
|
||||
},
|
||||
{
|
||||
id: 'collection',
|
||||
name: 'Collection',
|
||||
icon: 'library-add',
|
||||
description: 'Your collection',
|
||||
itemCount: (collectionMovies?.length || 0) + (collectionShows?.length || 0),
|
||||
gradient: ['#9C27B0', '#7B1FA2']
|
||||
},
|
||||
{
|
||||
id: 'ratings',
|
||||
name: 'Rated',
|
||||
icon: 'star',
|
||||
description: 'Your ratings',
|
||||
itemCount: ratedContent?.length || 0,
|
||||
gradient: ['#FF5722', '#D84315']
|
||||
}
|
||||
];
|
||||
|
||||
// Only return folders that have content
|
||||
return folders.filter(folder => folder.itemCount > 0);
|
||||
}, [traktAuthenticated, watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent]);
|
||||
|
||||
// Prepare Trakt items with proper poster URLs
|
||||
const traktItems = useMemo(() => {
|
||||
if (!traktAuthenticated || (!watchedMovies?.length && !watchedShows?.length)) {
|
||||
|
|
@ -226,7 +295,11 @@ const LibraryScreen = () => {
|
|||
}
|
||||
|
||||
// Sort by last watched date (most recent first)
|
||||
return items.sort((a, b) => new Date(b.lastWatched).getTime() - new Date(a.lastWatched).getTime());
|
||||
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;
|
||||
});
|
||||
}, [traktAuthenticated, watchedMovies, watchedShows]);
|
||||
|
||||
// State for tracking poster URLs
|
||||
|
|
@ -348,6 +421,41 @@ 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
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={[styles.posterContainer, styles.folderContainer, { shadowColor: currentTheme.colors.black }]}>
|
||||
<LinearGradient
|
||||
colors={folder.gradient}
|
||||
style={styles.folderGradient}
|
||||
>
|
||||
<MaterialIcons
|
||||
name={folder.icon}
|
||||
size={60}
|
||||
color={currentTheme.colors.white}
|
||||
style={{ marginBottom: 12 }}
|
||||
/>
|
||||
<Text style={[styles.folderTitle, { color: currentTheme.colors.white }]}>
|
||||
{folder.name}
|
||||
</Text>
|
||||
<Text style={styles.folderCount}>
|
||||
{folder.itemCount} items
|
||||
</Text>
|
||||
<Text style={styles.folderSubtitle}>
|
||||
{folder.description}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
const renderTraktFolder = () => (
|
||||
<TouchableOpacity
|
||||
style={[styles.itemContainer, { width: itemWidth }]}
|
||||
|
|
@ -356,6 +464,8 @@ const LibraryScreen = () => {
|
|||
navigation.navigate('TraktSettings');
|
||||
} else {
|
||||
setShowTraktContent(true);
|
||||
setSelectedTraktFolder(null); // Reset to folder view
|
||||
loadAllCollections(); // Load all collections when opening
|
||||
}
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
|
|
@ -427,7 +537,7 @@ const LibraryScreen = () => {
|
|||
<Text style={styles.lastWatched}>
|
||||
Last watched: {item.lastWatched}
|
||||
</Text>
|
||||
{item.plays > 1 && (
|
||||
{item.plays && item.plays > 1 && (
|
||||
<Text style={styles.playsCount}>
|
||||
{item.plays} plays
|
||||
</Text>
|
||||
|
|
@ -446,18 +556,265 @@ const LibraryScreen = () => {
|
|||
);
|
||||
};
|
||||
|
||||
// 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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: movie.year,
|
||||
lastWatched: new Date(watchedMovie.last_watched_at).toLocaleDateString(),
|
||||
plays: watchedMovie.plays,
|
||||
imdbId: movie.ids.imdb,
|
||||
traktId: movie.ids.trakt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: show.year,
|
||||
lastWatched: new Date(watchedShow.last_watched_at).toLocaleDateString(),
|
||||
plays: watchedShow.plays,
|
||||
imdbId: show.ids.imdb,
|
||||
traktId: show.ids.trakt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: item.movie.year,
|
||||
lastWatched: new Date(item.paused_at).toLocaleDateString(),
|
||||
imdbId: item.movie.ids.imdb,
|
||||
traktId: item.movie.ids.trakt,
|
||||
});
|
||||
} 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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: item.show.year,
|
||||
lastWatched: new Date(item.paused_at).toLocaleDateString(),
|
||||
imdbId: item.show.ids.imdb,
|
||||
traktId: item.show.ids.trakt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: movie.year,
|
||||
lastWatched: new Date(watchlistMovie.listed_at).toLocaleDateString(),
|
||||
imdbId: movie.ids.imdb,
|
||||
traktId: movie.ids.trakt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: show.year,
|
||||
lastWatched: new Date(watchlistShow.listed_at).toLocaleDateString(),
|
||||
imdbId: show.ids.imdb,
|
||||
traktId: show.ids.trakt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: movie.year,
|
||||
lastWatched: new Date(collectionMovie.collected_at).toLocaleDateString(),
|
||||
imdbId: movie.ids.imdb,
|
||||
traktId: movie.ids.trakt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: show.year,
|
||||
lastWatched: new Date(collectionShow.collected_at).toLocaleDateString(),
|
||||
imdbId: show.ids.imdb,
|
||||
traktId: show.ids.trakt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: movie.year,
|
||||
lastWatched: new Date(ratedItem.rated_at).toLocaleDateString(),
|
||||
rating: ratedItem.rating,
|
||||
imdbId: movie.ids.imdb,
|
||||
traktId: movie.ids.trakt,
|
||||
});
|
||||
} else if (ratedItem.show) {
|
||||
const show = ratedItem.show;
|
||||
items.push({
|
||||
id: String(show.ids.trakt),
|
||||
name: show.title,
|
||||
type: 'series',
|
||||
poster: 'https://via.placeholder.com/300x450/cccccc/666666?text=Loading...',
|
||||
year: show.year,
|
||||
lastWatched: new Date(ratedItem.rated_at).toLocaleDateString(),
|
||||
rating: ratedItem.rating,
|
||||
imdbId: show.ids.imdb,
|
||||
traktId: show.ids.trakt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Sort by last watched/added date (most recent first)
|
||||
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 <SkeletonLoader />;
|
||||
}
|
||||
|
||||
if (traktItems.length === 0) {
|
||||
// If no specific folder is selected, show the folder structure
|
||||
if (!selectedTraktFolder) {
|
||||
if (traktFolders.length === 0) {
|
||||
return (
|
||||
<View style={styles.emptyContainer}>
|
||||
<TraktIcon width={80} height={80} style={{ opacity: 0.7, marginBottom: 16 }} />
|
||||
<Text style={[styles.emptyText, { color: currentTheme.colors.white }]}>No Trakt collections</Text>
|
||||
<Text style={[styles.emptySubtext, { color: currentTheme.colors.mediumGray }]}>
|
||||
Your Trakt collections will appear here once you start using Trakt
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.exploreButton, {
|
||||
backgroundColor: currentTheme.colors.primary,
|
||||
shadowColor: currentTheme.colors.black
|
||||
}]}
|
||||
onPress={() => {
|
||||
loadAllCollections();
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[styles.exploreButtonText, { color: currentTheme.colors.white }]}>Load Collections</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Show collection folders
|
||||
return (
|
||||
<FlatList
|
||||
data={traktFolders}
|
||||
renderItem={({ item }) => renderTraktCollectionFolder({ folder: item })}
|
||||
keyExtractor={item => item.id}
|
||||
numColumns={2}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
columnWrapperStyle={styles.columnWrapper}
|
||||
initialNumToRender={6}
|
||||
maxToRenderPerBatch={6}
|
||||
windowSize={5}
|
||||
removeClippedSubviews={Platform.OS === 'android'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Show content for specific folder
|
||||
const folderItems = getTraktFolderItems(selectedTraktFolder);
|
||||
|
||||
if (folderItems.length === 0) {
|
||||
const folderName = traktFolders.find(f => f.id === selectedTraktFolder)?.name || 'Collection';
|
||||
return (
|
||||
<View style={styles.emptyContainer}>
|
||||
<TraktIcon width={80} height={80} style={{ opacity: 0.7, marginBottom: 16 }} />
|
||||
<Text style={[styles.emptyText, { color: currentTheme.colors.white }]}>No watched content</Text>
|
||||
<Text style={[styles.emptyText, { color: currentTheme.colors.white }]}>No content in {folderName}</Text>
|
||||
<Text style={[styles.emptySubtext, { color: currentTheme.colors.mediumGray }]}>
|
||||
Your Trakt watched history will appear here
|
||||
This collection is empty
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.exploreButton, {
|
||||
|
|
@ -465,7 +822,7 @@ const LibraryScreen = () => {
|
|||
shadowColor: currentTheme.colors.black
|
||||
}]}
|
||||
onPress={() => {
|
||||
loadWatchedItems();
|
||||
loadAllCollections();
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
|
|
@ -475,9 +832,9 @@ const LibraryScreen = () => {
|
|||
);
|
||||
}
|
||||
|
||||
// Separate movies and shows
|
||||
const movies = traktItems.filter(item => item.type === 'movie');
|
||||
const shows = traktItems.filter(item => item.type === 'series');
|
||||
// Separate movies and shows for the selected folder
|
||||
const movies = folderItems.filter(item => item.type === 'movie');
|
||||
const shows = folderItems.filter(item => item.type === 'series');
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
|
|
@ -656,7 +1013,13 @@ const LibraryScreen = () => {
|
|||
<>
|
||||
<TouchableOpacity
|
||||
style={styles.backButton}
|
||||
onPress={() => setShowTraktContent(false)}
|
||||
onPress={() => {
|
||||
if (selectedTraktFolder) {
|
||||
setSelectedTraktFolder(null);
|
||||
} else {
|
||||
setShowTraktContent(false);
|
||||
}
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MaterialIcons
|
||||
|
|
@ -667,7 +1030,10 @@ const LibraryScreen = () => {
|
|||
</TouchableOpacity>
|
||||
<View style={styles.headerTitleContainer}>
|
||||
<Text style={[styles.headerTitle, { color: currentTheme.colors.white }]}>
|
||||
Trakt Collection
|
||||
{selectedTraktFolder
|
||||
? traktFolders.find(f => f.id === selectedTraktFolder)?.name || 'Collection'
|
||||
: 'Trakt Collection'
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.headerSpacer} />
|
||||
|
|
|
|||
|
|
@ -47,6 +47,79 @@ export interface TraktWatchedItem {
|
|||
last_watched_at: string;
|
||||
}
|
||||
|
||||
export interface TraktWatchlistItem {
|
||||
movie?: {
|
||||
title: string;
|
||||
year: number;
|
||||
ids: {
|
||||
trakt: number;
|
||||
slug: string;
|
||||
imdb: string;
|
||||
tmdb: number;
|
||||
};
|
||||
};
|
||||
show?: {
|
||||
title: string;
|
||||
year: number;
|
||||
ids: {
|
||||
trakt: number;
|
||||
slug: string;
|
||||
imdb: string;
|
||||
tmdb: number;
|
||||
};
|
||||
};
|
||||
listed_at: string;
|
||||
}
|
||||
|
||||
export interface TraktCollectionItem {
|
||||
movie?: {
|
||||
title: string;
|
||||
year: number;
|
||||
ids: {
|
||||
trakt: number;
|
||||
slug: string;
|
||||
imdb: string;
|
||||
tmdb: number;
|
||||
};
|
||||
};
|
||||
show?: {
|
||||
title: string;
|
||||
year: number;
|
||||
ids: {
|
||||
trakt: number;
|
||||
slug: string;
|
||||
imdb: string;
|
||||
tmdb: number;
|
||||
};
|
||||
};
|
||||
collected_at: string;
|
||||
}
|
||||
|
||||
export interface TraktRatingItem {
|
||||
movie?: {
|
||||
title: string;
|
||||
year: number;
|
||||
ids: {
|
||||
trakt: number;
|
||||
slug: string;
|
||||
imdb: string;
|
||||
tmdb: number;
|
||||
};
|
||||
};
|
||||
show?: {
|
||||
title: string;
|
||||
year: number;
|
||||
ids: {
|
||||
trakt: number;
|
||||
slug: string;
|
||||
imdb: string;
|
||||
tmdb: number;
|
||||
};
|
||||
};
|
||||
rating: number;
|
||||
rated_at: string;
|
||||
}
|
||||
|
||||
// New types for scrobbling
|
||||
export interface TraktPlaybackItem {
|
||||
progress: number;
|
||||
|
|
@ -578,6 +651,42 @@ export class TraktService {
|
|||
return this.apiRequest<TraktWatchedItem[]>('/sync/watched/shows');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's watchlist movies
|
||||
*/
|
||||
public async getWatchlistMovies(): Promise<TraktWatchlistItem[]> {
|
||||
return this.apiRequest<TraktWatchlistItem[]>('/sync/watchlist/movies');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's watchlist shows
|
||||
*/
|
||||
public async getWatchlistShows(): Promise<TraktWatchlistItem[]> {
|
||||
return this.apiRequest<TraktWatchlistItem[]>('/sync/watchlist/shows');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's collection movies
|
||||
*/
|
||||
public async getCollectionMovies(): Promise<TraktCollectionItem[]> {
|
||||
return this.apiRequest<TraktCollectionItem[]>('/sync/collection/movies');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's collection shows
|
||||
*/
|
||||
public async getCollectionShows(): Promise<TraktCollectionItem[]> {
|
||||
return this.apiRequest<TraktCollectionItem[]>('/sync/collection/shows');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's ratings
|
||||
*/
|
||||
public async getRatings(type?: 'movies' | 'shows'): Promise<TraktRatingItem[]> {
|
||||
const endpoint = type ? `/sync/ratings/${type}` : '/sync/ratings';
|
||||
return this.apiRequest<TraktRatingItem[]>(endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trakt id from IMDb id
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue