some UI fix

This commit is contained in:
tapframe 2025-10-11 00:16:07 +05:30
parent 7dceb23e3d
commit 0c14d8641d
4 changed files with 211 additions and 24 deletions

View file

@ -97,7 +97,8 @@ const ActionButtons = memo(({
watchProgress,
groupedEpisodes,
metadata,
aiChatEnabled
aiChatEnabled,
settings
}: {
handleShowStreams: () => void;
toggleLibrary: () => void;
@ -112,6 +113,7 @@ const ActionButtons = memo(({
groupedEpisodes?: { [seasonNumber: number]: any[] };
metadata: any;
aiChatEnabled?: boolean;
settings: any;
}) => {
const { currentTheme } = useTheme();
@ -135,7 +137,7 @@ const ActionButtons = memo(({
if (!isNaN(parsedId)) {
finalTmdbId = parsedId;
}
} else if (id.startsWith('tt')) {
} else if (id.startsWith('tt') && settings.enrichMetadataWithTMDB) {
try {
const tmdbService = TMDBService.getInstance();
const convertedId = await tmdbService.findTMDBIdByIMDB(id);
@ -158,7 +160,7 @@ const ActionButtons = memo(({
navigation.navigate('ShowRatings', { showId: finalTmdbId });
});
}
}, [id, navigation]);
}, [id, navigation, settings.enrichMetadataWithTMDB]);
// Optimized play button style calculation
const playButtonStyle = useMemo(() => {
@ -1538,6 +1540,7 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
groupedEpisodes={groupedEpisodes}
metadata={metadata}
aiChatEnabled={settings?.aiChatEnabled}
settings={settings}
/>
</View>
</LinearGradient>

View file

@ -232,6 +232,28 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
(streams, addonId, addonName, error) => {
const processTime = Date.now() - sourceStartTime;
console.log('🔍 [processStremioSource] Callback received:', {
addonId,
addonName,
streamCount: streams?.length || 0,
error: error?.message || null,
processTime
});
// ALWAYS remove from active fetching list when callback is received
// This ensures that even failed scrapers are removed from the "Fetching from:" chip
if (addonName) {
setActiveFetchingScrapers(prev => {
const updated = prev.filter(name => name !== addonName);
console.log('🔍 [processStremioSource] Removing from activeFetchingScrapers:', {
addonName,
before: prev,
after: updated
});
return updated;
});
}
// Update scraper status when we get a callback
if (addonId && addonName) {
setScraperStatuses(prevStatuses => {
@ -254,9 +276,6 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
return [...prevStatuses, newStatus];
}
});
// Remove from active fetching list
setActiveFetchingScrapers(prev => prev.filter(name => name !== addonName));
}
if (error) {
@ -314,7 +333,53 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
} catch (error) {
// Catch errors from the initial call to getStreams (e.g., initialization errors)
logger.error(`❌ [${logPrefix}:${sourceName}] Initial call failed:`, error);
// Maybe update state to show a general Stremio error?
// Remove all addons and scrapers from active fetching since the entire request failed
setActiveFetchingScrapers(prev => {
// Get both Stremio addon names and local scraper names
const stremioAddons = stremioService.getInstalledAddons();
const stremioNames = stremioAddons.map(addon => addon.name);
// Get local scraper names
localScraperService.getInstalledScrapers().then(localScrapers => {
const localScraperNames = localScrapers.filter(s => s.enabled).map(s => s.name);
const allNames = [...stremioNames, ...localScraperNames];
// Remove all from active fetching
setActiveFetchingScrapers(current =>
current.filter(name => !allNames.includes(name))
);
}).catch(() => {
// If we can't get local scrapers, just remove Stremio addons
setActiveFetchingScrapers(current =>
current.filter(name => !stremioNames.includes(name))
);
});
// Immediately remove Stremio addons (local scrapers will be removed async above)
return prev.filter(name => !stremioNames.includes(name));
});
// Update scraper statuses to mark all scrapers as failed
setScraperStatuses(prevStatuses => {
const stremioAddons = stremioService.getInstalledAddons();
return prevStatuses.map(status => {
const isStremioAddon = stremioAddons.some(addon => addon.id === status.id || addon.name === status.name);
// Mark both Stremio addons and local scrapers as failed
if (isStremioAddon || !status.hasCompleted) {
return {
...status,
isLoading: false,
hasCompleted: true,
error: error instanceof Error ? error.message : 'Initial request failed',
endTime: Date.now()
};
}
return status;
});
});
}
// Note: This function completes when getStreams returns, not when all callbacks have fired.
// Loading indicators should probably be managed based on callbacks completing.
@ -358,9 +423,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}
}
// Handle IMDb IDs or convert to TMDB ID
// Handle IMDb IDs or convert to TMDB ID (only if enrichment is enabled)
let tmdbId;
if (id.startsWith('tt')) {
if (id.startsWith('tt') && settings.enrichMetadataWithTMDB) {
if (__DEV__) logger.log('[loadCast] Converting IMDb ID to TMDB ID');
tmdbId = await tmdbService.findTMDBIdByIMDB(id);
}
@ -1119,9 +1184,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
} else if (id.startsWith('tt')) {
// This is already an IMDB ID, perfect for Stremio
stremioId = id;
if (__DEV__) console.log('📝 [loadStreams] Converting IMDB ID to TMDB ID...');
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
if (__DEV__) console.log('✅ [loadStreams] Converted to TMDB ID:', tmdbId);
if (settings.enrichMetadataWithTMDB) {
if (__DEV__) console.log('📝 [loadStreams] Converting IMDB ID to TMDB ID...');
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
if (__DEV__) console.log('✅ [loadStreams] Converted to TMDB ID:', tmdbId);
} else {
if (__DEV__) console.log('📝 [loadStreams] TMDB enrichment disabled, skipping IMDB to TMDB conversion');
}
} else {
tmdbId = id;
stremioId = id;
@ -1215,6 +1284,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setScraperStatuses(initialStatuses);
setActiveFetchingScrapers(initialActiveFetching);
console.log('🔍 [loadStreams] Initialized activeFetchingScrapers:', initialActiveFetching);
} catch (error) {
if (__DEV__) console.error('Failed to initialize scraper tracking:', error);
}
@ -1243,6 +1313,14 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
clearInterval(completionInterval);
setLoadingStreams(false);
setActiveFetchingScrapers([]);
// Mark all incomplete scrapers as failed
setScraperStatuses(prevStatuses =>
prevStatuses.map(status =>
!status.hasCompleted && !status.error
? { ...status, isLoading: false, hasCompleted: true, error: 'Request timed out', endTime: Date.now() }
: status
)
);
}, 60000);
} catch (error) {
@ -1335,6 +1413,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setScraperStatuses(initialStatuses);
setActiveFetchingScrapers(initialActiveFetching);
console.log('🔍 [loadEpisodeStreams] Initialized activeFetchingScrapers:', initialActiveFetching);
} catch (error) {
if (__DEV__) console.error('Failed to initialize episode scraper tracking:', error);
}
@ -1356,7 +1435,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// episodeId format for collections: "tt7888964" (IMDb ID of individual movie)
if (episodeId.startsWith('tt')) {
// This is an IMDb ID of an individual movie in the collection
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(episodeId), API_TIMEOUT);
if (settings.enrichMetadataWithTMDB) {
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(episodeId), API_TIMEOUT);
}
stremioEpisodeId = episodeId; // Use the IMDb ID directly for Stremio addons
if (__DEV__) console.log('✅ [loadEpisodeStreams] Collection movie - using IMDb ID:', episodeId, 'TMDB ID:', tmdbId);
} else {
@ -1397,8 +1478,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}
} else if (id.startsWith('tt')) {
// This is already an IMDB ID, perfect for Stremio
if (__DEV__) console.log('📝 [loadEpisodeStreams] Converting IMDB ID to TMDB ID...');
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
if (settings.enrichMetadataWithTMDB) {
if (__DEV__) console.log('📝 [loadEpisodeStreams] Converting IMDB ID to TMDB ID...');
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
} else {
if (__DEV__) console.log('📝 [loadEpisodeStreams] TMDB enrichment disabled, skipping IMDB to TMDB conversion');
}
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted to TMDB ID:', tmdbId);
// Normalize episode id to 'tt:season:episode' format for addons that expect tt prefix
const parts = episodeId.split(':');
@ -1447,6 +1532,14 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
clearInterval(episodeCompletionInterval);
setLoadingEpisodeStreams(false);
setActiveFetchingScrapers([]);
// Mark all incomplete scrapers as failed
setScraperStatuses(prevStatuses =>
prevStatuses.map(status =>
!status.hasCompleted && !status.error
? { ...status, isLoading: false, hasCompleted: true, error: 'Request timed out', endTime: Date.now() }
: status
)
);
}, 60000);
} catch (error) {

View file

@ -150,7 +150,7 @@ export const useMetadataAssets = (
// Extract or find TMDB ID in one step
if (id.startsWith('tmdb:')) {
tmdbId = id.split(':')[1];
} else if (imdbId) {
} else if (imdbId && settings.enrichMetadataWithTMDB) {
try {
const tmdbService = TMDBService.getInstance();
const foundId = await tmdbService.findTMDBIdByIMDB(imdbId);
@ -248,7 +248,7 @@ export const useMetadataAssets = (
tmdbId = foundTmdbId;
} else if ((metadata as any).tmdbId) {
tmdbId = (metadata as any).tmdbId;
} else if (imdbId) {
} else if (imdbId && settings.enrichMetadataWithTMDB) {
try {
const tmdbService = TMDBService.getInstance();
const foundId = await tmdbService.findTMDBIdByIMDB(imdbId);

View file

@ -1427,6 +1427,18 @@ export const StreamsScreen = () => {
const sections = useMemo(() => {
const streams = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams;
const installedAddons = stremioService.getInstalledAddons();
console.log('🔍 [StreamsScreen] Sections debug:', {
streamsKeys: Object.keys(streams),
installedAddons: installedAddons.map(a => ({ id: a.id, name: a.name })),
selectedProvider,
streamDisplayMode: settings.streamDisplayMode,
streamsData: Object.entries(streams).map(([key, data]) => ({
provider: key,
addonName: data.addonName,
streamCount: data.streams?.length || 0
}))
});
// Filter streams by selected provider
const filteredEntries = Object.entries(streams)
@ -1444,8 +1456,18 @@ export const StreamsScreen = () => {
// Otherwise only show the selected provider
return addonId === selectedProvider;
})
.sort(([addonIdA], [addonIdB]) => {
});
console.log('🔍 [StreamsScreen] Filtered entries:', {
filteredCount: filteredEntries.length,
filteredEntries: filteredEntries.map(([addonId, data]) => ({
addonId,
addonName: data.addonName,
streamCount: data.streams?.length || 0
}))
});
const sortedEntries = filteredEntries.sort(([addonIdA], [addonIdB]) => {
// Sort by response order (actual order addons responded)
const indexA = addonResponseOrder.indexOf(addonIdA);
const indexB = addonResponseOrder.indexOf(addonIdB);
@ -1470,7 +1492,7 @@ export const StreamsScreen = () => {
const pluginStreams: Stream[] = [];
let totalOriginalCount = 0;
filteredEntries.forEach(([addonId, { addonName, streams: providerStreams }]) => {
sortedEntries.forEach(([addonId, { addonName, streams: providerStreams }]) => {
const isInstalledAddon = installedAddons.some(addon => addon.id === addonId);
// Count original streams before filtering
@ -1572,15 +1594,25 @@ export const StreamsScreen = () => {
combinedStreams.push(...pluginStreams);
}
return [{
const result = [{
title: 'Available Streams',
addonId: 'grouped-all',
data: combinedStreams,
isEmptyDueToQualityFilter: false
}];
console.log('🔍 [StreamsScreen] Grouped mode result:', {
resultCount: result.length,
combinedStreamsCount: combinedStreams.length,
addonStreamsCount: addonStreams.length,
pluginStreamsCount: pluginStreams.length,
totalOriginalCount
});
return result;
} else {
// Use separate sections for each provider (current behavior)
return filteredEntries.map(([addonId, { addonName, streams: providerStreams }]) => {
return sortedEntries.map(([addonId, { addonName, streams: providerStreams }]) => {
const isInstalledAddon = installedAddons.some(addon => addon.id === addonId);
// Count original streams before filtering
@ -1591,8 +1623,25 @@ export const StreamsScreen = () => {
// Only apply quality filtering to plugins, NOT addons
if (!isInstalledAddon) {
console.log('🔍 [StreamsScreen] Applying quality filter to plugin:', {
addonId,
addonName,
originalCount,
excludedQualities: settings.excludedQualities
});
filteredStreams = filterStreamsByQuality(providerStreams);
isEmptyDueToQualityFilter = originalCount > 0 && filteredStreams.length === 0;
console.log('🔍 [StreamsScreen] Quality filter result:', {
addonId,
filteredCount: filteredStreams.length,
isEmptyDueToQualityFilter
});
} else {
console.log('🔍 [StreamsScreen] Skipping quality filter for addon:', {
addonId,
addonName,
originalCount
});
}
if (isEmptyDueToQualityFilter) {
@ -1661,15 +1710,38 @@ export const StreamsScreen = () => {
});
}
return {
const result = {
title: addonName,
addonId,
data: processedStreams,
isEmptyDueToQualityFilter: false
};
console.log('🔍 [StreamsScreen] Individual mode result:', {
addonId,
addonName,
processedStreamsCount: processedStreams.length,
originalCount,
isInstalledAddon
});
return result;
});
}
}, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality, addonResponseOrder, settings.streamSortMode]);
}, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality, addonResponseOrder, settings.streamSortMode, selectedEpisode, metadata]);
// Debug log for sections result
React.useEffect(() => {
console.log('🔍 [StreamsScreen] Final sections:', {
sectionsCount: sections.length,
sections: sections.map(s => ({
title: s.title,
addonId: s.addonId,
dataCount: s.data?.length || 0,
isEmptyDueToQualityFilter: s.isEmptyDueToQualityFilter
}))
});
}, [sections]);
const episodeImage = useMemo(() => {
if (episodeThumbnail) {
@ -1726,6 +1798,25 @@ export const StreamsScreen = () => {
const showInitialLoading = streamsEmpty && (streamsLoadStart === null || loadElapsed < 10000);
const showStillFetching = streamsEmpty && loadElapsed >= 10000;
// Debug logging for stream availability
React.useEffect(() => {
console.log('🔍 [StreamsScreen] Streams debug:', {
streamsEmpty,
streamsKeys: Object.keys(streams),
streamsData: Object.entries(streams).map(([key, data]) => ({
provider: key,
addonName: data.addonName,
streamCount: data.streams?.length || 0,
streams: data.streams?.slice(0, 3).map(s => ({ name: s.name, title: s.title })) || []
})),
isLoading,
loadingStreams,
loadingEpisodeStreams,
selectedEpisode,
type
});
}, [streams, streamsEmpty, isLoading, loadingStreams, loadingEpisodeStreams, selectedEpisode, type]);
const renderSectionHeader = useCallback(({ section }: { section: { title: string; addonId: string; isEmptyDueToQualityFilter?: boolean } }) => {