From ebcbced14257ef488c10510930b04b76f1cf1db9 Mon Sep 17 00:00:00 2001 From: tapframe Date: Tue, 29 Jul 2025 01:13:34 +0530 Subject: [PATCH] fixes --- src/hooks/useSettings.ts | 2 + src/screens/PluginsScreen.tsx | 16 ++++ src/screens/StreamsScreen.tsx | 159 ++++++++++++++++++++++++++-------- 3 files changed, 142 insertions(+), 35 deletions(-) diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 5a4738c66..c3d3b0056 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -41,6 +41,7 @@ export interface AppSettings { enableLocalScrapers: boolean; // Enable/disable local scraper functionality scraperTimeout: number; // Timeout for scraper execution in seconds enableScraperUrlValidation: boolean; // Enable/disable URL validation for scrapers + streamDisplayMode: 'separate' | 'grouped'; // How to display streaming links - separately by provider or grouped under one name } export const DEFAULT_SETTINGS: AppSettings = { @@ -64,6 +65,7 @@ export const DEFAULT_SETTINGS: AppSettings = { enableLocalScrapers: true, scraperTimeout: 60, // 60 seconds timeout enableScraperUrlValidation: true, // Enable URL validation by default + streamDisplayMode: 'separate', // Default to separate display by provider }; const SETTINGS_STORAGE_KEY = 'app_settings'; diff --git a/src/screens/PluginsScreen.tsx b/src/screens/PluginsScreen.tsx index 9c3ef0d58..53dca9028 100644 --- a/src/screens/PluginsScreen.tsx +++ b/src/screens/PluginsScreen.tsx @@ -739,6 +739,22 @@ const PluginsScreen: React.FC = () => { disabled={!settings.enableLocalScrapers} /> + + + + Stream Display Mode + + {settings.streamDisplayMode === 'separate' ? 'Show each provider separately' : 'Group all providers under one name'} + + + updateSetting('streamDisplayMode', value ? 'grouped' : 'separate')} + trackColor={{ false: colors.elevation3, true: colors.primary }} + thumbColor={settings.streamDisplayMode === 'grouped' ? colors.white : '#f4f3f4'} + disabled={!settings.enableLocalScrapers} + /> + diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index 393992569..e412a2137 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -101,15 +101,8 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the }; }, [stream.name, stream.title, stream.behaviorHints, stream.size]); - // Animation delay based on index - stagger effect - const enterDelay = 100 + (index * 30); - return ( - - - ); }); @@ -232,11 +224,7 @@ const ProviderFilter = memo(({ const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]); const renderItem = useCallback(({ item, index }: { item: { id: string; name: string }; index: number }) => ( - - - ), [selectedProvider, onSelect, styles]); return ( - + - + ); }); @@ -385,7 +370,12 @@ export const StreamsScreen = () => { if (providersWithStreams.length > 0) { logger.log(`📊 Providers with streams: ${providersWithStreams.join(', ')}`); const providersWithStreamsSet = new Set(providersWithStreams); - setAvailableProviders(providersWithStreamsSet); + + // Only update if we have new providers, don't remove existing ones during loading + setAvailableProviders(prevProviders => { + const newProviders = new Set([...prevProviders, ...providersWithStreamsSet]); + return newProviders; + }); } // Update loading states for individual providers @@ -407,11 +397,28 @@ export const StreamsScreen = () => { }, [loadingStreams, loadingEpisodeStreams, groupedStreams, episodeStreams, type]); // Reset the selected provider to 'all' if the current selection is no longer available + // But preserve special filter values like 'grouped-plugins' and 'all' useEffect(() => { - if (selectedProvider !== 'all' && !availableProviders.has(selectedProvider)) { + // Don't reset if it's a special filter value + const isSpecialFilter = selectedProvider === 'all' || selectedProvider === 'grouped-plugins'; + + if (isSpecialFilter) { + return; // Always preserve special filters + } + + // Check if provider exists in current streams data + const currentStreamsData = type === 'series' ? episodeStreams : groupedStreams; + const hasStreamsForProvider = currentStreamsData[selectedProvider] && + currentStreamsData[selectedProvider].streams && + currentStreamsData[selectedProvider].streams.length > 0; + + // Only reset if the provider doesn't exist in available providers AND doesn't have streams + const isAvailableProvider = availableProviders.has(selectedProvider); + + if (!isAvailableProvider && !hasStreamsForProvider) { setSelectedProvider('all'); } - }, [selectedProvider, availableProviders]); + }, [selectedProvider, availableProviders, episodeStreams, groupedStreams, type]); // Update useEffect to check for sources useEffect(() => { @@ -863,6 +870,43 @@ export const StreamsScreen = () => { ) ]); + // In grouped mode, separate addons and plugins + if (settings.streamDisplayMode === 'grouped') { + const addonProviders: string[] = []; + const pluginProviders: string[] = []; + + Array.from(allProviders).forEach(provider => { + const isInstalledAddon = installedAddons.some(addon => addon.id === provider); + if (isInstalledAddon) { + addonProviders.push(provider); + } else { + pluginProviders.push(provider); + } + }); + + const filterChips = [{ id: 'all', name: 'All Providers' }]; + + // Add individual addon chips + addonProviders + .sort((a, b) => { + const indexA = installedAddons.findIndex(addon => addon.id === a); + const indexB = installedAddons.findIndex(addon => addon.id === b); + return indexA - indexB; + }) + .forEach(provider => { + const installedAddon = installedAddons.find(addon => addon.id === provider); + filterChips.push({ id: provider, name: installedAddon?.name || provider }); + }); + + // Add single grouped plugins chip if there are any plugins + if (pluginProviders.length > 0) { + filterChips.push({ id: 'grouped-plugins', name: 'Plugins' }); + } + + return filterChips; + } + + // Normal mode - individual chips for all providers return [ { id: 'all', name: 'All Providers' }, ...Array.from(allProviders) @@ -889,19 +933,26 @@ export const StreamsScreen = () => { return { id: provider, name: displayName }; }) ]; - }, [availableProviders, type, episodeStreams, groupedStreams]); + }, [availableProviders, type, episodeStreams, groupedStreams, settings.streamDisplayMode]); const sections = useMemo(() => { const streams = type === 'series' ? episodeStreams : groupedStreams; const installedAddons = stremioService.getInstalledAddons(); - // Filter streams by selected provider - only if not "all" + // Filter streams by selected provider const filteredEntries = Object.entries(streams) .filter(([addonId]) => { // If "all" is selected, show all providers if (selectedProvider === 'all') { return true; } + + // In grouped mode, handle special 'grouped-plugins' filter + if (settings.streamDisplayMode === 'grouped' && selectedProvider === 'grouped-plugins') { + const isInstalledAddon = installedAddons.some(addon => addon.id === addonId); + return !isInstalledAddon; // Show only plugins (non-installed addons) + } + // Otherwise only show the selected provider return addonId === selectedProvider; }) @@ -914,17 +965,59 @@ export const StreamsScreen = () => { if (indexA !== -1) return -1; if (indexB !== -1) return 1; return 0; - }) - .map(([addonId, { addonName, streams: providerStreams }]) => { + }); + + // Check if we should group all streams under one section + if (settings.streamDisplayMode === 'grouped') { + // Separate streams by type: installed addons vs plugins + const addonStreams: Stream[] = []; + const pluginStreams: Stream[] = []; + const addonNames: string[] = []; + const pluginNames: string[] = []; + + filteredEntries.forEach(([addonId, { addonName, streams: providerStreams }]) => { + const isInstalledAddon = installedAddons.some(addon => addon.id === addonId); + if (isInstalledAddon) { + addonStreams.push(...providerStreams); + if (!addonNames.includes(addonName)) { + addonNames.push(addonName); + } + } else { + pluginStreams.push(...providerStreams); + if (!pluginNames.includes(addonName)) { + pluginNames.push(addonName); + } + } + }); + + const sections = []; + if (addonStreams.length > 0) { + sections.push({ + title: addonNames.join(', '), + addonId: 'grouped-addons', + data: addonStreams + }); + } + if (pluginStreams.length > 0) { + sections.push({ + title: pluginNames.join(', '), + addonId: 'grouped-plugins', + data: pluginStreams + }); + } + + return sections; + } else { + // Use separate sections for each provider (current behavior) + return filteredEntries.map(([addonId, { addonName, streams: providerStreams }]) => { return { title: addonName, addonId, data: providerStreams }; }); - - return filteredEntries; - }, [selectedProvider, type, episodeStreams, groupedStreams]); + } + }, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode]); const episodeImage = useMemo(() => { if (episodeThumbnail) { @@ -993,11 +1086,7 @@ export const StreamsScreen = () => { const isProviderLoading = loadingProviders[section.addonId]; return ( - + {section.title} {isProviderLoading && ( @@ -1009,7 +1098,7 @@ export const StreamsScreen = () => { )} - + ); }, [styles.streamGroupTitle, styles.sectionHeaderContainer, styles.sectionHeaderContent, styles.sectionLoadingIndicator, styles.sectionLoadingText, loadingProviders, colors.primary]);