mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
chore: improved tmdb enrichment logic
This commit is contained in:
parent
3d5a9ebf42
commit
4aa22cc1c3
8 changed files with 413 additions and 343 deletions
|
|
@ -226,7 +226,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
|
||||
try {
|
||||
const shouldFetchMeta = await stremioService.isValidContentId(type, id);
|
||||
|
||||
|
||||
const [metadata, basicContent, addonSpecificMeta] = await Promise.all([
|
||||
shouldFetchMeta ? stremioService.getMetaDetails(type, id) : Promise.resolve(null),
|
||||
catalogService.getBasicContentDetails(type, id),
|
||||
|
|
@ -237,7 +237,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
]);
|
||||
|
||||
const preferredAddonMeta = addonSpecificMeta || metadata;
|
||||
|
||||
|
||||
|
||||
const finalContent = basicContent ? {
|
||||
...basicContent,
|
||||
|
|
@ -245,7 +245,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
...(preferredAddonMeta?.poster && { poster: preferredAddonMeta.poster }),
|
||||
...(preferredAddonMeta?.description && { description: preferredAddonMeta.description }),
|
||||
} : null;
|
||||
|
||||
|
||||
|
||||
if (finalContent) {
|
||||
const result = {
|
||||
|
|
@ -263,11 +263,11 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
const findNextEpisode = useCallback((
|
||||
currentSeason: number,
|
||||
currentEpisode: number,
|
||||
currentSeason: number,
|
||||
currentEpisode: number,
|
||||
videos: any[],
|
||||
watchedSet?: Set<string>,
|
||||
showId?: string
|
||||
|
|
@ -282,16 +282,16 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
const isAlreadyWatched = (season: number, episode: number): boolean => {
|
||||
if (!watchedSet || !showId) return false;
|
||||
const cleanShowId = showId.startsWith('tt') ? showId : `tt${showId}`;
|
||||
return watchedSet.has(`${cleanShowId}:${season}:${episode}`) ||
|
||||
watchedSet.has(`${showId}:${season}:${episode}`);
|
||||
return watchedSet.has(`${cleanShowId}:${season}:${episode}`) ||
|
||||
watchedSet.has(`${showId}:${season}:${episode}`);
|
||||
};
|
||||
|
||||
for (const video of sortedVideos) {
|
||||
if (video.season < currentSeason) continue;
|
||||
if (video.season === currentSeason && video.episode <= currentEpisode) continue;
|
||||
|
||||
|
||||
if (isAlreadyWatched(video.season, video.episode)) continue;
|
||||
|
||||
|
||||
if (isEpisodeReleased(video)) {
|
||||
return video;
|
||||
}
|
||||
|
|
@ -299,7 +299,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
// Modified loadContinueWatching to render incrementally
|
||||
const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => {
|
||||
|
|
@ -379,7 +379,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
const progressPercent =
|
||||
progress.duration > 0
|
||||
? (progress.currentTime / progress.duration) * 100
|
||||
: 0;
|
||||
: 0;
|
||||
// Skip fully watched movies
|
||||
if (type === 'movie' && progressPercent >= 85) continue;
|
||||
// Skip movies with no actual progress (ensure > 0%)
|
||||
|
|
@ -533,7 +533,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (!nextEpisode && metadata?.videos) {
|
||||
|
|
@ -558,7 +558,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
} as ContinueWatchingItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
@ -711,7 +711,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
const movieKey = `movie:${imdbId}`;
|
||||
if (recentlyRemovedRef.current.has(movieKey)) continue;
|
||||
|
||||
const cachedData = await getCachedMetadata('movie', imdbId, item.addonId);
|
||||
const cachedData = await getCachedMetadata('movie', imdbId);
|
||||
if (!cachedData?.basicContent) continue;
|
||||
|
||||
const pausedAt = new Date(item.paused_at).getTime();
|
||||
|
|
@ -743,7 +743,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
continue;
|
||||
}
|
||||
|
||||
const cachedData = await getCachedMetadata('series', showImdb, item.addonId);
|
||||
const cachedData = await getCachedMetadata('series', showImdb);
|
||||
if (!cachedData?.basicContent) continue;
|
||||
|
||||
traktBatch.push({
|
||||
|
|
@ -1204,41 +1204,40 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
padding: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12
|
||||
}
|
||||
]}>
|
||||
{(() => {
|
||||
const isUpNext = item.type === 'series' && item.progress === 0;
|
||||
return (
|
||||
<View style={styles.titleRow}>
|
||||
<Text
|
||||
style={[
|
||||
styles.contentTitle,
|
||||
{
|
||||
color: currentTheme.colors.highEmphasis,
|
||||
fontSize: isTV ? 20 : isLargeTablet ? 18 : isTablet ? 17 : 16
|
||||
}
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
{isUpNext && (
|
||||
<View style={[
|
||||
styles.progressBadge,
|
||||
{
|
||||
backgroundColor: currentTheme.colors.primary,
|
||||
paddingHorizontal: isTV ? 12 : isLargeTablet ? 10 : isTablet ? 8 : 8,
|
||||
paddingVertical: isTV ? 6 : isLargeTablet ? 5 : isTablet ? 4 : 3
|
||||
}
|
||||
]}>
|
||||
<Text style={[
|
||||
styles.progressText,
|
||||
{ fontSize: isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12 }
|
||||
]}>Up Next</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})()}
|
||||
</View>
|
||||
{(() => {
|
||||
const isUpNext = item.type === 'series' && item.progress === 0;
|
||||
return (
|
||||
<View style={styles.titleRow}>
|
||||
<Text
|
||||
style={[
|
||||
styles.contentTitle,
|
||||
{
|
||||
color: currentTheme.colors.highEmphasis,
|
||||
fontSize: isTV ? 20 : isLargeTablet ? 18 : isTablet ? 17 : 16
|
||||
}
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
{isUpNext && (
|
||||
<View style={[
|
||||
styles.progressBadge,
|
||||
{
|
||||
backgroundColor: currentTheme.colors.primary,
|
||||
paddingHorizontal: isTV ? 12 : isLargeTablet ? 10 : isTablet ? 8 : 8,
|
||||
paddingVertical: isTV ? 6 : isLargeTablet ? 5 : isTablet ? 4 : 3
|
||||
}
|
||||
]}>
|
||||
<Text style={[
|
||||
styles.progressText,
|
||||
{ fontSize: isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12 }
|
||||
]}>Up Next</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Episode Info or Year */}
|
||||
{(() => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import React, { createContext, useContext, ReactNode } from 'react';
|
||||
import { useTraktIntegration } from '../hooks/useTraktIntegration';
|
||||
import {
|
||||
TraktUser,
|
||||
TraktWatchedItem,
|
||||
TraktWatchlistItem,
|
||||
TraktCollectionItem,
|
||||
import {
|
||||
TraktUser,
|
||||
TraktWatchedItem,
|
||||
TraktWatchlistItem,
|
||||
TraktCollectionItem,
|
||||
TraktRatingItem,
|
||||
TraktPlaybackItem
|
||||
TraktPlaybackItem,
|
||||
traktService
|
||||
} from '../services/traktService';
|
||||
|
||||
interface TraktContextProps {
|
||||
|
|
@ -37,15 +38,25 @@ interface TraktContextProps {
|
|||
removeFromCollection: (imdbId: string, type: 'movie' | 'show') => Promise<boolean>;
|
||||
isInWatchlist: (imdbId: string, type: 'movie' | 'show') => boolean;
|
||||
isInCollection: (imdbId: string, type: 'movie' | 'show') => boolean;
|
||||
// Maintenance mode
|
||||
isMaintenanceMode: boolean;
|
||||
maintenanceMessage: string;
|
||||
}
|
||||
|
||||
const TraktContext = createContext<TraktContextProps | undefined>(undefined);
|
||||
|
||||
export function TraktProvider({ children }: { children: ReactNode }) {
|
||||
const traktIntegration = useTraktIntegration();
|
||||
|
||||
|
||||
// Add maintenance mode values to the context
|
||||
const contextValue: TraktContextProps = {
|
||||
...traktIntegration,
|
||||
isMaintenanceMode: traktService.isMaintenanceMode(),
|
||||
maintenanceMessage: traktService.getMaintenanceMessage(),
|
||||
};
|
||||
|
||||
return (
|
||||
<TraktContext.Provider value={traktIntegration}>
|
||||
<TraktContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</TraktContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -550,7 +550,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (__DEV__) logger.log('Fetching movie details from TMDB for:', tmdbId);
|
||||
const movieDetails = await tmdbService.getMovieDetails(
|
||||
tmdbId,
|
||||
settings.useTmdbLocalizedMetadata ? `${settings.tmdbLanguagePreference || 'en'}-US` : 'en-US'
|
||||
settings.useTmdbLocalizedMetadata ? (settings.tmdbLanguagePreference || 'en') : 'en'
|
||||
);
|
||||
if (movieDetails) {
|
||||
const imdbId = movieDetails.imdb_id || movieDetails.external_ids?.imdb_id;
|
||||
|
|
@ -634,7 +634,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
try {
|
||||
const showDetails = await tmdbService.getTVShowDetails(
|
||||
parseInt(tmdbId),
|
||||
settings.useTmdbLocalizedMetadata ? `${settings.tmdbLanguagePreference || 'en'}-US` : 'en-US'
|
||||
settings.useTmdbLocalizedMetadata ? (settings.tmdbLanguagePreference || 'en') : 'en'
|
||||
);
|
||||
if (showDetails) {
|
||||
// OPTIMIZATION: Fetch external IDs, credits, and logo in parallel
|
||||
|
|
@ -824,9 +824,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Store addon logo before TMDB enrichment overwrites it
|
||||
const addonLogo = (finalMetadata as any).logo;
|
||||
|
||||
// If localization is enabled, merge TMDB localized text (name/overview) before first render
|
||||
// If localization is enabled AND title/description enrichment is enabled, merge TMDB localized text (name/overview) before first render
|
||||
try {
|
||||
if (settings.enrichMetadataWithTMDB && settings.useTmdbLocalizedMetadata) {
|
||||
if (settings.enrichMetadataWithTMDB && settings.useTmdbLocalizedMetadata && settings.tmdbEnrichTitleDescription) {
|
||||
const tmdbSvc = TMDBService.getInstance();
|
||||
let finalTmdbId: number | null = tmdbId;
|
||||
if (!finalTmdbId) {
|
||||
|
|
@ -857,8 +857,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
finalMetadata = {
|
||||
...finalMetadata,
|
||||
name: finalMetadata.name || localized.title,
|
||||
description: finalMetadata.description || localized.overview,
|
||||
name: localized.title || finalMetadata.name,
|
||||
description: localized.overview || finalMetadata.description,
|
||||
movieDetails: movieDetailsObj,
|
||||
...(productionInfo.length > 0 && { networks: productionInfo }),
|
||||
};
|
||||
|
|
@ -894,8 +894,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
finalMetadata = {
|
||||
...finalMetadata,
|
||||
name: finalMetadata.name || localized.name,
|
||||
description: finalMetadata.description || localized.overview,
|
||||
name: localized.name || finalMetadata.name,
|
||||
description: localized.overview || finalMetadata.description,
|
||||
tvDetails,
|
||||
...(productionInfo.length > 0 && { networks: productionInfo }),
|
||||
};
|
||||
|
|
@ -909,14 +909,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
// Centralized logo fetching logic
|
||||
try {
|
||||
if (addonLogo) {
|
||||
finalMetadata.logo = addonLogo;
|
||||
if (__DEV__) {
|
||||
console.log('[useMetadata] Using addon-provided logo:', { hasLogo: true });
|
||||
}
|
||||
// Check both master switch AND granular logos setting
|
||||
} else if (settings.enrichMetadataWithTMDB && settings.tmdbEnrichLogos) {
|
||||
// Only use TMDB logos when both enrichment AND logos option are ON
|
||||
// When TMDB enrichment AND logos are enabled, prioritize TMDB logo over addon logo
|
||||
if (settings.enrichMetadataWithTMDB && settings.tmdbEnrichLogos) {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
const contentType = type === 'series' ? 'tv' : 'movie';
|
||||
|
|
@ -932,23 +926,26 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
if (tmdbIdForLogo) {
|
||||
const logoUrl = await tmdbService.getContentLogo(contentType, tmdbIdForLogo, preferredLanguage);
|
||||
finalMetadata.logo = logoUrl || undefined; // TMDB logo or undefined (no addon fallback)
|
||||
// Use TMDB logo if found, otherwise fall back to addon logo
|
||||
finalMetadata.logo = logoUrl || addonLogo || undefined;
|
||||
if (__DEV__) {
|
||||
console.log('[useMetadata] Logo fetch result:', {
|
||||
contentType,
|
||||
tmdbIdForLogo,
|
||||
preferredLanguage,
|
||||
logoUrl: !!logoUrl,
|
||||
tmdbLogoFound: !!logoUrl,
|
||||
usingAddonFallback: !logoUrl && !!addonLogo,
|
||||
enrichmentEnabled: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
finalMetadata.logo = undefined; // No TMDB ID means no logo
|
||||
if (__DEV__) console.log('[useMetadata] No TMDB ID found for logo, will show text title');
|
||||
// No TMDB ID, fall back to addon logo
|
||||
finalMetadata.logo = addonLogo || undefined;
|
||||
if (__DEV__) console.log('[useMetadata] No TMDB ID found for logo, using addon logo');
|
||||
}
|
||||
} else {
|
||||
// When enrichment or logos is OFF, keep addon logo or undefined
|
||||
finalMetadata.logo = finalMetadata.logo || undefined;
|
||||
// When enrichment or logos is OFF, use addon logo
|
||||
finalMetadata.logo = addonLogo || finalMetadata.logo || undefined;
|
||||
if (__DEV__) {
|
||||
console.log('[useMetadata] TMDB logo enrichment disabled, using addon logo:', {
|
||||
hasAddonLogo: !!finalMetadata.logo,
|
||||
|
|
@ -1125,10 +1122,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Fetch season posters from TMDB only if enrichment AND season posters are enabled
|
||||
if (settings.enrichMetadataWithTMDB && settings.tmdbEnrichSeasonPosters) {
|
||||
try {
|
||||
const lang = settings.useTmdbLocalizedMetadata ? `${settings.tmdbLanguagePreference || 'en'}` : 'en';
|
||||
const tmdbIdToUse = tmdbId || (id.startsWith('tt') ? await tmdbService.findTMDBIdByIMDB(id) : null);
|
||||
if (tmdbIdToUse) {
|
||||
if (!tmdbId) setTmdbId(tmdbIdToUse);
|
||||
const showDetails = await tmdbService.getTVShowDetails(tmdbIdToUse);
|
||||
const showDetails = await tmdbService.getTVShowDetails(tmdbIdToUse, lang);
|
||||
if (showDetails?.seasons) {
|
||||
Object.keys(groupedAddonEpisodes).forEach(seasonStr => {
|
||||
const seasonNum = parseInt(seasonStr, 10);
|
||||
|
|
@ -1156,7 +1154,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
try {
|
||||
const tmdbIdToUse = tmdbId || (id.startsWith('tt') ? await tmdbService.findTMDBIdByIMDB(id) : null);
|
||||
if (tmdbIdToUse) {
|
||||
const lang = `${settings.tmdbLanguagePreference || 'en'}-US`;
|
||||
// Use just the language code (e.g., 'ar', not 'ar-US') for TMDB API
|
||||
const lang = settings.tmdbLanguagePreference || 'en';
|
||||
const seasons = Object.keys(groupedAddonEpisodes).map(Number);
|
||||
for (const seasonNum of seasons) {
|
||||
const seasonEps = groupedAddonEpisodes[seasonNum];
|
||||
|
|
@ -1264,13 +1263,14 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
// Fallback to TMDB if no addon episodes
|
||||
logger.log('📺 No addon episodes found, falling back to TMDB');
|
||||
const lang = settings.useTmdbLocalizedMetadata ? `${settings.tmdbLanguagePreference || 'en'}` : 'en';
|
||||
const tmdbIdResult = await tmdbService.findTMDBIdByIMDB(id);
|
||||
if (tmdbIdResult) {
|
||||
setTmdbId(tmdbIdResult);
|
||||
|
||||
const [allEpisodes, showDetails] = await Promise.all([
|
||||
tmdbService.getAllEpisodes(tmdbIdResult),
|
||||
tmdbService.getTVShowDetails(tmdbIdResult)
|
||||
tmdbService.getAllEpisodes(tmdbIdResult, lang),
|
||||
tmdbService.getTVShowDetails(tmdbIdResult, lang)
|
||||
]);
|
||||
|
||||
const transformedEpisodes: GroupedEpisodes = {};
|
||||
|
|
@ -2038,7 +2038,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
setLoadingRecommendations(true);
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const results = await tmdbService.getRecommendations(type === 'movie' ? 'movie' : 'tv', String(tmdbId));
|
||||
const lang = settings.useTmdbLocalizedMetadata ? (settings.tmdbLanguagePreference || 'en') : 'en';
|
||||
const results = await tmdbService.getRecommendations(type === 'movie' ? 'movie' : 'tv', String(tmdbId), lang);
|
||||
|
||||
// Convert TMDB results to StreamingContent format (simplified)
|
||||
const formattedRecommendations: StreamingContent[] = results.map((item: any) => ({
|
||||
|
|
@ -2056,7 +2057,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
} finally {
|
||||
setLoadingRecommendations(false);
|
||||
}
|
||||
}, [tmdbId, type]);
|
||||
}, [tmdbId, type, settings.useTmdbLocalizedMetadata, settings.tmdbLanguagePreference]);
|
||||
|
||||
// Fetch TMDB ID if needed and then recommendations
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ export interface AppSettings {
|
|||
tmdbEnrichMovieDetails: boolean; // Show movie details (budget, revenue, tagline, etc.)
|
||||
tmdbEnrichTvDetails: boolean; // Show TV details (status, seasons count, networks, etc.)
|
||||
tmdbEnrichCollections: boolean; // Show movie collections/franchises
|
||||
tmdbEnrichTitleDescription: boolean; // Use TMDB title/description (overrides addon when localization enabled)
|
||||
// Trakt integration
|
||||
showTraktComments: boolean; // Show Trakt comments in metadata screens
|
||||
// Continue Watching behavior
|
||||
|
|
@ -176,6 +177,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
|||
tmdbEnrichMovieDetails: true,
|
||||
tmdbEnrichTvDetails: true,
|
||||
tmdbEnrichCollections: true,
|
||||
tmdbEnrichTitleDescription: true, // Enabled by default for backward compatibility
|
||||
// Trakt integration
|
||||
showTraktComments: true, // Show Trakt comments by default when authenticated
|
||||
// Continue Watching behavior
|
||||
|
|
|
|||
|
|
@ -677,6 +677,23 @@ const TMDBSettingsScreen = () => {
|
|||
/>
|
||||
</View>
|
||||
|
||||
{/* Title & Description */}
|
||||
<View style={styles.settingRow}>
|
||||
<View style={styles.settingTextContainer}>
|
||||
<Text style={[styles.settingTitle, { color: currentTheme.colors.text }]}>Title & Description</Text>
|
||||
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
Use TMDb localized title and overview text
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={settings.tmdbEnrichTitleDescription}
|
||||
onValueChange={(v) => updateSetting('tmdbEnrichTitleDescription', v)}
|
||||
trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }}
|
||||
thumbColor={Platform.OS === 'android' ? currentTheme.colors.white : ''}
|
||||
ios_backgroundColor={'rgba(255,255,255,0.1)'}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Title Logos */}
|
||||
<View style={styles.settingRow}>
|
||||
<View style={styles.settingTextContainer}>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [userProfile, setUserProfile] = useState<TraktUser | null>(null);
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
|
||||
const {
|
||||
settings: autosyncSettings,
|
||||
isSyncing,
|
||||
|
|
@ -101,7 +101,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
try {
|
||||
const authenticated = await traktService.isAuthenticated();
|
||||
setIsAuthenticated(authenticated);
|
||||
|
||||
|
||||
if (authenticated) {
|
||||
const profile = await traktService.getUserProfile();
|
||||
setUserProfile(profile);
|
||||
|
|
@ -151,8 +151,8 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
'Successfully Connected',
|
||||
'Your Trakt account has been connected successfully.',
|
||||
[
|
||||
{
|
||||
label: 'OK',
|
||||
{
|
||||
label: 'OK',
|
||||
onPress: () => navigation.goBack(),
|
||||
}
|
||||
]
|
||||
|
|
@ -190,9 +190,9 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
'Sign Out',
|
||||
'Are you sure you want to sign out of your Trakt account?',
|
||||
[
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
label: 'Sign Out',
|
||||
{ label: 'Cancel', onPress: () => { } },
|
||||
{
|
||||
label: 'Sign Out',
|
||||
onPress: async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
|
|
@ -224,26 +224,39 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
onPress={() => navigation.goBack()}
|
||||
style={styles.backButton}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="arrow-back"
|
||||
size={24}
|
||||
color={isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark}
|
||||
<MaterialIcons
|
||||
name="arrow-back"
|
||||
size={24}
|
||||
color={isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark}
|
||||
/>
|
||||
<Text style={[styles.backText, { color: isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark }]}>
|
||||
Settings
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
<View style={styles.headerActions}>
|
||||
{/* Empty for now, but ready for future actions */}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
<Text style={[styles.headerTitle, { color: isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark }]}>
|
||||
Trakt Settings
|
||||
</Text>
|
||||
|
||||
<ScrollView
|
||||
{/* Maintenance Mode Banner */}
|
||||
{traktService.isMaintenanceMode() && (
|
||||
<View style={styles.maintenanceBanner}>
|
||||
<MaterialIcons name="engineering" size={24} color="#FFF" />
|
||||
<View style={styles.maintenanceBannerTextContainer}>
|
||||
<Text style={styles.maintenanceBannerTitle}>Under Maintenance</Text>
|
||||
<Text style={styles.maintenanceBannerMessage}>
|
||||
{traktService.getMaintenanceMessage()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
>
|
||||
|
|
@ -255,12 +268,44 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={currentTheme.colors.primary} />
|
||||
</View>
|
||||
) : traktService.isMaintenanceMode() ? (
|
||||
<View style={styles.signInContainer}>
|
||||
<TraktIcon
|
||||
width={120}
|
||||
height={120}
|
||||
style={[styles.traktLogo, { opacity: 0.5 }]}
|
||||
/>
|
||||
<Text style={[
|
||||
styles.signInTitle,
|
||||
{ color: isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark }
|
||||
]}>
|
||||
Trakt Unavailable
|
||||
</Text>
|
||||
<Text style={[
|
||||
styles.signInDescription,
|
||||
{ color: isDarkMode ? currentTheme.colors.mediumEmphasis : currentTheme.colors.textMutedDark }
|
||||
]}>
|
||||
The Trakt integration is temporarily paused for maintenance. All syncing and authentication is disabled until maintenance is complete.
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.button,
|
||||
{ backgroundColor: currentTheme.colors.border, opacity: 0.6 }
|
||||
]}
|
||||
disabled={true}
|
||||
>
|
||||
<MaterialIcons name="engineering" size={20} color={currentTheme.colors.mediumEmphasis} style={{ marginRight: 8 }} />
|
||||
<Text style={[styles.buttonText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
Service Under Maintenance
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : isAuthenticated && userProfile ? (
|
||||
<View style={styles.profileContainer}>
|
||||
<View style={styles.profileHeader}>
|
||||
{userProfile.avatar ? (
|
||||
<FastImage
|
||||
source={{ uri: userProfile.avatar }}
|
||||
<FastImage
|
||||
source={{ uri: userProfile.avatar }}
|
||||
style={styles.avatar}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
|
|
@ -315,7 +360,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
</View>
|
||||
) : (
|
||||
<View style={styles.signInContainer}>
|
||||
<TraktIcon
|
||||
<TraktIcon
|
||||
width={120}
|
||||
height={120}
|
||||
style={styles.traktLogo}
|
||||
|
|
@ -497,7 +542,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
|
|
@ -704,6 +749,31 @@ const styles = StyleSheet.create({
|
|||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
},
|
||||
// Maintenance mode styles
|
||||
maintenanceBanner: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#E67E22',
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
},
|
||||
maintenanceBannerTextContainer: {
|
||||
marginLeft: 12,
|
||||
flex: 1,
|
||||
},
|
||||
maintenanceBannerTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#FFF',
|
||||
marginBottom: 4,
|
||||
},
|
||||
maintenanceBannerMessage: {
|
||||
fontSize: 13,
|
||||
color: '#FFF',
|
||||
opacity: 0.9,
|
||||
},
|
||||
});
|
||||
|
||||
export default TraktSettingsScreen;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -577,6 +577,10 @@ export type TraktContentCommentLegacy =
|
|||
| TraktEpisodeComment
|
||||
| TraktListComment;
|
||||
|
||||
|
||||
const TRAKT_MAINTENANCE_MODE = true;
|
||||
const TRAKT_MAINTENANCE_MESSAGE = 'Trakt integration is temporarily unavailable for maintenance. Please try again later.';
|
||||
|
||||
export class TraktService {
|
||||
private static instance: TraktService;
|
||||
private accessToken: string | null = null;
|
||||
|
|
@ -584,6 +588,16 @@ export class TraktService {
|
|||
private tokenExpiry: number = 0;
|
||||
private isInitialized: boolean = false;
|
||||
|
||||
|
||||
public isMaintenanceMode(): boolean {
|
||||
return TRAKT_MAINTENANCE_MODE;
|
||||
}
|
||||
|
||||
|
||||
public getMaintenanceMessage(): string {
|
||||
return TRAKT_MAINTENANCE_MESSAGE;
|
||||
}
|
||||
|
||||
// Rate limiting - Optimized for real-time scrobbling
|
||||
private lastApiCall: number = 0;
|
||||
private readonly MIN_API_INTERVAL = 500; // Reduced to 500ms for faster updates
|
||||
|
|
@ -726,6 +740,12 @@ export class TraktService {
|
|||
* Check if the user is authenticated with Trakt
|
||||
*/
|
||||
public async isAuthenticated(): Promise<boolean> {
|
||||
// During maintenance, report as not authenticated to disable all syncing
|
||||
if (this.isMaintenanceMode()) {
|
||||
logger.log('[TraktService] Maintenance mode: reporting as not authenticated');
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.ensureInitialized();
|
||||
|
||||
if (!this.accessToken) {
|
||||
|
|
@ -756,6 +776,12 @@ export class TraktService {
|
|||
* Exchange the authorization code for an access token
|
||||
*/
|
||||
public async exchangeCodeForToken(code: string, codeVerifier: string): Promise<boolean> {
|
||||
// Block authentication during maintenance
|
||||
if (this.isMaintenanceMode()) {
|
||||
logger.warn('[TraktService] Maintenance mode: blocking new authentication');
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.ensureInitialized();
|
||||
|
||||
try {
|
||||
|
|
@ -887,6 +913,12 @@ export class TraktService {
|
|||
body?: any,
|
||||
retryCount: number = 0
|
||||
): Promise<T> {
|
||||
// Block all API requests during maintenance
|
||||
if (this.isMaintenanceMode()) {
|
||||
logger.warn('[TraktService] Maintenance mode: blocking API request to', endpoint);
|
||||
throw new Error(TRAKT_MAINTENANCE_MESSAGE);
|
||||
}
|
||||
|
||||
await this.ensureInitialized();
|
||||
|
||||
// Rate limiting: ensure minimum interval between API calls
|
||||
|
|
@ -1106,10 +1138,10 @@ export class TraktService {
|
|||
? imdbId
|
||||
: `tt${imdbId}`;
|
||||
|
||||
const response = await this.client.get('/sync/watched/movies');
|
||||
const movies = Array.isArray(response.data) ? response.data : [];
|
||||
const movies = await this.apiRequest<any[]>('/sync/watched/movies');
|
||||
const moviesArray = Array.isArray(movies) ? movies : [];
|
||||
|
||||
return movies.some(
|
||||
return moviesArray.some(
|
||||
(m: any) => m.movie?.ids?.imdb === imdb
|
||||
);
|
||||
} catch (err) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue