major ui changes

This commit is contained in:
tapframe 2025-07-29 00:09:16 +05:30
parent 9c12f9fc08
commit 494b35b1c0
5 changed files with 211 additions and 66 deletions

@ -1 +1 @@
Subproject commit 63d560d55f1a84a16318525ad4eb1db5162e059c
Subproject commit 22ed3a1c96ed2a8adf5bf9f277acd9d8c53c069c

View file

@ -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,

View file

@ -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;

View file

@ -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"

View file

@ -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);