Fix: Critical Tablet screen crash fix upon app opening

This commit is contained in:
tapframe 2026-01-08 15:54:40 +05:30
parent 2ebec55bbc
commit bb94a49662
11 changed files with 41 additions and 657 deletions

View file

@ -34,7 +34,9 @@
"thu": "الخميس",
"fri": "الجمعة",
"sat": "السبت"
}
},
"email": "البريد الإلكتروني",
"status": "الحالة"
},
"home": {
"categories": {
@ -655,7 +657,7 @@
"addons": "الإضافات",
"installed": "مثبتة",
"debrid_integration": "تكامل Debrid",
"debrid_desc": "توصيل Torbox للبث المميز",
"debrid_desc": "توصيل Torbox",
"plugins": "البلاجنز",
"plugins_desc": "إدارة البلاجنز والمستودعات",
"catalogs": "الكتالوجات",

View file

@ -34,7 +34,9 @@
"thu": "Do",
"fri": "Fr",
"sat": "Sa"
}
},
"email": "E-Mail",
"status": "Status"
},
"home": {
"categories": {
@ -658,7 +660,7 @@
"addons": "Addons",
"installed": "installiert",
"debrid_integration": "Debrid-Integration",
"debrid_desc": "Torbox für Premium-Streams verbinden",
"debrid_desc": "Torbox verbinden",
"plugins": "Plugins",
"plugins_desc": "Plugins und Repositories verwalten",
"catalogs": "Kataloge",

View file

@ -34,7 +34,9 @@
"thu": "Thu",
"fri": "Fri",
"sat": "Sat"
}
},
"email": "Email",
"status": "Status"
},
"home": {
"categories": {
@ -658,7 +660,7 @@
"addons": "Addons",
"installed": "installed",
"debrid_integration": "Debrid Integration",
"debrid_desc": "Connect Torbox for premium streams",
"debrid_desc": "Connect Torbox",
"plugins": "Plugins",
"plugins_desc": "Manage plugins and repositories",
"catalogs": "Catalogs",

View file

@ -34,7 +34,9 @@
"thu": "Jue",
"fri": "Vie",
"sat": "Sáb"
}
},
"email": "Email",
"status": "Estado"
},
"home": {
"categories": {
@ -655,7 +657,7 @@
"addons": "Complementos",
"installed": "instalados",
"debrid_integration": "Integración de Debrid",
"debrid_desc": "Conectar Torbox para fuentes premium",
"debrid_desc": "Conectar Torbox",
"plugins": "Plugins",
"plugins_desc": "Gestionar plugins y repositorios",
"catalogs": "Catálogos",

View file

@ -34,7 +34,9 @@
"thu": "Jeu",
"fri": "Ven",
"sat": "Sam"
}
},
"email": "E-mail",
"status": "Statut"
},
"home": {
"categories": {
@ -655,7 +657,7 @@
"addons": "Extensions",
"installed": "installées",
"debrid_integration": "Intégration Debrid",
"debrid_desc": "Connecter Torbox pour des flux premium",
"debrid_desc": "Connecter Torbox",
"plugins": "Plugins",
"plugins_desc": "Gérer les plugins et les dépôts",
"catalogs": "Catalogues",

View file

@ -34,7 +34,9 @@
"thu": "Gio",
"fri": "Ven",
"sat": "Sab"
}
},
"email": "Email",
"status": "Stato"
},
"home": {
"categories": {
@ -655,7 +657,7 @@
"addons": "Addon",
"installed": "installati",
"debrid_integration": "Integrazione Debrid",
"debrid_desc": "Connetti Torbox per streaming premium",
"debrid_desc": "Connetti Torbox",
"plugins": "Plugin",
"plugins_desc": "Gestisci plugin e repository",
"catalogs": "Cataloghi",

View file

@ -34,7 +34,9 @@
"thu": "Qui",
"fri": "Sex",
"sat": "Sáb"
}
},
"email": "E-mail",
"status": "Status"
},
"home": {
"categories": {
@ -636,7 +638,7 @@
"addons": "Addons",
"installed": "instalados",
"debrid_integration": "Integração Debrid",
"debrid_desc": "Conectar Torbox para streams premium",
"debrid_desc": "Conectar Torbox",
"plugins": "Plugins",
"plugins_desc": "Gerenciar plugins e repositórios",
"catalogs": "Catálogos",

View file

@ -34,7 +34,9 @@
"thu": "Qui",
"fri": "Sex",
"sat": "Sáb"
}
},
"email": "E-mail",
"status": "Estado"
},
"home": {
"categories": {
@ -636,7 +638,7 @@
"addons": "Addons",
"installed": "instalados",
"debrid_integration": "Integração Debrid",
"debrid_desc": "Conectar Torbox para streams premium",
"debrid_desc": "Conectar Torbox",
"plugins": "Plugins",
"plugins_desc": "Gerir plugins e repositórios",
"catalogs": "Catálogos",

View file

@ -695,8 +695,6 @@ const MainTabs = () => {
const isFocused = props.state.index === index;
const lastTapRef = useRef<Record<string, number>>({}); // Add this ref at the top of MainTabs component
const onPress = () => {
const now = Date.now();
const DOUBLE_TAP_DELAY = 300;

View file

@ -569,20 +569,10 @@ const AddonsScreen = () => {
// Use the regular method without disabled state
const installedAddons = await stremioService.getInstalledAddonsAsync();
// Filter out Torbox addons (managed via DebridIntegrationScreen)
// Filter out only the official Torbox integration addon (managed via DebridIntegrationScreen)
// but allow other addons (like Torrentio, MediaFusion) that may be configured with Torbox
const filteredAddons = installedAddons.filter(addon => {
const isOfficialTorboxAddon =
addon.url?.includes('stremio.torbox.app') ||
(addon as any).transport?.includes('stremio.torbox.app') ||
// Check for ID but be careful not to catch others if possible, though ID usually comes from URL in stremioService
(addon.id?.includes('stremio.torbox.app'));
setAddons(installedAddons as ExtendedManifest[]);
return !isOfficialTorboxAddon;
});
setAddons(filteredAddons as ExtendedManifest[]);
// Kept variable for compatibility with existing counting logic below
const filteredAddons = installedAddons;
// Count catalogs
let totalCatalogs = 0;

View file

@ -32,7 +32,7 @@ import axios from 'axios';
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
const TORBOX_STORAGE_KEY = 'torbox_debrid_config';
const TORBOX_API_BASE = 'https://api.torbox.app/v1';
const TORRENTIO_CONFIG_KEY = 'torrentio_config';
interface TorboxConfig {
apiKey: string;
@ -51,115 +51,7 @@ interface TorboxUserData {
base_email: string;
}
// Torrentio Configuration Types
interface TorrentioConfig {
providers: string[];
sort: string;
qualityFilter: string[];
priorityLanguages: string[];
maxResults: string;
debridService: string;
debridApiKey: string;
noDownloadLinks: boolean;
noCatalog: boolean;
isInstalled: boolean;
manifestUrl?: string;
}
// Torrentio Options Data
const TORRENTIO_PROVIDERS = [
{ id: 'yts', name: 'YTS' },
{ id: 'eztv', name: 'EZTV' },
{ id: 'rarbg', name: 'RARBG' },
{ id: '1337x', name: '1337x' },
{ id: 'thepiratebay', name: 'ThePirateBay' },
{ id: 'kickasstorrents', name: 'KickassTorrents' },
{ id: 'torrentgalaxy', name: 'TorrentGalaxy' },
{ id: 'magnetdl', name: 'MagnetDL' },
{ id: 'horriblesubs', name: 'HorribleSubs' },
{ id: 'nyaasi', name: 'NyaaSi' },
{ id: 'tokyotosho', name: 'TokyoTosho' },
{ id: 'anidex', name: 'AniDex' },
{ id: 'rutor', name: '🇷🇺 Rutor' },
{ id: 'rutracker', name: '🇷🇺 Rutracker' },
{ id: 'comando', name: '🇵🇹 Comando' },
{ id: 'bludv', name: '🇧🇷 BluDV' },
{ id: 'torrent9', name: '🇫🇷 Torrent9' },
{ id: 'ilcorsaronero', name: '🇮🇹 ilCorSaRoNeRo' },
{ id: 'mejortorrent', name: '🇪🇸 MejorTorrent' },
{ id: 'wolfmax4k', name: '🇪🇸 Wolfmax4k' },
{ id: 'cinecalidad', name: '🇲🇽 Cinecalidad' },
];
const TORRENTIO_SORT_OPTIONS = [
{ id: 'quality', name: 'By quality then seeders' },
{ id: 'qualitysize', name: 'By quality then size' },
{ id: 'seeders', name: 'By seeders' },
{ id: 'size', name: 'By size' },
];
const TORRENTIO_QUALITY_FILTERS = [
{ id: 'brremux', name: 'BluRay REMUX' },
{ id: 'hdrall', name: 'HDR/HDR10+/Dolby Vision' },
{ id: 'dolbyvision', name: 'Dolby Vision' },
{ id: '4k', name: '4K' },
{ id: '1080p', name: '1080p' },
{ id: '720p', name: '720p' },
{ id: '480p', name: '480p' },
{ id: 'scr', name: 'Screener' },
{ id: 'cam', name: 'CAM' },
{ id: 'unknown', name: 'Unknown' },
];
const TORRENTIO_LANGUAGES = [
{ id: 'english', name: '🇬🇧 English' },
{ id: 'russian', name: '🇷🇺 Russian' },
{ id: 'italian', name: '🇮🇹 Italian' },
{ id: 'portuguese', name: '🇵🇹 Portuguese' },
{ id: 'spanish', name: '🇪🇸 Spanish' },
{ id: 'latino', name: '🇲🇽 Latino' },
{ id: 'korean', name: '🇰🇷 Korean' },
{ id: 'chinese', name: '🇨🇳 Chinese' },
{ id: 'french', name: '🇫🇷 French' },
{ id: 'german', name: '🇩🇪 German' },
{ id: 'dutch', name: '🇳🇱 Dutch' },
{ id: 'hindi', name: '🇮🇳 Hindi' },
{ id: 'japanese', name: '🇯🇵 Japanese' },
{ id: 'polish', name: '🇵🇱 Polish' },
{ id: 'arabic', name: '🇸🇦 Arabic' },
{ id: 'turkish', name: '🇹🇷 Turkish' },
];
const TORRENTIO_DEBRID_SERVICES = [
{ id: 'torbox', name: 'TorBox', keyParam: 'torbox' },
{ id: 'realdebrid', name: 'RealDebrid', keyParam: 'realdebrid' },
{ id: 'alldebrid', name: 'AllDebrid', keyParam: 'alldebrid' },
{ id: 'premiumize', name: 'Premiumize', keyParam: 'premiumize' },
{ id: 'debridlink', name: 'DebridLink', keyParam: 'debridlink' },
{ id: 'offcloud', name: 'Offcloud', keyParam: 'offcloud' },
];
const TORRENTIO_MAX_RESULTS = [
{ id: '', name: 'All results' },
{ id: '1', name: '1 per quality' },
{ id: '2', name: '2 per quality' },
{ id: '3', name: '3 per quality' },
{ id: '5', name: '5 per quality' },
{ id: '10', name: '10 per quality' },
];
const DEFAULT_TORRENTIO_CONFIG: TorrentioConfig = {
providers: TORRENTIO_PROVIDERS.map(p => p.id), // All providers by default
sort: 'quality',
qualityFilter: ['scr', 'cam'],
priorityLanguages: [],
maxResults: '',
debridService: 'torbox', // Default to TorBox
debridApiKey: '',
noDownloadLinks: false,
noCatalog: false,
isInstalled: false,
};
const getPlanName = (plan: number, t: any): string => {
switch (plan) {
@ -694,8 +586,7 @@ const DebridIntegrationScreen = () => {
const colors = currentTheme.colors;
const styles = createStyles(colors);
// Tab state
const [activeTab, setActiveTab] = useState<'torbox' | 'torrentio'>('torbox');
// Torbox state
const [apiKey, setApiKey] = useState('');
@ -706,19 +597,6 @@ const DebridIntegrationScreen = () => {
const [userDataLoading, setUserDataLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
// Torrentio state
const [torrentioConfig, setTorrentioConfig] = useState<TorrentioConfig>(DEFAULT_TORRENTIO_CONFIG);
const [torrentioLoading, setTorrentioLoading] = useState(false);
// Accordion states for collapsible sections
const [expandedSections, setExpandedSections] = useState<{ [key: string]: boolean }>({
sorting: false,
qualityFilter: false,
languages: false,
maxResults: false,
options: false,
});
// Alert state
const [alertVisible, setAlertVisible] = useState(false);
const [alertTitle, setAlertTitle] = useState('');
@ -758,34 +636,7 @@ const DebridIntegrationScreen = () => {
}
}, []);
// Load Torrentio config
const loadTorrentioConfig = useCallback(async () => {
try {
const storedConfig = await mmkvStorage.getItem(TORRENTIO_CONFIG_KEY);
if (storedConfig) {
const parsedConfig = JSON.parse(storedConfig);
setTorrentioConfig(parsedConfig);
}
// Check if Torrentio addon is installed
const addons = await stremioService.getInstalledAddonsAsync();
const torrentioAddon = addons.find(addon =>
addon.id?.includes('torrentio') ||
addon.url?.includes('torrentio.strem.fun') ||
(addon as any).transport?.includes('torrentio.strem.fun')
);
if (torrentioAddon) {
setTorrentioConfig(prev => ({
...prev,
isInstalled: true,
manifestUrl: (torrentioAddon as any).transport || torrentioAddon.url
}));
}
} catch (error) {
logger.error('Failed to load Torrentio config:', error);
}
}, []);
const fetchUserData = useCallback(async () => {
if (!config?.apiKey || !config?.isConnected) return;
@ -814,8 +665,7 @@ const DebridIntegrationScreen = () => {
useFocusEffect(
useCallback(() => {
loadConfig();
loadTorrentioConfig();
}, [loadConfig, loadTorrentioConfig])
}, [loadConfig])
);
useEffect(() => {
@ -826,9 +676,9 @@ const DebridIntegrationScreen = () => {
const onRefresh = useCallback(async () => {
setRefreshing(true);
await Promise.all([loadConfig(), loadTorrentioConfig(), fetchUserData()]);
await Promise.all([loadConfig(), fetchUserData()]);
setRefreshing(false);
}, [loadConfig, loadTorrentioConfig, fetchUserData]);
}, [loadConfig, fetchUserData]);
// Torbox handlers
const handleConnect = async () => {
@ -939,176 +789,6 @@ const DebridIntegrationScreen = () => {
Linking.openURL('https://torbox.app/subscription?referral=493192f2-6403-440f-b414-768f72222ec7');
};
// Torrentio handlers
const generateTorrentioManifestUrl = useCallback((): string => {
const parts: string[] = [];
// Providers (only if not all selected)
if (torrentioConfig.providers.length > 0 && torrentioConfig.providers.length < TORRENTIO_PROVIDERS.length) {
parts.push(`providers=${torrentioConfig.providers.join(',')}`);
}
// Sort (only if not default)
if (torrentioConfig.sort && torrentioConfig.sort !== 'quality') {
parts.push(`sort=${torrentioConfig.sort}`);
}
// Quality filter
if (torrentioConfig.qualityFilter.length > 0) {
parts.push(`qualityfilter=${torrentioConfig.qualityFilter.join(',')}`);
}
// Priority languages
if (torrentioConfig.priorityLanguages.length > 0) {
parts.push(`language=${torrentioConfig.priorityLanguages.join(',')}`);
}
// Max results
if (torrentioConfig.maxResults) {
parts.push(`limit=${torrentioConfig.maxResults}`);
}
// Debrid service and API key
if (torrentioConfig.debridService !== 'none' && torrentioConfig.debridApiKey) {
const debridInfo = TORRENTIO_DEBRID_SERVICES.find(d => d.id === torrentioConfig.debridService);
if (debridInfo) {
parts.push(`${debridInfo.keyParam}=${torrentioConfig.debridApiKey}`);
}
}
// Options
if (torrentioConfig.noDownloadLinks) {
parts.push('nodownloadlinks=true');
}
if (torrentioConfig.noCatalog) {
parts.push('nocatalog=true');
}
const configString = parts.length > 0 ? parts.join('|') + '/' : '';
return `https://torrentio.strem.fun/${configString}manifest.json`;
}, [torrentioConfig]);
const toggleQualityFilter = (qualityId: string) => {
setTorrentioConfig(prev => {
const newFilters = prev.qualityFilter.includes(qualityId)
? prev.qualityFilter.filter(q => q !== qualityId)
: [...prev.qualityFilter, qualityId];
return { ...prev, qualityFilter: newFilters };
});
};
const toggleLanguage = (langId: string) => {
setTorrentioConfig(prev => {
const newLangs = prev.priorityLanguages.includes(langId)
? prev.priorityLanguages.filter(l => l !== langId)
: [...prev.priorityLanguages, langId];
return { ...prev, priorityLanguages: newLangs };
});
};
const handleInstallTorrentio = async () => {
// Check if API key is provided
if (!torrentioConfig.debridApiKey.trim()) {
setAlertTitle(t('debrid.error_api_required'));
setAlertMessage(t('debrid.error_api_required_desc'));
setAlertActions([{ label: t('common.ok'), onPress: () => setAlertVisible(false) }]);
setAlertVisible(true);
return;
}
setTorrentioLoading(true);
try {
const manifestUrl = generateTorrentioManifestUrl();
// Check if already installed
const addons = await stremioService.getInstalledAddonsAsync();
const existingTorrentio = addons.find(addon =>
addon.id?.includes('torrentio') ||
addon.url?.includes('torrentio.strem.fun') ||
(addon as any).transport?.includes('torrentio.strem.fun')
);
if (existingTorrentio) {
// Remove existing and reinstall with new config
await stremioService.removeAddon(existingTorrentio.id);
}
await stremioService.installAddon(manifestUrl);
// Save config
const newConfig = {
...torrentioConfig,
isInstalled: true,
manifestUrl
};
await mmkvStorage.setItem(TORRENTIO_CONFIG_KEY, JSON.stringify(newConfig));
setTorrentioConfig(newConfig);
setAlertTitle(t('common.success'));
setAlertMessage(t('debrid.success_installed'));
setAlertActions([{ label: t('common.ok'), onPress: () => setAlertVisible(false) }]);
setAlertVisible(true);
} catch (error) {
logger.error('Failed to install Torrentio addon:', error);
setAlertTitle(t('common.error'));
setAlertMessage(t('addons.install_error'));
setAlertActions([{ label: t('common.ok'), onPress: () => setAlertVisible(false) }]);
setAlertVisible(true);
} finally {
setTorrentioLoading(false);
}
};
const handleRemoveTorrentio = async () => {
setAlertTitle(t('debrid.remove_button'));
setAlertMessage(t('addons.uninstall_message', { name: 'Torrentio' }));
setAlertActions([
{ label: t('common.cancel'), onPress: () => setAlertVisible(false), style: { color: colors.mediumGray } },
{
label: t('debrid.remove_button'),
onPress: async () => {
setAlertVisible(false);
setTorrentioLoading(true);
try {
const addons = await stremioService.getInstalledAddonsAsync();
const torrentioAddon = addons.find(addon =>
addon.id?.includes('torrentio') ||
addon.url?.includes('torrentio.strem.fun') ||
(addon as any).transport?.includes('torrentio.strem.fun')
);
if (torrentioAddon) {
await stremioService.removeAddon(torrentioAddon.id);
}
const newConfig = {
...torrentioConfig,
isInstalled: false,
manifestUrl: undefined
};
await mmkvStorage.setItem(TORRENTIO_CONFIG_KEY, JSON.stringify(newConfig));
setTorrentioConfig(newConfig);
setAlertTitle(t('common.success'));
setAlertMessage(t('debrid.success_removed'));
setAlertActions([{ label: t('common.ok'), onPress: () => setAlertVisible(false) }]);
setAlertVisible(true);
} catch (error) {
logger.error('Failed to remove Torrentio:', error);
setAlertTitle(t('common.error'));
setAlertMessage(t('addons.uninstall_error', 'Failed to remove Torrentio addon'));
setAlertActions([{ label: t('common.ok'), onPress: () => setAlertVisible(false) }]);
setAlertVisible(true);
} finally {
setTorrentioLoading(false);
}
},
style: { color: colors.error || '#F44336' }
}
]);
setAlertVisible(true);
};
// Render Torbox Tab
const renderTorboxTab = () => (
<>
@ -1284,286 +964,6 @@ const DebridIntegrationScreen = () => {
</>
);
// Render Torrentio Tab
const toggleSection = (section: string) => {
setExpandedSections(prev => ({ ...prev, [section]: !prev[section] }));
};
const renderTorrentioTab = () => (
<>
<Text style={styles.description}>
{t('debrid.description_torrentio')}
</Text>
{torrentioConfig.isInstalled && (
<View style={styles.installedBadge}>
<Text style={styles.installedBadgeText}>{t('debrid.installed_badge')}</Text>
</View>
)}
{/* TorBox Promotion Card */}
{!torrentioConfig.debridApiKey && (
<View style={styles.promoCard}>
<Text style={styles.promoTitle}>{t('debrid.promo_title')}</Text>
<Text style={styles.promoText}>
{t('debrid.promo_desc')}
</Text>
<TouchableOpacity
style={styles.promoButton}
onPress={() => Linking.openURL('https://torbox.app/subscription?referral=493192f2-6403-440f-b414-768f72222ec7')}
>
<Text style={styles.promoButtonText}>{t('debrid.promo_button')}</Text>
</TouchableOpacity>
</View>
)}
{/* Debrid Service Selection */}
<View style={styles.configSection}>
<Text style={styles.configSectionTitle}>{t('debrid.service_label')}</Text>
<View style={styles.pickerContainer}>
{TORRENTIO_DEBRID_SERVICES.map((service: any) => (
<TouchableOpacity
key={service.id}
style={[
styles.pickerItem,
torrentioConfig.debridService === service.id && styles.pickerItemSelected
]}
onPress={() => setTorrentioConfig(prev => ({ ...prev, debridService: service.id }))}
>
<Text style={[
styles.pickerItemText,
torrentioConfig.debridService === service.id && styles.pickerItemTextSelected
]}>
{service.name}
</Text>
</TouchableOpacity>
))}
</View>
</View>
{/* Debrid API Key */}
<View style={styles.configSection}>
<Text style={styles.configSectionTitle}>{t('debrid.api_key_label')}</Text>
<TextInput
style={styles.input}
placeholder={`Enter your ${TORRENTIO_DEBRID_SERVICES.find((d: any) => d.id === torrentioConfig.debridService)?.name || 'Debrid'} API Key`}
placeholderTextColor={colors.mediumGray}
value={torrentioConfig.debridApiKey}
onChangeText={(text) => setTorrentioConfig(prev => ({ ...prev, debridApiKey: text }))}
autoCapitalize="none"
autoCorrect={false}
secureTextEntry
/>
</View>
{/* Sorting - Accordion */}
<TouchableOpacity
style={[styles.accordionHeader, expandedSections.sorting && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, marginBottom: 0 }]}
onPress={() => toggleSection('sorting')}
>
<View>
<Text style={styles.accordionHeaderText}>{t('debrid.sorting_label')}</Text>
<Text style={styles.accordionSubtext}>
{TORRENTIO_SORT_OPTIONS.find(o => o.id === torrentioConfig.sort)?.name || t('debrid.by_quality', 'By quality')}
</Text>
</View>
<Feather name={expandedSections.sorting ? 'chevron-up' : 'chevron-down'} size={20} color={colors.mediumEmphasis} />
</TouchableOpacity>
{expandedSections.sorting && (
<View style={styles.accordionContent}>
<View style={styles.pickerContainer}>
{TORRENTIO_SORT_OPTIONS.map(option => (
<TouchableOpacity
key={option.id}
style={[styles.pickerItem, torrentioConfig.sort === option.id && styles.pickerItemSelected]}
onPress={() => setTorrentioConfig(prev => ({ ...prev, sort: option.id }))}
>
<Text style={[styles.pickerItemText, torrentioConfig.sort === option.id && styles.pickerItemTextSelected]}>
{option.name}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
{/* Quality Filter - Accordion */}
<TouchableOpacity
style={[styles.accordionHeader, expandedSections.qualityFilter && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, marginBottom: 0 }]}
onPress={() => toggleSection('qualityFilter')}
>
<View>
<Text style={styles.accordionHeaderText}>{t('debrid.exclude_qualities')}</Text>
<Text style={styles.accordionSubtext}>
{torrentioConfig.qualityFilter.length > 0 ? t('debrid.excluded_count', { count: torrentioConfig.qualityFilter.length, defaultValue: '{{count}} excluded' }) : t('debrid.none_excluded', 'None excluded')}
</Text>
</View>
<Feather name={expandedSections.qualityFilter ? 'chevron-up' : 'chevron-down'} size={20} color={colors.mediumEmphasis} />
</TouchableOpacity>
{expandedSections.qualityFilter && (
<View style={styles.accordionContent}>
<View style={styles.chipContainer}>
{TORRENTIO_QUALITY_FILTERS.map(quality => (
<TouchableOpacity
key={quality.id}
style={[styles.chip, torrentioConfig.qualityFilter.includes(quality.id) && styles.chipSelected]}
onPress={() => toggleQualityFilter(quality.id)}
>
<Text style={[styles.chipText, torrentioConfig.qualityFilter.includes(quality.id) && styles.chipTextSelected]}>
{quality.name}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
{/* Priority Languages - Accordion */}
<TouchableOpacity
style={[styles.accordionHeader, expandedSections.languages && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, marginBottom: 0 }]}
onPress={() => toggleSection('languages')}
>
<View>
<Text style={styles.accordionHeaderText}>{t('debrid.priority_languages')}</Text>
<Text style={styles.accordionSubtext}>
{torrentioConfig.priorityLanguages.length > 0 ? `${torrentioConfig.priorityLanguages.length} ${t('home_screen.selected')}` : t('debrid.no_preference', 'No preference')}
</Text>
</View>
<Feather name={expandedSections.languages ? 'chevron-up' : 'chevron-down'} size={20} color={colors.mediumEmphasis} />
</TouchableOpacity>
{expandedSections.languages && (
<View style={styles.accordionContent}>
<View style={styles.chipContainer}>
{TORRENTIO_LANGUAGES.map(lang => (
<TouchableOpacity
key={lang.id}
style={[styles.chip, torrentioConfig.priorityLanguages.includes(lang.id) && styles.chipSelected]}
onPress={() => toggleLanguage(lang.id)}
>
<Text style={[styles.chipText, torrentioConfig.priorityLanguages.includes(lang.id) && styles.chipTextSelected]}>
{lang.name}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
{/* Max Results - Accordion */}
<TouchableOpacity
style={[styles.accordionHeader, expandedSections.maxResults && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, marginBottom: 0 }]}
onPress={() => toggleSection('maxResults')}
>
<View>
<Text style={styles.accordionHeaderText}>{t('debrid.max_results')}</Text>
<Text style={styles.accordionSubtext}>
{TORRENTIO_MAX_RESULTS.find(o => o.id === torrentioConfig.maxResults)?.name || t('debrid.all_results', 'All results')}
</Text>
</View>
<Feather name={expandedSections.maxResults ? 'chevron-up' : 'chevron-down'} size={20} color={colors.mediumEmphasis} />
</TouchableOpacity>
{expandedSections.maxResults && (
<View style={styles.accordionContent}>
<View style={styles.pickerContainer}>
{TORRENTIO_MAX_RESULTS.map(option => (
<TouchableOpacity
key={option.id || 'all'}
style={[styles.pickerItem, torrentioConfig.maxResults === option.id && styles.pickerItemSelected]}
onPress={() => setTorrentioConfig(prev => ({ ...prev, maxResults: option.id }))}
>
<Text style={[styles.pickerItemText, torrentioConfig.maxResults === option.id && styles.pickerItemTextSelected]}>
{option.name}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
{/* Additional Options - Accordion */}
<TouchableOpacity
style={[styles.accordionHeader, expandedSections.options && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, marginBottom: 0 }]}
onPress={() => toggleSection('options')}
>
<View>
<Text style={styles.accordionHeaderText}>{t('debrid.additional_options')}</Text>
<Text style={styles.accordionSubtext}>{t('debrid.catalog_download_settings', 'Catalog & download settings')}</Text>
</View>
<Feather name={expandedSections.options ? 'chevron-up' : 'chevron-down'} size={20} color={colors.mediumEmphasis} />
</TouchableOpacity>
{expandedSections.options && (
<View style={styles.accordionContent}>
<View style={styles.switchRow}>
<Text style={styles.switchLabel}>{t('debrid.no_download_links')}</Text>
<Switch
value={torrentioConfig.noDownloadLinks}
onValueChange={(val) => setTorrentioConfig(prev => ({ ...prev, noDownloadLinks: val }))}
trackColor={{ false: colors.elevation3, true: colors.primary }}
thumbColor={colors.white}
/>
</View>
<View style={styles.switchRow}>
<Text style={styles.switchLabel}>{t('debrid.no_debrid_catalog')}</Text>
<Switch
value={torrentioConfig.noCatalog}
onValueChange={(val) => setTorrentioConfig(prev => ({ ...prev, noCatalog: val }))}
trackColor={{ false: colors.elevation3, true: colors.primary }}
thumbColor={colors.white}
/>
</View>
</View>
)}
{/* Manifest URL Preview */}
<View style={styles.configSection}>
<Text style={styles.configSectionTitle}>Manifest URL</Text>
<View style={styles.manifestPreview}>
<Text style={styles.manifestUrl} numberOfLines={3}>
{generateTorrentioManifestUrl()}
</Text>
</View>
</View>
{/* Install/Update/Remove Buttons */}
<View style={{ marginTop: 8 }}>
{torrentioConfig.isInstalled ? (
<>
<TouchableOpacity
style={[styles.connectButton, torrentioLoading && styles.disabledButton]}
onPress={handleInstallTorrentio}
disabled={torrentioLoading}
>
<Text style={styles.connectButtonText}>
{torrentioLoading ? t('debrid.updating') : t('debrid.update_button')}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionButton, styles.dangerButton, torrentioLoading && styles.disabledButton]}
onPress={handleRemoveTorrentio}
disabled={torrentioLoading}
>
<Text style={styles.buttonText}>{t('debrid.remove_button')}</Text>
</TouchableOpacity>
</>
) : (
<TouchableOpacity
style={[styles.connectButton, torrentioLoading && styles.disabledButton]}
onPress={handleInstallTorrentio}
disabled={torrentioLoading}
>
<Text style={styles.connectButtonText}>
{torrentioLoading ? t('debrid.installing') : t('debrid.install_button')}
</Text>
</TouchableOpacity>
)}
</View>
<Text style={[styles.disclaimer, { marginTop: 24, marginBottom: 40 }]}>
{t('debrid.disclaimer_torrentio')}
</Text>
</>
);
if (initialLoading) {
return (
<SafeAreaView style={styles.container}>
@ -1589,26 +989,6 @@ const DebridIntegrationScreen = () => {
<Text style={styles.headerTitle}>{t('debrid.title')}</Text>
</View>
{/* Tab Selector */}
<View style={styles.tabContainer}>
<TouchableOpacity
style={[styles.tab, activeTab === 'torbox' && styles.activeTab]}
onPress={() => setActiveTab('torbox')}
>
<Text style={[styles.tabText, activeTab === 'torbox' && styles.activeTabText]}>
{t('debrid.tab_torbox')}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'torrentio' && styles.activeTab]}
onPress={() => setActiveTab('torrentio')}
>
<Text style={[styles.tabText, activeTab === 'torrentio' && styles.activeTabText]}>
{t('debrid.tab_torrentio')}
</Text>
</TouchableOpacity>
</View>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
@ -1625,7 +1005,7 @@ const DebridIntegrationScreen = () => {
/>
}
>
{activeTab === 'torbox' ? renderTorboxTab() : renderTorrentioTab()}
{renderTorboxTab()}
</ScrollView>
</KeyboardAvoidingView>