revamped alert UI

This commit is contained in:
tapframe 2025-12-16 15:24:32 +05:30
parent d876b7618c
commit 59cb902658
3 changed files with 328 additions and 308 deletions

1
.gitignore vendored
View file

@ -86,3 +86,4 @@ expofs.md
ios/sentry.properties ios/sentry.properties
android/sentry.properties android/sentry.properties
Stremio addons refer Stremio addons refer
trakt-docs

View file

@ -15,7 +15,7 @@ import Animated, {
withTiming, withTiming,
} from 'react-native-reanimated'; } from 'react-native-reanimated';
import { useTheme } from '../contexts/ThemeContext'; import { useTheme } from '../contexts/ThemeContext';
import { Portal, Dialog, Button } from 'react-native-paper'; import { Portal } from 'react-native-paper';
interface CustomAlertProps { interface CustomAlertProps {
visible: boolean; visible: boolean;
@ -40,8 +40,8 @@ export const CustomAlert = ({
}: CustomAlertProps) => { }: CustomAlertProps) => {
const opacity = useSharedValue(0); const opacity = useSharedValue(0);
const scale = useSharedValue(0.95); const scale = useSharedValue(0.95);
const isDarkMode = useColorScheme() === 'dark';
const { currentTheme } = useTheme(); const { currentTheme } = useTheme();
// Using hardcoded dark theme values to match SeriesContent modal
const themeColors = currentTheme.colors; const themeColors = currentTheme.colors;
useEffect(() => { useEffect(() => {
@ -68,10 +68,11 @@ export const CustomAlert = ({
const handleActionPress = useCallback((action: { label: string; onPress: () => void; style?: object }) => { const handleActionPress = useCallback((action: { label: string; onPress: () => void; style?: object }) => {
try { try {
action.onPress(); action.onPress();
// Don't auto-close here if the action handles it, or check if we should
// Standard behavior is to close
onClose(); onClose();
} catch (error) { } catch (error) {
console.warn('[CustomAlert] Error in action handler:', error); console.warn('[CustomAlert] Error in action handler:', error);
// Still close the alert even if action fails
onClose(); onClose();
} }
}, [onClose]); }, [onClose]);
@ -91,7 +92,7 @@ export const CustomAlert = ({
<Animated.View <Animated.View
style={[ style={[
styles.overlay, styles.overlay,
{ backgroundColor: 'rgba(0,0,0,0.6)' }, { backgroundColor: 'rgba(0, 0, 0, 0.85)' },
overlayStyle overlayStyle
]} ]}
> >
@ -100,23 +101,22 @@ export const CustomAlert = ({
<Animated.View style={[ <Animated.View style={[
styles.alertContainer, styles.alertContainer,
alertStyle, alertStyle,
{
backgroundColor: themeColors.darkBackground,
borderColor: themeColors.primary,
}
]}> ]}>
{/* Title */} {/* Title */}
<Text style={[styles.title, { color: themeColors.highEmphasis }]}> <Text style={styles.title}>
{title} {title}
</Text> </Text>
{/* Message */} {/* Message */}
<Text style={[styles.message, { color: themeColors.mediumEmphasis }]}> <Text style={styles.message}>
{message} {message}
</Text> </Text>
{/* Actions */} {/* Actions */}
<View style={styles.actionsRow}> <View style={[
styles.actionsRow,
actions.length === 1 && { justifyContent: 'center' }
]}>
{actions.map((action, idx) => { {actions.map((action, idx) => {
const isPrimary = idx === actions.length - 1; const isPrimary = idx === actions.length - 1;
return ( return (
@ -125,9 +125,10 @@ export const CustomAlert = ({
style={[ style={[
styles.actionButton, styles.actionButton,
isPrimary isPrimary
? { ...styles.primaryButton, backgroundColor: themeColors.primary } ? { backgroundColor: themeColors.primary }
: styles.secondaryButton, : styles.secondaryButton,
action.style action.style,
actions.length === 1 && { minWidth: 120, maxWidth: '100%' }
]} ]}
onPress={() => handleActionPress(action)} onPress={() => handleActionPress(action)}
activeOpacity={0.7} activeOpacity={0.7}
@ -135,8 +136,8 @@ export const CustomAlert = ({
<Text style={[ <Text style={[
styles.actionText, styles.actionText,
isPrimary isPrimary
? { color: themeColors.white } ? { color: '#FFFFFF' }
: { color: themeColors.primary } : { color: '#FFFFFF' }
]}> ]}>
{action.label} {action.label}
</Text> </Text>
@ -157,6 +158,7 @@ const styles = StyleSheet.create({
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
zIndex: 9999,
}, },
overlayPressable: { overlayPressable: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,
@ -165,29 +167,32 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 24, paddingHorizontal: 20,
width: '100%',
}, },
alertContainer: { alertContainer: {
width: '100%', width: '100%',
maxWidth: 340, maxWidth: 400,
borderRadius: 24, backgroundColor: '#1E1E1E', // Solid opaque dark background
padding: 28, borderRadius: 16,
padding: 24,
borderWidth: 1, borderWidth: 1,
borderColor: '#007AFF', // iOS blue - will be overridden by theme borderColor: 'rgba(255, 255, 255, 0.1)',
overflow: 'hidden', // Ensure background fills entire card overflow: 'hidden',
...Platform.select({ ...Platform.select({
ios: { ios: {
shadowColor: '#000', shadowColor: '#000',
shadowOffset: { width: 0, height: 8 }, shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.3, shadowOpacity: 0.51,
shadowRadius: 24, shadowRadius: 13.16,
}, },
android: { android: {
elevation: 12, elevation: 20,
}, },
}), }),
}, },
title: { title: {
color: '#FFFFFF',
fontSize: 20, fontSize: 20,
fontWeight: '700', fontWeight: '700',
marginBottom: 8, marginBottom: 8,
@ -195,6 +200,7 @@ const styles = StyleSheet.create({
letterSpacing: 0.2, letterSpacing: 0.2,
}, },
message: { message: {
color: '#AAAAAA',
fontSize: 15, fontSize: 15,
marginBottom: 24, marginBottom: 24,
textAlign: 'center', textAlign: 'center',
@ -209,17 +215,16 @@ const styles = StyleSheet.create({
}, },
actionButton: { actionButton: {
paddingHorizontal: 20, paddingHorizontal: 20,
paddingVertical: 11, paddingVertical: 12,
borderRadius: 12, borderRadius: 12,
minWidth: 80, minWidth: 80,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, flex: 1, // Distribute space
primaryButton: { maxWidth: 200, // But limit width
// Background color set dynamically via theme
}, },
secondaryButton: { secondaryButton: {
backgroundColor: 'rgba(255, 255, 255, 0.1)', backgroundColor: 'rgba(255, 255, 255, 0.08)',
}, },
actionText: { actionText: {
fontSize: 16, fontSize: 16,

View file

@ -78,17 +78,17 @@ const createStyles = (colors: any) => StyleSheet.create({
padding: 16, padding: 16,
}, },
sectionTitle: { sectionTitle: {
fontSize: 20, fontSize: 20,
fontWeight: '600', fontWeight: '600',
color: colors.white, color: colors.white,
marginBottom: 8, marginBottom: 8,
}, },
sectionHeader: { sectionHeader: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
marginBottom: 16, marginBottom: 16,
}, },
sectionDescription: { sectionDescription: {
fontSize: 14, fontSize: 14,
color: colors.mediumGray, color: colors.mediumGray,
@ -283,59 +283,59 @@ const createStyles = (colors: any) => StyleSheet.create({
marginTop: 8, marginTop: 8,
}, },
infoText: { infoText: {
fontSize: 14, fontSize: 14,
color: colors.mediumEmphasis, color: colors.mediumEmphasis,
lineHeight: 20, lineHeight: 20,
}, },
content: { content: {
flex: 1, flex: 1,
}, },
emptyState: { emptyState: {
alignItems: 'center', alignItems: 'center',
paddingVertical: 32, paddingVertical: 32,
}, },
emptyStateTitle: { emptyStateTitle: {
fontSize: 18, fontSize: 18,
fontWeight: '600', fontWeight: '600',
color: colors.white, color: colors.white,
marginTop: 16, marginTop: 16,
marginBottom: 8, marginBottom: 8,
}, },
emptyStateDescription: { emptyStateDescription: {
fontSize: 14, fontSize: 14,
color: colors.mediumGray, color: colors.mediumGray,
textAlign: 'center', textAlign: 'center',
lineHeight: 20, lineHeight: 20,
}, },
scrapersList: { scrapersList: {
gap: 12, gap: 12,
}, },
scrapersContainer: { scrapersContainer: {
marginBottom: 24, marginBottom: 24,
}, },
inputContainer: { inputContainer: {
marginBottom: 16, marginBottom: 16,
}, },
lastSection: { lastSection: {
borderBottomWidth: 0, borderBottomWidth: 0,
}, },
disabledSection: { disabledSection: {
opacity: 0.5, opacity: 0.5,
}, },
disabledText: { disabledText: {
color: colors.elevation3, color: colors.elevation3,
}, },
disabledContainer: { disabledContainer: {
opacity: 0.5, opacity: 0.5,
}, },
disabledInput: { disabledInput: {
backgroundColor: colors.elevation1, backgroundColor: colors.elevation1,
opacity: 0.5, opacity: 0.5,
}, },
disabledButton: { disabledButton: {
opacity: 0.5, opacity: 0.5,
}, },
disabledImage: { disabledImage: {
opacity: 0.3, opacity: 0.3,
}, },
availableIndicator: { availableIndicator: {
@ -484,46 +484,60 @@ const createStyles = (colors: any) => StyleSheet.create({
}, },
modalOverlay: { modalOverlay: {
flex: 1, flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)', backgroundColor: 'rgba(0, 0, 0, 0.85)',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
padding: 20,
}, },
modalContent: { modalContent: {
backgroundColor: colors.darkBackground, backgroundColor: '#1E1E1E', // Match CustomAlert
borderRadius: 16, borderRadius: 16,
padding: 20, padding: 24,
margin: 20, width: '100%',
maxHeight: '70%', maxWidth: 400,
width: screenWidth - 40,
borderWidth: 1, borderWidth: 1,
borderColor: colors.elevation3, borderColor: 'rgba(255, 255, 255, 0.1)',
alignSelf: 'center',
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.51,
shadowRadius: 13.16,
},
android: {
elevation: 20,
},
}),
}, },
modalTitle: { modalTitle: {
fontSize: 18, fontSize: 20,
fontWeight: '600', fontWeight: '700',
color: colors.white, color: '#FFFFFF',
marginBottom: 8, marginBottom: 8,
textAlign: 'center',
}, },
modalText: { modalText: {
fontSize: 16, fontSize: 15,
color: colors.mediumGray, color: '#AAAAAA',
lineHeight: 24, lineHeight: 22,
marginBottom: 16, marginBottom: 16,
textAlign: 'center',
}, },
modalButton: { modalButton: {
backgroundColor: colors.primary, backgroundColor: colors.primary,
paddingVertical: 14, paddingVertical: 12,
paddingHorizontal: 24, paddingHorizontal: 20,
borderRadius: 8, borderRadius: 12,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginTop: 16, marginTop: 16,
minHeight: 48, minHeight: 48,
}, },
modalButtonText: { modalButtonText: {
color: colors.white, color: '#FFFFFF',
fontSize: 16, fontSize: 16,
fontWeight: '500', fontWeight: '600',
}, },
// Compact modal styles // Compact modal styles
modalHeader: { modalHeader: {
@ -842,7 +856,7 @@ const PluginsScreen: React.FC = () => {
) => { ) => {
setAlertTitle(title); setAlertTitle(title);
setAlertMessage(message); setAlertMessage(message);
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]); setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => { } }]);
setAlertVisible(true); setAlertVisible(true);
}; };
@ -1044,7 +1058,7 @@ const PluginsScreen: React.FC = () => {
alertTitle, alertTitle,
alertMessage, alertMessage,
[ [
{ label: 'Cancel', onPress: () => {} }, { label: 'Cancel', onPress: () => { } },
{ {
label: 'Remove', label: 'Remove',
onPress: async () => { onPress: async () => {
@ -1222,7 +1236,7 @@ const PluginsScreen: React.FC = () => {
'Clear All Scrapers', 'Clear All Scrapers',
'Are you sure you want to remove all installed scrapers? This action cannot be undone.', 'Are you sure you want to remove all installed scrapers? This action cannot be undone.',
[ [
{ label: 'Cancel', onPress: () => {} }, { label: 'Cancel', onPress: () => { } },
{ {
label: 'Clear', label: 'Clear',
onPress: async () => { onPress: async () => {
@ -1245,7 +1259,7 @@ const PluginsScreen: React.FC = () => {
'Clear Repository Cache', 'Clear Repository Cache',
'This will remove the saved repository URL and clear all cached scraper data. You will need to re-enter your repository URL.', 'This will remove the saved repository URL and clear all cached scraper data. You will need to re-enter your repository URL.',
[ [
{ label: 'Cancel', onPress: () => {} }, { label: 'Cancel', onPress: () => { } },
{ {
label: 'Clear Cache', label: 'Clear Cache',
onPress: async () => { onPress: async () => {
@ -1467,8 +1481,8 @@ const PluginsScreen: React.FC = () => {
<Text style={styles.repositoryUrl}>{repo.url}</Text> <Text style={styles.repositoryUrl}>{repo.url}</Text>
<Text style={styles.repositoryMeta}> <Text style={styles.repositoryMeta}>
{repo.scraperCount || 0} scrapers Last updated: {repo.lastUpdated ? new Date(repo.lastUpdated).toLocaleDateString() : 'Never'} {repo.scraperCount || 0} scrapers Last updated: {repo.lastUpdated ? new Date(repo.lastUpdated).toLocaleDateString() : 'Never'}
</Text> </Text>
</View> </View>
<View style={styles.repositoryActions}> <View style={styles.repositoryActions}>
{repo.id !== currentRepositoryId && ( {repo.id !== currentRepositoryId && (
<TouchableOpacity <TouchableOpacity
@ -1502,7 +1516,7 @@ const PluginsScreen: React.FC = () => {
<Text style={styles.repositoryActionButtonText}>Remove</Text> <Text style={styles.repositoryActionButtonText}>Remove</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
))} ))}
</View> </View>
)} )}
@ -1541,9 +1555,9 @@ const PluginsScreen: React.FC = () => {
{searchQuery.length > 0 && ( {searchQuery.length > 0 && (
<TouchableOpacity onPress={() => setSearchQuery('')}> <TouchableOpacity onPress={() => setSearchQuery('')}>
<Ionicons name="close-circle" size={20} color={colors.mediumGray} /> <Ionicons name="close-circle" size={20} color={colors.mediumGray} />
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
{/* Filter Chips */} {/* Filter Chips */}
<View style={styles.filterContainer}> <View style={styles.filterContainer}>
@ -1561,7 +1575,7 @@ const PluginsScreen: React.FC = () => {
selectedFilter === filter && styles.filterChipTextSelected selectedFilter === filter && styles.filterChipTextSelected
]}> ]}>
{filter === 'all' ? 'All' : filter === 'movie' ? 'Movies' : 'TV Shows'} {filter === 'all' ? 'All' : filter === 'movie' ? 'Movies' : 'TV Shows'}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
))} ))}
</View> </View>
@ -1598,7 +1612,7 @@ const PluginsScreen: React.FC = () => {
/> />
<Text style={styles.emptyStateTitle}> <Text style={styles.emptyStateTitle}>
{searchQuery ? 'No Scrapers Found' : 'No Scrapers Available'} {searchQuery ? 'No Scrapers Found' : 'No Scrapers Available'}
</Text> </Text>
<Text style={styles.emptyStateDescription}> <Text style={styles.emptyStateDescription}>
{searchQuery {searchQuery
? `No scrapers match "${searchQuery}". Try a different search term.` ? `No scrapers match "${searchQuery}". Try a different search term.`
@ -1613,44 +1627,44 @@ const PluginsScreen: React.FC = () => {
<Text style={styles.secondaryButtonText}>Clear Search</Text> <Text style={styles.secondaryButtonText}>Clear Search</Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
) : ( ) : (
<View style={styles.scrapersContainer}> <View style={styles.scrapersContainer}>
{filteredScrapers.map((scraper) => ( {filteredScrapers.map((scraper) => (
<View key={scraper.id} style={styles.scraperCard}> <View key={scraper.id} style={styles.scraperCard}>
<View style={styles.scraperCardHeader}> <View style={styles.scraperCardHeader}>
{scraper.logo ? ( {scraper.logo ? (
(scraper.logo.toLowerCase().endsWith('.svg') || scraper.logo.toLowerCase().includes('.svg?')) ? ( (scraper.logo.toLowerCase().endsWith('.svg') || scraper.logo.toLowerCase().includes('.svg?')) ? (
<Image <Image
source={{ uri: scraper.logo }} source={{ uri: scraper.logo }}
style={styles.scraperLogo} style={styles.scraperLogo}
resizeMode="contain" resizeMode="contain"
/> />
) : ( ) : (
<FastImage <FastImage
source={{ uri: scraper.logo }} source={{ uri: scraper.logo }}
style={styles.scraperLogo} style={styles.scraperLogo}
resizeMode={FastImage.resizeMode.contain} resizeMode={FastImage.resizeMode.contain}
/> />
) )
) : ( ) : (
<View style={styles.scraperLogo} /> <View style={styles.scraperLogo} />
)} )}
<View style={styles.scraperCardInfo}> <View style={styles.scraperCardInfo}>
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 4, gap: 8 }}> <View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 4, gap: 8 }}>
<Text style={styles.scraperName}>{scraper.name}</Text> <Text style={styles.scraperName}>{scraper.name}</Text>
<StatusBadge status={getScraperStatus(scraper)} colors={colors} /> <StatusBadge status={getScraperStatus(scraper)} colors={colors} />
</View>
<Text style={styles.scraperDescription}>{scraper.description}</Text>
</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 || (scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android'))}
/>
</View> </View>
<Text style={styles.scraperDescription}>{scraper.description}</Text>
</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 || (scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android'))}
/>
</View>
<View style={styles.scraperCardMeta}> <View style={styles.scraperCardMeta}>
<View style={styles.scraperCardMetaItem}> <View style={styles.scraperCardMetaItem}>
@ -1682,62 +1696,62 @@ const PluginsScreen: React.FC = () => {
</View> </View>
{/* ShowBox Settings - only visible when ShowBox scraper is available */} {/* ShowBox Settings - only visible when ShowBox scraper is available */}
{showboxScraperId && scraper.id === showboxScraperId && settings.enableLocalScrapers && ( {showboxScraperId && scraper.id === showboxScraperId && settings.enableLocalScrapers && (
<View style={{ marginTop: 16, paddingTop: 16, borderTopWidth: 1, borderTopColor: colors.elevation3 }}> <View style={{ marginTop: 16, paddingTop: 16, borderTopWidth: 1, borderTopColor: colors.elevation3 }}>
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>ShowBox UI Token</Text> <Text style={[styles.settingTitle, { marginBottom: 8 }]}>ShowBox UI Token</Text>
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12 }}> <View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12 }}>
<TextInput <TextInput
style={[styles.textInput, { flex: 1, marginBottom: 0 }]} style={[styles.textInput, { flex: 1, marginBottom: 0 }]}
value={showboxUiToken} value={showboxUiToken}
onChangeText={setShowboxUiToken} onChangeText={setShowboxUiToken}
placeholder="Paste your ShowBox UI token" placeholder="Paste your ShowBox UI token"
placeholderTextColor={colors.mediumGray} placeholderTextColor={colors.mediumGray}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
secureTextEntry={showboxSavedToken.length > 0 && !showboxTokenVisible} secureTextEntry={showboxSavedToken.length > 0 && !showboxTokenVisible}
multiline={false} multiline={false}
numberOfLines={1} numberOfLines={1}
/> />
{showboxSavedToken.length > 0 && ( {showboxSavedToken.length > 0 && (
<TouchableOpacity onPress={() => setShowboxTokenVisible(v => !v)} accessibilityRole="button" accessibilityLabel={showboxTokenVisible ? 'Hide token' : 'Show token'} style={{ marginLeft: 10 }}> <TouchableOpacity onPress={() => setShowboxTokenVisible(v => !v)} accessibilityRole="button" accessibilityLabel={showboxTokenVisible ? 'Hide token' : 'Show token'} style={{ marginLeft: 10 }}>
<Ionicons name={showboxTokenVisible ? 'eye-off' : 'eye'} size={18} color={colors.primary} /> <Ionicons name={showboxTokenVisible ? 'eye-off' : 'eye'} size={18} color={colors.primary} />
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
<View style={styles.buttonRow}> <View style={styles.buttonRow}>
{showboxUiToken !== showboxSavedToken && ( {showboxUiToken !== showboxSavedToken && (
<TouchableOpacity <TouchableOpacity
style={[styles.button, styles.primaryButton]} style={[styles.button, styles.primaryButton]}
onPress={async () => { onPress={async () => {
if (showboxScraperId) { if (showboxScraperId) {
await pluginService.setScraperSettings(showboxScraperId, { uiToken: showboxUiToken }); await pluginService.setScraperSettings(showboxScraperId, { uiToken: showboxUiToken });
} }
setShowboxSavedToken(showboxUiToken); setShowboxSavedToken(showboxUiToken);
openAlert('Saved', 'ShowBox settings updated'); openAlert('Saved', 'ShowBox settings updated');
}} }}
> >
<Text style={styles.buttonText}>Save</Text> <Text style={styles.buttonText}>Save</Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
<TouchableOpacity <TouchableOpacity
style={[styles.button, styles.secondaryButton]} style={[styles.button, styles.secondaryButton]}
onPress={async () => { onPress={async () => {
setShowboxUiToken(''); setShowboxUiToken('');
setShowboxSavedToken(''); setShowboxSavedToken('');
if (showboxScraperId) { if (showboxScraperId) {
await pluginService.setScraperSettings(showboxScraperId, {}); await pluginService.setScraperSettings(showboxScraperId, {});
} }
}} }}
> >
<Text style={styles.secondaryButtonText}>Clear</Text> <Text style={styles.secondaryButtonText}>Clear</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
)} )}
</View> </View>
))} ))}
</View> </View>
)} )}
</CollapsibleSection> </CollapsibleSection>
{/* Additional Settings */} {/* Additional Settings */}
@ -1772,18 +1786,18 @@ const PluginsScreen: React.FC = () => {
</Text> </Text>
</View> </View>
<Switch <Switch
value={settings.streamDisplayMode === 'grouped'} value={settings.streamDisplayMode === 'grouped'}
onValueChange={(value) => { onValueChange={(value) => {
updateSetting('streamDisplayMode', value ? 'grouped' : 'separate'); updateSetting('streamDisplayMode', value ? 'grouped' : 'separate');
// Auto-disable quality sorting when grouping is disabled // Auto-disable quality sorting when grouping is disabled
if (!value && settings.streamSortMode === 'quality-then-scraper') { if (!value && settings.streamSortMode === 'quality-then-scraper') {
updateSetting('streamSortMode', 'scraper-then-quality'); updateSetting('streamSortMode', 'scraper-then-quality');
} }
}} }}
trackColor={{ false: colors.elevation3, true: colors.primary }} trackColor={{ false: colors.elevation3, true: colors.primary }}
thumbColor={settings.streamDisplayMode === 'grouped' ? colors.white : '#f4f3f4'} thumbColor={settings.streamDisplayMode === 'grouped' ? colors.white : '#f4f3f4'}
disabled={!settings.enableLocalScrapers} disabled={!settings.enableLocalScrapers}
/> />
</View> </View>
<View style={styles.settingRow}> <View style={styles.settingRow}>
@ -1794,12 +1808,12 @@ const PluginsScreen: React.FC = () => {
</Text> </Text>
</View> </View>
<Switch <Switch
value={settings.streamSortMode === 'quality-then-scraper'} value={settings.streamSortMode === 'quality-then-scraper'}
onValueChange={(value) => updateSetting('streamSortMode', value ? 'quality-then-scraper' : 'scraper-then-quality')} onValueChange={(value) => updateSetting('streamSortMode', value ? 'quality-then-scraper' : 'scraper-then-quality')}
trackColor={{ false: colors.elevation3, true: colors.primary }} trackColor={{ false: colors.elevation3, true: colors.primary }}
thumbColor={settings.streamSortMode === 'quality-then-scraper' ? colors.white : '#f4f3f4'} thumbColor={settings.streamSortMode === 'quality-then-scraper' ? colors.white : '#f4f3f4'}
disabled={!settings.enableLocalScrapers || settings.streamDisplayMode !== 'grouped'} disabled={!settings.enableLocalScrapers || settings.streamDisplayMode !== 'grouped'}
/> />
</View> </View>
<View style={styles.settingRow}> <View style={styles.settingRow}>
@ -1810,12 +1824,12 @@ const PluginsScreen: React.FC = () => {
</Text> </Text>
</View> </View>
<Switch <Switch
value={settings.showScraperLogos && settings.enableLocalScrapers} value={settings.showScraperLogos && settings.enableLocalScrapers}
onValueChange={(value) => updateSetting('showScraperLogos', value)} onValueChange={(value) => updateSetting('showScraperLogos', value)}
trackColor={{ false: colors.elevation3, true: colors.primary }} trackColor={{ false: colors.elevation3, true: colors.primary }}
thumbColor={settings.showScraperLogos && settings.enableLocalScrapers ? colors.white : '#f4f3f4'} thumbColor={settings.showScraperLogos && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
disabled={!settings.enableLocalScrapers} disabled={!settings.enableLocalScrapers}
/> />
</View> </View>
</CollapsibleSection> </CollapsibleSection>
@ -1988,36 +2002,36 @@ const PluginsScreen: React.FC = () => {
/> />
{/* Format Hint */} {/* Format Hint */}
<Text style={styles.formatHint}> <Text style={styles.formatHint}>
Format: https://raw.githubusercontent.com/username/repo/refs/heads/branch Format: https://raw.githubusercontent.com/username/repo/refs/heads/branch
</Text> </Text>
{/* Action Buttons */} {/* Action Buttons */}
<View style={styles.compactActions}> <View style={styles.compactActions}>
<TouchableOpacity <TouchableOpacity
style={[styles.compactButton, styles.cancelButton]} style={[styles.compactButton, styles.cancelButton]}
onPress={() => { onPress={() => {
setShowAddRepositoryModal(false); setShowAddRepositoryModal(false);
setNewRepositoryUrl(''); setNewRepositoryUrl('');
}} }}
> >
<Text style={styles.cancelButtonText}>Cancel</Text> <Text style={styles.cancelButtonText}>Cancel</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={[styles.compactButton, styles.addButton, (!newRepositoryUrl.trim() || isLoading) && styles.disabledButton]} style={[styles.compactButton, styles.addButton, (!newRepositoryUrl.trim() || isLoading) && styles.disabledButton]}
onPress={handleAddRepository} onPress={handleAddRepository}
disabled={!newRepositoryUrl.trim() || isLoading} disabled={!newRepositoryUrl.trim() || isLoading}
> >
{isLoading ? ( {isLoading ? (
<ActivityIndicator size="small" color={colors.white} /> <ActivityIndicator size="small" color={colors.white} />
) : ( ) : (
<Text style={styles.addButtonText}>Add</Text> <Text style={styles.addButtonText}>Add</Text>
)} )}
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</ScrollView> </ScrollView>
</View> </View>
</View> </View>
</Modal> </Modal>