mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
major ui changes
This commit is contained in:
parent
9c12f9fc08
commit
494b35b1c0
5 changed files with 211 additions and 66 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 63d560d55f1a84a16318525ad4eb1db5162e059c
|
||||
Subproject commit 22ed3a1c96ed2a8adf5bf9f277acd9d8c53c069c
|
||||
|
|
@ -39,7 +39,7 @@ import LogoSourceSettings from '../screens/LogoSourceSettings';
|
|||
import ThemeScreen from '../screens/ThemeScreen';
|
||||
import ProfilesScreen from '../screens/ProfilesScreen';
|
||||
import OnboardingScreen from '../screens/OnboardingScreen';
|
||||
import ScraperSettingsScreen from '../screens/ScraperSettingsScreen';
|
||||
import PluginsScreen from '../screens/PluginsScreen';
|
||||
|
||||
// Stack navigator types
|
||||
export type RootStackParamList = {
|
||||
|
|
@ -1028,7 +1028,7 @@ const AppNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStack
|
|||
/>
|
||||
<Stack.Screen
|
||||
name="ScraperSettings"
|
||||
component={ScraperSettingsScreen}
|
||||
component={PluginsScreen}
|
||||
options={{
|
||||
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
||||
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
||||
|
|
|
|||
|
|
@ -314,11 +314,23 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
opacity: 0.5,
|
||||
},
|
||||
disabledImage: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
});
|
||||
opacity: 0.3,
|
||||
},
|
||||
availableIndicator: {
|
||||
backgroundColor: colors.primary,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 4,
|
||||
marginLeft: 8,
|
||||
},
|
||||
availableIndicatorText: {
|
||||
color: colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
||||
const ScraperSettingsScreen: React.FC = () => {
|
||||
const PluginsScreen: React.FC = () => {
|
||||
const navigation = useNavigation();
|
||||
const { settings, updateSetting } = useSettings();
|
||||
const { currentTheme } = useTheme();
|
||||
|
|
@ -337,7 +349,7 @@ const ScraperSettingsScreen: React.FC = () => {
|
|||
|
||||
const loadScrapers = async () => {
|
||||
try {
|
||||
const scrapers = await localScraperService.getInstalledScrapers();
|
||||
const scrapers = await localScraperService.getAvailableScrapers();
|
||||
setInstalledScrapers(scrapers);
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to load scrapers:', error);
|
||||
|
|
@ -395,7 +407,7 @@ const ScraperSettingsScreen: React.FC = () => {
|
|||
try {
|
||||
setIsRefreshing(true);
|
||||
await localScraperService.refreshRepository();
|
||||
await loadScrapers();
|
||||
await loadScrapers(); // This will now load available scrapers from manifest
|
||||
Alert.alert('Success', 'Repository refreshed successfully');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to refresh repository:', error);
|
||||
|
|
@ -411,11 +423,25 @@ const ScraperSettingsScreen: React.FC = () => {
|
|||
|
||||
const handleToggleScraper = async (scraperId: string, enabled: boolean) => {
|
||||
try {
|
||||
if (enabled) {
|
||||
// If enabling a scraper, ensure it's installed first
|
||||
const installedScrapers = await localScraperService.getInstalledScrapers();
|
||||
const isInstalled = installedScrapers.some(scraper => scraper.id === scraperId);
|
||||
|
||||
if (!isInstalled) {
|
||||
// Need to install the scraper first
|
||||
setIsRefreshing(true);
|
||||
await localScraperService.refreshRepository();
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
await localScraperService.setScraperEnabled(scraperId, enabled);
|
||||
await loadScrapers();
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to toggle scraper:', error);
|
||||
Alert.alert('Error', 'Failed to update scraper status');
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -502,7 +528,7 @@ const ScraperSettingsScreen: React.FC = () => {
|
|||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<Text style={styles.headerTitle}>Local Scrapers</Text>
|
||||
<Text style={styles.headerTitle}>Plugins</Text>
|
||||
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
|
|
@ -606,10 +632,10 @@ const ScraperSettingsScreen: React.FC = () => {
|
|||
</View>
|
||||
</View>
|
||||
|
||||
{/* Installed Scrapers */}
|
||||
{/* Available Scrapers */}
|
||||
<View style={[styles.section, !settings.enableLocalScrapers && styles.disabledSection]}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={[styles.sectionTitle, !settings.enableLocalScrapers && styles.disabledText]}>Installed Scrapers</Text>
|
||||
<Text style={[styles.sectionTitle, !settings.enableLocalScrapers && styles.disabledText]}>Available Scrapers</Text>
|
||||
{installedScrapers.length > 0 && settings.enableLocalScrapers && (
|
||||
<TouchableOpacity
|
||||
style={styles.clearButton}
|
||||
|
|
@ -619,56 +645,78 @@ const ScraperSettingsScreen: React.FC = () => {
|
|||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<Text style={[styles.sectionDescription, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||
Scrapers available in the repository. Only enabled scrapers that are also installed will be used for streaming.
|
||||
</Text>
|
||||
|
||||
{installedScrapers.length === 0 ? (
|
||||
<View style={[styles.emptyContainer, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
||||
<Ionicons name="download-outline" size={48} color={!settings.enableLocalScrapers ? colors.elevation3 : colors.mediumGray} />
|
||||
<Text style={[styles.emptyStateTitle, !settings.enableLocalScrapers && styles.disabledText]}>No Scrapers Installed</Text>
|
||||
<Text style={[styles.emptyStateTitle, !settings.enableLocalScrapers && styles.disabledText]}>No Scrapers Available</Text>
|
||||
<Text style={[styles.emptyStateDescription, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||
Configure a repository above to install scrapers.
|
||||
Configure a repository above to view available scrapers.
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.scrapersContainer}>
|
||||
{installedScrapers.map((scraper) => (
|
||||
<View key={scraper.id} style={[styles.scraperItem, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
||||
{scraper.logo ? (
|
||||
<Image
|
||||
source={{ uri: scraper.logo }}
|
||||
style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledImage]}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<View style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledContainer]} />
|
||||
)}
|
||||
<View style={styles.scraperInfo}>
|
||||
<Text style={[styles.scraperName, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.name}</Text>
|
||||
<Text style={[styles.scraperDescription, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.description}</Text>
|
||||
<View style={styles.scraperMeta}>
|
||||
<Text style={[styles.scraperVersion, !settings.enableLocalScrapers && styles.disabledText]}>v{scraper.version}</Text>
|
||||
<Text style={[styles.scraperDot, !settings.enableLocalScrapers && styles.disabledText]}>•</Text>
|
||||
<Text style={[styles.scraperTypes, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||
{scraper.supportedTypes && Array.isArray(scraper.supportedTypes) ? scraper.supportedTypes.join(', ') : 'Unknown'}
|
||||
</Text>
|
||||
{scraper.contentLanguage && Array.isArray(scraper.contentLanguage) && scraper.contentLanguage.length > 0 && (
|
||||
<>
|
||||
<Text style={[styles.scraperDot, !settings.enableLocalScrapers && styles.disabledText]}>•</Text>
|
||||
<Text style={[styles.scraperLanguage, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||
{scraper.contentLanguage.map(lang => lang.toUpperCase()).join(', ')}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
{installedScrapers.map((scraper) => {
|
||||
// Check if scraper is actually installed (has cached code)
|
||||
const isInstalled = localScraperService.getInstalledScrapers().then(installed =>
|
||||
installed.some(s => s.id === scraper.id)
|
||||
);
|
||||
|
||||
return (
|
||||
<View key={scraper.id} style={[styles.scraperItem, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
||||
{scraper.logo ? (
|
||||
<Image
|
||||
source={{ uri: scraper.logo }}
|
||||
style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledImage]}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<View style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledContainer]} />
|
||||
)}
|
||||
<View style={styles.scraperInfo}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={[styles.scraperName, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.name}</Text>
|
||||
{scraper.manifestEnabled === false ? (
|
||||
<View style={[styles.availableIndicator, { backgroundColor: colors.mediumGray }]}>
|
||||
<Text style={styles.availableIndicatorText}>Disabled</Text>
|
||||
</View>
|
||||
) : !scraper.enabled && (
|
||||
<View style={styles.availableIndicator}>
|
||||
<Text style={styles.availableIndicatorText}>Available</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text style={[styles.scraperDescription, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.description}</Text>
|
||||
<View style={styles.scraperMeta}>
|
||||
<Text style={[styles.scraperVersion, !settings.enableLocalScrapers && styles.disabledText]}>v{scraper.version}</Text>
|
||||
<Text style={[styles.scraperDot, !settings.enableLocalScrapers && styles.disabledText]}>•</Text>
|
||||
<Text style={[styles.scraperTypes, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||
{scraper.supportedTypes && Array.isArray(scraper.supportedTypes) ? scraper.supportedTypes.join(', ') : 'Unknown'}
|
||||
</Text>
|
||||
{scraper.contentLanguage && Array.isArray(scraper.contentLanguage) && scraper.contentLanguage.length > 0 && (
|
||||
<>
|
||||
<Text style={[styles.scraperDot, !settings.enableLocalScrapers && styles.disabledText]}>•</Text>
|
||||
<Text style={[styles.scraperLanguage, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||
{scraper.contentLanguage.map(lang => lang.toUpperCase()).join(', ')}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<Switch
|
||||
value={scraper.enabled && settings.enableLocalScrapers}
|
||||
onValueChange={(enabled) => handleToggleScraper(scraper.id, enabled)}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={scraper.enabled && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers || scraper.manifestEnabled === false}
|
||||
style={{ opacity: (!settings.enableLocalScrapers || scraper.manifestEnabled === false) ? 0.5 : 1 }}
|
||||
/>
|
||||
</View>
|
||||
<Switch
|
||||
value={scraper.enabled && settings.enableLocalScrapers}
|
||||
onValueChange={(enabled) => handleToggleScraper(scraper.id, enabled)}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={scraper.enabled && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
|
@ -945,4 +993,4 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
export default ScraperSettingsScreen;
|
||||
export default PluginsScreen;
|
||||
|
|
@ -323,6 +323,13 @@ const SettingsScreen: React.FC = () => {
|
|||
renderControl={ChevronRight}
|
||||
onPress={() => navigation.navigate('Addons')}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Plugins"
|
||||
description="Manage plugins and repositories"
|
||||
icon="code"
|
||||
renderControl={ChevronRight}
|
||||
onPress={() => navigation.navigate('ScraperSettings')}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Catalogs"
|
||||
description={`${catalogCount} active`}
|
||||
|
|
@ -401,13 +408,6 @@ const SettingsScreen: React.FC = () => {
|
|||
renderControl={ChevronRight}
|
||||
onPress={() => navigation.navigate('PlayerSettings')}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Local Scrapers"
|
||||
description="Manage local scraper repositories"
|
||||
icon="code"
|
||||
renderControl={ChevronRight}
|
||||
onPress={() => navigation.navigate('ScraperSettings')}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Notifications"
|
||||
description="Episode reminders"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export interface ScraperInfo {
|
|||
enabled: boolean;
|
||||
logo?: string;
|
||||
contentLanguage?: string[];
|
||||
manifestEnabled?: boolean; // Whether the scraper is enabled in the manifest
|
||||
}
|
||||
|
||||
export interface LocalScraperResult {
|
||||
|
|
@ -189,17 +190,48 @@ class LocalScraperService {
|
|||
try {
|
||||
logger.log('[LocalScraperService] Fetching repository manifest from:', this.repositoryUrl);
|
||||
|
||||
// Fetch manifest
|
||||
const manifestUrl = this.repositoryUrl.endsWith('/')
|
||||
// Fetch manifest with cache busting
|
||||
const baseManifestUrl = this.repositoryUrl.endsWith('/')
|
||||
? `${this.repositoryUrl}manifest.json`
|
||||
: `${this.repositoryUrl}/manifest.json`;
|
||||
const manifestUrl = `${baseManifestUrl}?t=${Date.now()}`;
|
||||
|
||||
const response = await axios.get(manifestUrl, { timeout: 10000 });
|
||||
const manifest: ScraperManifest = response.data;
|
||||
const response = await axios.get(manifestUrl, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
const manifest: ScraperManifest = response.data;
|
||||
|
||||
logger.log('[LocalScraperService] getAvailableScrapers - Raw manifest data:', JSON.stringify(manifest, null, 2));
|
||||
logger.log('[LocalScraperService] getAvailableScrapers - Manifest scrapers count:', manifest.scrapers?.length || 0);
|
||||
|
||||
// Log each scraper's enabled status from manifest
|
||||
manifest.scrapers?.forEach(scraper => {
|
||||
logger.log(`[LocalScraperService] getAvailableScrapers - Scraper ${scraper.name}: enabled=${scraper.enabled}`);
|
||||
});
|
||||
|
||||
logger.log('[LocalScraperService] Found', manifest.scrapers.length, 'scrapers in repository');
|
||||
|
||||
// Download and install each scraper
|
||||
// Get current manifest scraper IDs
|
||||
const manifestScraperIds = new Set(manifest.scrapers.map(s => s.id));
|
||||
|
||||
// Remove scrapers that are no longer in the manifest
|
||||
const currentScraperIds = Array.from(this.installedScrapers.keys());
|
||||
for (const scraperId of currentScraperIds) {
|
||||
if (!manifestScraperIds.has(scraperId)) {
|
||||
logger.log('[LocalScraperService] Removing scraper no longer in manifest:', this.installedScrapers.get(scraperId)?.name || scraperId);
|
||||
this.installedScrapers.delete(scraperId);
|
||||
this.scraperCode.delete(scraperId);
|
||||
// Remove from AsyncStorage cache
|
||||
await AsyncStorage.removeItem(`scraper-code-${scraperId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Download and install each scraper from manifest
|
||||
for (const scraperInfo of manifest.scrapers) {
|
||||
await this.downloadScraper(scraperInfo);
|
||||
}
|
||||
|
|
@ -296,6 +328,65 @@ class LocalScraperService {
|
|||
return Array.from(this.installedScrapers.values());
|
||||
}
|
||||
|
||||
// Get available scrapers from manifest.json (for display in settings)
|
||||
async getAvailableScrapers(): Promise<ScraperInfo[]> {
|
||||
if (!this.repositoryUrl) {
|
||||
logger.log('[LocalScraperService] No repository URL configured, returning installed scrapers');
|
||||
return this.getInstalledScrapers();
|
||||
}
|
||||
|
||||
try {
|
||||
logger.log('[LocalScraperService] Fetching available scrapers from manifest');
|
||||
|
||||
// Fetch manifest with cache busting
|
||||
const baseManifestUrl = this.repositoryUrl.endsWith('/')
|
||||
? `${this.repositoryUrl}manifest.json`
|
||||
: `${this.repositoryUrl}/manifest.json`;
|
||||
const manifestUrl = `${baseManifestUrl}?t=${Date.now()}`;
|
||||
|
||||
const response = await axios.get(manifestUrl, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
const manifest: ScraperManifest = response.data;
|
||||
|
||||
// Return scrapers from manifest, respecting manifest's enabled field
|
||||
const availableScrapers = manifest.scrapers.map(scraperInfo => {
|
||||
const installedScraper = this.installedScrapers.get(scraperInfo.id);
|
||||
|
||||
// Create a copy with manifest data
|
||||
const scraperWithManifestData = {
|
||||
...scraperInfo,
|
||||
// Store the manifest's enabled state separately
|
||||
manifestEnabled: scraperInfo.enabled,
|
||||
// If manifest says enabled: false, scraper cannot be enabled
|
||||
// If manifest says enabled: true, use installed state or default to false
|
||||
enabled: scraperInfo.enabled ? (installedScraper?.enabled ?? false) : false
|
||||
};
|
||||
|
||||
return scraperWithManifestData;
|
||||
});
|
||||
|
||||
logger.log('[LocalScraperService] Found', availableScrapers.length, 'available scrapers in repository');
|
||||
|
||||
// Log final scraper states being returned to UI
|
||||
availableScrapers.forEach(scraper => {
|
||||
logger.log(`[LocalScraperService] Final scraper ${scraper.name}: manifestEnabled=${scraper.manifestEnabled}, enabled=${scraper.enabled}`);
|
||||
});
|
||||
|
||||
return availableScrapers;
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[LocalScraperService] Failed to fetch available scrapers from manifest:', error);
|
||||
// Fallback to installed scrapers if manifest fetch fails
|
||||
return this.getInstalledScrapers();
|
||||
}
|
||||
}
|
||||
|
||||
// Enable/disable scraper
|
||||
async setScraperEnabled(scraperId: string, enabled: boolean): Promise<void> {
|
||||
await this.ensureInitialized();
|
||||
|
|
@ -313,8 +404,14 @@ class LocalScraperService {
|
|||
async getStreams(type: string, tmdbId: string, season?: number, episode?: number, callback?: ScraperCallback): Promise<void> {
|
||||
await this.ensureInitialized();
|
||||
|
||||
const enabledScrapers = Array.from(this.installedScrapers.values())
|
||||
.filter(scraper => scraper.enabled && scraper.supportedTypes.includes(type as 'movie' | 'tv'));
|
||||
// Get available scrapers from manifest (respects manifestEnabled)
|
||||
const availableScrapers = await this.getAvailableScrapers();
|
||||
const enabledScrapers = availableScrapers
|
||||
.filter(scraper =>
|
||||
scraper.enabled &&
|
||||
scraper.manifestEnabled !== false &&
|
||||
scraper.supportedTypes.includes(type as 'movie' | 'tv')
|
||||
);
|
||||
|
||||
if (enabledScrapers.length === 0) {
|
||||
logger.log('[LocalScraperService] No enabled scrapers found for type:', type);
|
||||
|
|
|
|||
Loading…
Reference in a new issue