mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
UI changes for pluginscreen
This commit is contained in:
parent
8d488298cf
commit
5143917117
3 changed files with 234 additions and 53 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -52,3 +52,4 @@ VERSION_UPDATE_README.md
|
|||
hackintosh-emulator-fix.sh
|
||||
/ota-builds
|
||||
src/screens/xavio.md
|
||||
/nuvio-providers
|
||||
|
|
|
|||
|
|
@ -490,16 +490,18 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
modalContent: {
|
||||
backgroundColor: colors.darkBackground,
|
||||
borderRadius: 16,
|
||||
padding: 24,
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
maxHeight: '80%',
|
||||
maxHeight: '70%',
|
||||
width: screenWidth - 40,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.elevation3,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginBottom: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
modalText: {
|
||||
fontSize: 16,
|
||||
|
|
@ -522,6 +524,82 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
// Compact modal styles
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 6,
|
||||
gap: 6,
|
||||
},
|
||||
compactTextInput: {
|
||||
backgroundColor: colors.darkBackground,
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
color: colors.white,
|
||||
fontSize: 15,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.elevation3,
|
||||
marginBottom: 12,
|
||||
},
|
||||
compactExamples: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
marginBottom: 12,
|
||||
},
|
||||
quickButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.elevation2,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 6,
|
||||
gap: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.elevation3,
|
||||
},
|
||||
quickButtonText: {
|
||||
fontSize: 12,
|
||||
color: colors.white,
|
||||
fontWeight: '500',
|
||||
},
|
||||
formatHint: {
|
||||
fontSize: 12,
|
||||
color: colors.mediumGray,
|
||||
fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace',
|
||||
marginBottom: 16,
|
||||
lineHeight: 16,
|
||||
},
|
||||
compactActions: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
compactButton: {
|
||||
flex: 1,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 6,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: 40,
|
||||
},
|
||||
cancelButton: {
|
||||
backgroundColor: colors.elevation2,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.elevation3,
|
||||
},
|
||||
cancelButtonText: {
|
||||
color: colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
addButton: {
|
||||
backgroundColor: colors.primary,
|
||||
},
|
||||
addButtonText: {
|
||||
color: colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
quickSetupContainer: {
|
||||
backgroundColor: colors.elevation2,
|
||||
borderRadius: 12,
|
||||
|
|
@ -563,6 +641,7 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
marginBottom: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.elevation3,
|
||||
minHeight: 120,
|
||||
},
|
||||
scraperCardHeader: {
|
||||
flexDirection: 'row',
|
||||
|
|
@ -577,12 +656,14 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: 8,
|
||||
gap: 12,
|
||||
gap: 8,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
scraperCardMetaItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
gap: 2,
|
||||
marginBottom: 4,
|
||||
},
|
||||
scraperCardMetaText: {
|
||||
fontSize: 12,
|
||||
|
|
@ -857,8 +938,27 @@ const PluginsScreen: React.FC = () => {
|
|||
const url = newRepositoryUrl.trim();
|
||||
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
|
||||
Alert.alert(
|
||||
'Invalid URL Format',
|
||||
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master'
|
||||
'Invalid URL Format',
|
||||
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch\n\nor include manifest.json:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch/manifest.json\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if URL already includes manifest.json
|
||||
const isManifestUrl = url.includes('/manifest.json');
|
||||
|
||||
// Normalize URL - if it's a manifest URL, extract the base repository URL
|
||||
let normalizedUrl = url;
|
||||
if (isManifestUrl) {
|
||||
normalizedUrl = url.replace('/manifest.json', '');
|
||||
logger.log('[PluginsScreen] Detected manifest URL, extracting base repository URL:', normalizedUrl);
|
||||
}
|
||||
|
||||
// Additional validation for normalized URL
|
||||
if (!normalizedUrl.endsWith('/refs/heads/') && !normalizedUrl.includes('/refs/heads/')) {
|
||||
Alert.alert(
|
||||
'Invalid Repository Structure',
|
||||
'The URL should point to a GitHub repository branch.\n\nExpected format:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -867,7 +967,7 @@ const PluginsScreen: React.FC = () => {
|
|||
setIsLoading(true);
|
||||
const repoId = await localScraperService.addRepository({
|
||||
name: '', // Let the service fetch from manifest
|
||||
url,
|
||||
url: normalizedUrl, // Use normalized URL (without manifest.json)
|
||||
description: '',
|
||||
enabled: true
|
||||
});
|
||||
|
|
@ -952,6 +1052,8 @@ const PluginsScreen: React.FC = () => {
|
|||
const loadScrapers = async () => {
|
||||
try {
|
||||
const scrapers = await localScraperService.getAvailableScrapers();
|
||||
|
||||
|
||||
setInstalledScrapers(scrapers);
|
||||
// preload showbox settings if present
|
||||
const sb = scrapers.find(s => s.id === 'showboxog');
|
||||
|
|
@ -1036,14 +1138,20 @@ const PluginsScreen: React.FC = () => {
|
|||
|
||||
try {
|
||||
setIsRefreshing(true);
|
||||
logger.log('[PluginsScreen] Starting hard refresh of repository...');
|
||||
|
||||
// Force a complete hard refresh by clearing any cached data first
|
||||
await localScraperService.refreshRepository();
|
||||
await loadScrapers(); // This will now load available scrapers from manifest
|
||||
Alert.alert('Success', 'Repository refreshed successfully');
|
||||
|
||||
// Load fresh scrapers from the updated repository
|
||||
await loadScrapers();
|
||||
|
||||
Alert.alert('Success', 'Repository refreshed successfully with latest files');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to refresh repository:', error);
|
||||
logger.error('[PluginsScreen] Failed to refresh repository:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
Alert.alert(
|
||||
'Repository Error',
|
||||
'Repository Error',
|
||||
`Failed to refresh repository: ${errorMessage}\n\nPlease ensure your URL is correct and follows this format:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch`
|
||||
);
|
||||
} finally {
|
||||
|
|
@ -1191,7 +1299,25 @@ const PluginsScreen: React.FC = () => {
|
|||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={isRefreshing} onRefresh={loadScrapers} />
|
||||
<RefreshControl
|
||||
refreshing={isRefreshing}
|
||||
onRefresh={async () => {
|
||||
try {
|
||||
setIsRefreshing(true);
|
||||
logger.log('[PluginsScreen] Pull-to-refresh: Starting hard refresh...');
|
||||
|
||||
// Force hard refresh of repository
|
||||
await localScraperService.refreshRepository();
|
||||
await loadScrapers();
|
||||
|
||||
logger.log('[PluginsScreen] Pull-to-refresh completed');
|
||||
} catch (error) {
|
||||
logger.error('[PluginsScreen] Pull-to-refresh failed:', error);
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{/* Quick Setup banner removed */}
|
||||
|
|
@ -1489,6 +1615,14 @@ const PluginsScreen: React.FC = () => {
|
|||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{scraper.supportsExternalPlayer === false && (
|
||||
<View style={styles.scraperCardMetaItem}>
|
||||
<Ionicons name="play-circle" size={12} color={colors.mediumGray} />
|
||||
<Text style={styles.scraperCardMetaText}>
|
||||
No external player
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* ShowBox Settings */}
|
||||
|
|
@ -1727,43 +1861,66 @@ const PluginsScreen: React.FC = () => {
|
|||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContent}>
|
||||
<Text style={styles.modalTitle}>Add New Repository</Text>
|
||||
|
||||
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>Repository URL</Text>
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
value={newRepositoryUrl}
|
||||
onChangeText={handleUrlChange}
|
||||
placeholder="https://raw.githubusercontent.com/username/repo/refs/heads/branch"
|
||||
placeholderTextColor={colors.mediumGray}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
keyboardType="url"
|
||||
/>
|
||||
|
||||
<View style={styles.buttonRow}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.modalHeader}>
|
||||
<Ionicons name="add-circle" size={20} color={colors.primary} />
|
||||
<Text style={styles.modalTitle}>Add Repository</Text>
|
||||
</View>
|
||||
|
||||
<TextInput
|
||||
style={styles.compactTextInput}
|
||||
value={newRepositoryUrl}
|
||||
onChangeText={handleUrlChange}
|
||||
placeholder="Repository URL"
|
||||
placeholderTextColor={colors.mediumGray}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
keyboardType="url"
|
||||
multiline={false}
|
||||
numberOfLines={1}
|
||||
/>
|
||||
|
||||
{/* Quick Example */}
|
||||
<View style={styles.compactExamples}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.secondaryButton, { flex: 1 }]}
|
||||
style={styles.quickButton}
|
||||
onPress={() => setNewRepositoryUrl('https://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main')}
|
||||
>
|
||||
<Ionicons name="star" size={14} color={colors.primary} />
|
||||
<Text style={styles.quickButtonText}>Official</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Format Hint */}
|
||||
<Text style={styles.formatHint}>
|
||||
Format: https://raw.githubusercontent.com/username/repo/refs/heads/branch
|
||||
</Text>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<View style={styles.compactActions}>
|
||||
<TouchableOpacity
|
||||
style={[styles.compactButton, styles.cancelButton]}
|
||||
onPress={() => {
|
||||
setShowAddRepositoryModal(false);
|
||||
setNewRepositoryUrl('');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Cancel</Text>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.primaryButton, { flex: 1 }]}
|
||||
style={[styles.compactButton, styles.addButton, (!newRepositoryUrl.trim() || isLoading) && styles.disabledButton]}
|
||||
onPress={handleAddRepository}
|
||||
disabled={isLoading}
|
||||
disabled={!newRepositoryUrl.trim() || isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator size="small" color="#ffffff" />
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
) : (
|
||||
<Text style={styles.buttonText}>Add Repository</Text>
|
||||
<Text style={styles.addButtonText}>Add</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export interface ScraperInfo {
|
|||
formats?: string[];
|
||||
supportedFormats?: string[];
|
||||
repositoryId?: string; // Which repository this scraper came from
|
||||
supportsExternalPlayer?: boolean; // Whether this scraper supports external players
|
||||
}
|
||||
|
||||
export interface RepositoryInfo {
|
||||
|
|
@ -600,14 +601,25 @@ class LocalScraperService {
|
|||
|
||||
try {
|
||||
logger.log('[LocalScraperService] Fetching repository manifest from:', this.repositoryUrl);
|
||||
|
||||
|
||||
// Clear all cached scraper code for this repository to force hard refresh
|
||||
const cachedScraperIds = Array.from(this.installedScrapers.keys());
|
||||
for (const scraperId of cachedScraperIds) {
|
||||
const scraper = this.installedScrapers.get(scraperId);
|
||||
if (scraper && scraper.repositoryId === this.currentRepositoryId) {
|
||||
this.scraperCode.delete(scraperId);
|
||||
await AsyncStorage.removeItem(`scraper-code-${scraperId}`);
|
||||
logger.log('[LocalScraperService] Cleared cached code for scraper:', scraper.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch manifest with cache busting
|
||||
const baseManifestUrl = this.repositoryUrl.endsWith('/')
|
||||
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, {
|
||||
const manifestUrl = `${baseManifestUrl}?t=${Date.now()}&v=${Math.random()}`;
|
||||
|
||||
const response = await axios.get(manifestUrl, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
|
|
@ -700,7 +712,17 @@ class LocalScraperService {
|
|||
|
||||
logger.log('[LocalScraperService] Downloading scraper:', scraperInfo.name);
|
||||
|
||||
const response = await axios.get(scraperUrl, { timeout: 15000 });
|
||||
// Add cache-busting parameters to force fresh download
|
||||
const scraperUrlWithCacheBust = `${scraperUrl}?t=${Date.now()}&v=${Math.random()}`;
|
||||
|
||||
const response = await axios.get(scraperUrlWithCacheBust, {
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
const scraperCode = response.data;
|
||||
|
||||
// Store scraper info and code
|
||||
|
|
@ -836,19 +858,19 @@ class LocalScraperService {
|
|||
logger.log('[LocalScraperService] Fetching available scrapers from manifest');
|
||||
|
||||
// Fetch manifest with cache busting
|
||||
const baseManifestUrl = this.repositoryUrl.endsWith('/')
|
||||
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 manifestUrl = `${baseManifestUrl}?t=${Date.now()}&v=${Math.random()}`;
|
||||
|
||||
const response = await axios.get(manifestUrl, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
const manifest: ScraperManifest = response.data;
|
||||
|
||||
// Store repository name from manifest
|
||||
|
|
@ -872,6 +894,7 @@ class LocalScraperService {
|
|||
enabled: scraperInfo.enabled ? (installedScraper?.enabled ?? false) : false
|
||||
};
|
||||
|
||||
|
||||
// Normalize formats fields (support both `formats` and `supportedFormats`)
|
||||
const anyScraper: any = scraperWithManifestData as any;
|
||||
if (typeof anyScraper.formats === 'string') {
|
||||
|
|
|
|||
Loading…
Reference in a new issue