mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 16:51:57 +00:00
revamped alert UI
This commit is contained in:
parent
d876b7618c
commit
59cb902658
3 changed files with 328 additions and 308 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -86,3 +86,4 @@ expofs.md
|
|||
ios/sentry.properties
|
||||
android/sentry.properties
|
||||
Stremio addons refer
|
||||
trakt-docs
|
||||
|
|
@ -15,7 +15,7 @@ import Animated, {
|
|||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { Portal, Dialog, Button } from 'react-native-paper';
|
||||
import { Portal } from 'react-native-paper';
|
||||
|
||||
interface CustomAlertProps {
|
||||
visible: boolean;
|
||||
|
|
@ -40,8 +40,8 @@ export const CustomAlert = ({
|
|||
}: CustomAlertProps) => {
|
||||
const opacity = useSharedValue(0);
|
||||
const scale = useSharedValue(0.95);
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
const { currentTheme } = useTheme();
|
||||
// Using hardcoded dark theme values to match SeriesContent modal
|
||||
const themeColors = currentTheme.colors;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -68,10 +68,11 @@ export const CustomAlert = ({
|
|||
const handleActionPress = useCallback((action: { label: string; onPress: () => void; style?: object }) => {
|
||||
try {
|
||||
action.onPress();
|
||||
// Don't auto-close here if the action handles it, or check if we should
|
||||
// Standard behavior is to close
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.warn('[CustomAlert] Error in action handler:', error);
|
||||
// Still close the alert even if action fails
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
|
|
@ -91,7 +92,7 @@ export const CustomAlert = ({
|
|||
<Animated.View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{ backgroundColor: 'rgba(0,0,0,0.6)' },
|
||||
{ backgroundColor: 'rgba(0, 0, 0, 0.85)' },
|
||||
overlayStyle
|
||||
]}
|
||||
>
|
||||
|
|
@ -100,23 +101,22 @@ export const CustomAlert = ({
|
|||
<Animated.View style={[
|
||||
styles.alertContainer,
|
||||
alertStyle,
|
||||
{
|
||||
backgroundColor: themeColors.darkBackground,
|
||||
borderColor: themeColors.primary,
|
||||
}
|
||||
]}>
|
||||
{/* Title */}
|
||||
<Text style={[styles.title, { color: themeColors.highEmphasis }]}>
|
||||
<Text style={styles.title}>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
{/* Message */}
|
||||
<Text style={[styles.message, { color: themeColors.mediumEmphasis }]}>
|
||||
<Text style={styles.message}>
|
||||
{message}
|
||||
</Text>
|
||||
|
||||
{/* Actions */}
|
||||
<View style={styles.actionsRow}>
|
||||
<View style={[
|
||||
styles.actionsRow,
|
||||
actions.length === 1 && { justifyContent: 'center' }
|
||||
]}>
|
||||
{actions.map((action, idx) => {
|
||||
const isPrimary = idx === actions.length - 1;
|
||||
return (
|
||||
|
|
@ -125,9 +125,10 @@ export const CustomAlert = ({
|
|||
style={[
|
||||
styles.actionButton,
|
||||
isPrimary
|
||||
? { ...styles.primaryButton, backgroundColor: themeColors.primary }
|
||||
? { backgroundColor: themeColors.primary }
|
||||
: styles.secondaryButton,
|
||||
action.style
|
||||
action.style,
|
||||
actions.length === 1 && { minWidth: 120, maxWidth: '100%' }
|
||||
]}
|
||||
onPress={() => handleActionPress(action)}
|
||||
activeOpacity={0.7}
|
||||
|
|
@ -135,8 +136,8 @@ export const CustomAlert = ({
|
|||
<Text style={[
|
||||
styles.actionText,
|
||||
isPrimary
|
||||
? { color: themeColors.white }
|
||||
: { color: themeColors.primary }
|
||||
? { color: '#FFFFFF' }
|
||||
: { color: '#FFFFFF' }
|
||||
]}>
|
||||
{action.label}
|
||||
</Text>
|
||||
|
|
@ -157,6 +158,7 @@ const styles = StyleSheet.create({
|
|||
...StyleSheet.absoluteFillObject,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 9999,
|
||||
},
|
||||
overlayPressable: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
|
|
@ -165,29 +167,32 @@ const styles = StyleSheet.create({
|
|||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 24,
|
||||
paddingHorizontal: 20,
|
||||
width: '100%',
|
||||
},
|
||||
alertContainer: {
|
||||
width: '100%',
|
||||
maxWidth: 340,
|
||||
borderRadius: 24,
|
||||
padding: 28,
|
||||
maxWidth: 400,
|
||||
backgroundColor: '#1E1E1E', // Solid opaque dark background
|
||||
borderRadius: 16,
|
||||
padding: 24,
|
||||
borderWidth: 1,
|
||||
borderColor: '#007AFF', // iOS blue - will be overridden by theme
|
||||
overflow: 'hidden', // Ensure background fills entire card
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
overflow: 'hidden',
|
||||
...Platform.select({
|
||||
ios: {
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 8 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 24,
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.51,
|
||||
shadowRadius: 13.16,
|
||||
},
|
||||
android: {
|
||||
elevation: 12,
|
||||
elevation: 20,
|
||||
},
|
||||
}),
|
||||
},
|
||||
title: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 20,
|
||||
fontWeight: '700',
|
||||
marginBottom: 8,
|
||||
|
|
@ -195,6 +200,7 @@ const styles = StyleSheet.create({
|
|||
letterSpacing: 0.2,
|
||||
},
|
||||
message: {
|
||||
color: '#AAAAAA',
|
||||
fontSize: 15,
|
||||
marginBottom: 24,
|
||||
textAlign: 'center',
|
||||
|
|
@ -209,17 +215,16 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
actionButton: {
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 11,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
minWidth: 80,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
primaryButton: {
|
||||
// Background color set dynamically via theme
|
||||
flex: 1, // Distribute space
|
||||
maxWidth: 200, // But limit width
|
||||
},
|
||||
secondaryButton: {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||
},
|
||||
actionText: {
|
||||
fontSize: 16,
|
||||
|
|
|
|||
|
|
@ -78,17 +78,17 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
padding: 16,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginBottom: 8,
|
||||
},
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginBottom: 8,
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
sectionDescription: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumGray,
|
||||
|
|
@ -283,59 +283,59 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
marginTop: 8,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumEmphasis,
|
||||
lineHeight: 20,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
emptyState: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: 32,
|
||||
},
|
||||
emptyStateTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginTop: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
emptyStateDescription: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumGray,
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
},
|
||||
scrapersList: {
|
||||
gap: 12,
|
||||
},
|
||||
scrapersContainer: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
inputContainer: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
lastSection: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
disabledSection: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledText: {
|
||||
color: colors.elevation3,
|
||||
},
|
||||
disabledContainer: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledInput: {
|
||||
backgroundColor: colors.elevation1,
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledButton: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledImage: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumEmphasis,
|
||||
lineHeight: 20,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
emptyState: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: 32,
|
||||
},
|
||||
emptyStateTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginTop: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
emptyStateDescription: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumGray,
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
},
|
||||
scrapersList: {
|
||||
gap: 12,
|
||||
},
|
||||
scrapersContainer: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
inputContainer: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
lastSection: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
disabledSection: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledText: {
|
||||
color: colors.elevation3,
|
||||
},
|
||||
disabledContainer: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledInput: {
|
||||
backgroundColor: colors.elevation1,
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledButton: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledImage: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
availableIndicator: {
|
||||
|
|
@ -484,46 +484,60 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
modalContent: {
|
||||
backgroundColor: colors.darkBackground,
|
||||
backgroundColor: '#1E1E1E', // Match CustomAlert
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
maxHeight: '70%',
|
||||
width: screenWidth - 40,
|
||||
padding: 24,
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
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: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: '700',
|
||||
color: '#FFFFFF',
|
||||
marginBottom: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
modalText: {
|
||||
fontSize: 16,
|
||||
color: colors.mediumGray,
|
||||
lineHeight: 24,
|
||||
fontSize: 15,
|
||||
color: '#AAAAAA',
|
||||
lineHeight: 22,
|
||||
marginBottom: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
modalButton: {
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: 8,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 16,
|
||||
minHeight: 48,
|
||||
},
|
||||
modalButtonText: {
|
||||
color: colors.white,
|
||||
color: '#FFFFFF',
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
fontWeight: '600',
|
||||
},
|
||||
// Compact modal styles
|
||||
modalHeader: {
|
||||
|
|
@ -842,7 +856,7 @@ const PluginsScreen: React.FC = () => {
|
|||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => { } }]);
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
|
|
@ -1044,7 +1058,7 @@ const PluginsScreen: React.FC = () => {
|
|||
alertTitle,
|
||||
alertMessage,
|
||||
[
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{ label: 'Cancel', onPress: () => { } },
|
||||
{
|
||||
label: 'Remove',
|
||||
onPress: async () => {
|
||||
|
|
@ -1222,7 +1236,7 @@ const PluginsScreen: React.FC = () => {
|
|||
'Clear All Scrapers',
|
||||
'Are you sure you want to remove all installed scrapers? This action cannot be undone.',
|
||||
[
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{ label: 'Cancel', onPress: () => { } },
|
||||
{
|
||||
label: 'Clear',
|
||||
onPress: async () => {
|
||||
|
|
@ -1245,7 +1259,7 @@ const PluginsScreen: React.FC = () => {
|
|||
'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.',
|
||||
[
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{ label: 'Cancel', onPress: () => { } },
|
||||
{
|
||||
label: 'Clear Cache',
|
||||
onPress: async () => {
|
||||
|
|
@ -1467,8 +1481,8 @@ const PluginsScreen: React.FC = () => {
|
|||
<Text style={styles.repositoryUrl}>{repo.url}</Text>
|
||||
<Text style={styles.repositoryMeta}>
|
||||
{repo.scraperCount || 0} scrapers • Last updated: {repo.lastUpdated ? new Date(repo.lastUpdated).toLocaleDateString() : 'Never'}
|
||||
</Text>
|
||||
</View>
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.repositoryActions}>
|
||||
{repo.id !== currentRepositoryId && (
|
||||
<TouchableOpacity
|
||||
|
|
@ -1502,7 +1516,7 @@ const PluginsScreen: React.FC = () => {
|
|||
<Text style={styles.repositoryActionButtonText}>Remove</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
|
@ -1541,9 +1555,9 @@ const PluginsScreen: React.FC = () => {
|
|||
{searchQuery.length > 0 && (
|
||||
<TouchableOpacity onPress={() => setSearchQuery('')}>
|
||||
<Ionicons name="close-circle" size={20} color={colors.mediumGray} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Filter Chips */}
|
||||
<View style={styles.filterContainer}>
|
||||
|
|
@ -1561,7 +1575,7 @@ const PluginsScreen: React.FC = () => {
|
|||
selectedFilter === filter && styles.filterChipTextSelected
|
||||
]}>
|
||||
{filter === 'all' ? 'All' : filter === 'movie' ? 'Movies' : 'TV Shows'}
|
||||
</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
|
@ -1598,7 +1612,7 @@ const PluginsScreen: React.FC = () => {
|
|||
/>
|
||||
<Text style={styles.emptyStateTitle}>
|
||||
{searchQuery ? 'No Scrapers Found' : 'No Scrapers Available'}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={styles.emptyStateDescription}>
|
||||
{searchQuery
|
||||
? `No scrapers match "${searchQuery}". Try a different search term.`
|
||||
|
|
@ -1613,44 +1627,44 @@ const PluginsScreen: React.FC = () => {
|
|||
<Text style={styles.secondaryButtonText}>Clear Search</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.scrapersContainer}>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.scrapersContainer}>
|
||||
{filteredScrapers.map((scraper) => (
|
||||
<View key={scraper.id} style={styles.scraperCard}>
|
||||
<View style={styles.scraperCardHeader}>
|
||||
{scraper.logo ? (
|
||||
(scraper.logo.toLowerCase().endsWith('.svg') || scraper.logo.toLowerCase().includes('.svg?')) ? (
|
||||
<Image
|
||||
source={{ uri: scraper.logo }}
|
||||
style={styles.scraperLogo}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<FastImage
|
||||
source={{ uri: scraper.logo }}
|
||||
style={styles.scraperLogo}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
{scraper.logo ? (
|
||||
(scraper.logo.toLowerCase().endsWith('.svg') || scraper.logo.toLowerCase().includes('.svg?')) ? (
|
||||
<Image
|
||||
source={{ uri: scraper.logo }}
|
||||
style={styles.scraperLogo}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<FastImage
|
||||
source={{ uri: scraper.logo }}
|
||||
style={styles.scraperLogo}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<View style={styles.scraperLogo} />
|
||||
)}
|
||||
<View style={styles.scraperCardInfo}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 4, gap: 8 }}>
|
||||
<Text style={styles.scraperName}>{scraper.name}</Text>
|
||||
<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>
|
||||
<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.scraperCardMetaItem}>
|
||||
|
|
@ -1682,62 +1696,62 @@ const PluginsScreen: React.FC = () => {
|
|||
</View>
|
||||
|
||||
{/* 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 }}>
|
||||
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>ShowBox UI Token</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12 }}>
|
||||
<TextInput
|
||||
style={[styles.textInput, { flex: 1, marginBottom: 0 }]}
|
||||
value={showboxUiToken}
|
||||
onChangeText={setShowboxUiToken}
|
||||
placeholder="Paste your ShowBox UI token"
|
||||
placeholderTextColor={colors.mediumGray}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
secureTextEntry={showboxSavedToken.length > 0 && !showboxTokenVisible}
|
||||
multiline={false}
|
||||
numberOfLines={1}
|
||||
/>
|
||||
{showboxSavedToken.length > 0 && (
|
||||
<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} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.buttonRow}>
|
||||
{showboxUiToken !== showboxSavedToken && (
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.primaryButton]}
|
||||
onPress={async () => {
|
||||
if (showboxScraperId) {
|
||||
await pluginService.setScraperSettings(showboxScraperId, { uiToken: showboxUiToken });
|
||||
}
|
||||
setShowboxSavedToken(showboxUiToken);
|
||||
openAlert('Saved', 'ShowBox settings updated');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.buttonText}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.secondaryButton]}
|
||||
onPress={async () => {
|
||||
setShowboxUiToken('');
|
||||
setShowboxSavedToken('');
|
||||
if (showboxScraperId) {
|
||||
await pluginService.setScraperSettings(showboxScraperId, {});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Clear</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>ShowBox UI Token</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12 }}>
|
||||
<TextInput
|
||||
style={[styles.textInput, { flex: 1, marginBottom: 0 }]}
|
||||
value={showboxUiToken}
|
||||
onChangeText={setShowboxUiToken}
|
||||
placeholder="Paste your ShowBox UI token"
|
||||
placeholderTextColor={colors.mediumGray}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
secureTextEntry={showboxSavedToken.length > 0 && !showboxTokenVisible}
|
||||
multiline={false}
|
||||
numberOfLines={1}
|
||||
/>
|
||||
{showboxSavedToken.length > 0 && (
|
||||
<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} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.buttonRow}>
|
||||
{showboxUiToken !== showboxSavedToken && (
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.primaryButton]}
|
||||
onPress={async () => {
|
||||
if (showboxScraperId) {
|
||||
await pluginService.setScraperSettings(showboxScraperId, { uiToken: showboxUiToken });
|
||||
}
|
||||
setShowboxSavedToken(showboxUiToken);
|
||||
openAlert('Saved', 'ShowBox settings updated');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.buttonText}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.secondaryButton]}
|
||||
onPress={async () => {
|
||||
setShowboxUiToken('');
|
||||
setShowboxSavedToken('');
|
||||
if (showboxScraperId) {
|
||||
await pluginService.setScraperSettings(showboxScraperId, {});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Clear</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Additional Settings */}
|
||||
|
|
@ -1772,18 +1786,18 @@ const PluginsScreen: React.FC = () => {
|
|||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={settings.streamDisplayMode === 'grouped'}
|
||||
onValueChange={(value) => {
|
||||
updateSetting('streamDisplayMode', value ? 'grouped' : 'separate');
|
||||
// Auto-disable quality sorting when grouping is disabled
|
||||
if (!value && settings.streamSortMode === 'quality-then-scraper') {
|
||||
updateSetting('streamSortMode', 'scraper-then-quality');
|
||||
}
|
||||
}}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.streamDisplayMode === 'grouped' ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
value={settings.streamDisplayMode === 'grouped'}
|
||||
onValueChange={(value) => {
|
||||
updateSetting('streamDisplayMode', value ? 'grouped' : 'separate');
|
||||
// Auto-disable quality sorting when grouping is disabled
|
||||
if (!value && settings.streamSortMode === 'quality-then-scraper') {
|
||||
updateSetting('streamSortMode', 'scraper-then-quality');
|
||||
}
|
||||
}}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.streamDisplayMode === 'grouped' ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.settingRow}>
|
||||
|
|
@ -1794,12 +1808,12 @@ const PluginsScreen: React.FC = () => {
|
|||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={settings.streamSortMode === 'quality-then-scraper'}
|
||||
onValueChange={(value) => updateSetting('streamSortMode', value ? 'quality-then-scraper' : 'scraper-then-quality')}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.streamSortMode === 'quality-then-scraper' ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers || settings.streamDisplayMode !== 'grouped'}
|
||||
/>
|
||||
value={settings.streamSortMode === 'quality-then-scraper'}
|
||||
onValueChange={(value) => updateSetting('streamSortMode', value ? 'quality-then-scraper' : 'scraper-then-quality')}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.streamSortMode === 'quality-then-scraper' ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers || settings.streamDisplayMode !== 'grouped'}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.settingRow}>
|
||||
|
|
@ -1810,12 +1824,12 @@ const PluginsScreen: React.FC = () => {
|
|||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={settings.showScraperLogos && settings.enableLocalScrapers}
|
||||
onValueChange={(value) => updateSetting('showScraperLogos', value)}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.showScraperLogos && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
value={settings.showScraperLogos && settings.enableLocalScrapers}
|
||||
onValueChange={(value) => updateSetting('showScraperLogos', value)}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.showScraperLogos && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
</View>
|
||||
</CollapsibleSection>
|
||||
|
||||
|
|
@ -1988,36 +2002,36 @@ const PluginsScreen: React.FC = () => {
|
|||
/>
|
||||
|
||||
|
||||
{/* Format Hint */}
|
||||
<Text style={styles.formatHint}>
|
||||
Format: https://raw.githubusercontent.com/username/repo/refs/heads/branch
|
||||
</Text>
|
||||
{/* 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.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
{/* Action Buttons */}
|
||||
<View style={styles.compactActions}>
|
||||
<TouchableOpacity
|
||||
style={[styles.compactButton, styles.cancelButton]}
|
||||
onPress={() => {
|
||||
setShowAddRepositoryModal(false);
|
||||
setNewRepositoryUrl('');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.compactButton, styles.addButton, (!newRepositoryUrl.trim() || isLoading) && styles.disabledButton]}
|
||||
onPress={handleAddRepository}
|
||||
disabled={!newRepositoryUrl.trim() || isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
) : (
|
||||
<Text style={styles.addButtonText}>Add</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<TouchableOpacity
|
||||
style={[styles.compactButton, styles.addButton, (!newRepositoryUrl.trim() || isLoading) && styles.disabledButton]}
|
||||
onPress={handleAddRepository}
|
||||
disabled={!newRepositoryUrl.trim() || isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
) : (
|
||||
<Text style={styles.addButtonText}>Add</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
|
|
|||
Loading…
Reference in a new issue