mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-23 11:07:44 +00:00
Updated all Alert.alert to the new custom one
This commit is contained in:
parent
8d60bff989
commit
46b8173b41
15 changed files with 533 additions and 193 deletions
|
|
@ -7,16 +7,16 @@ import {
|
|||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { Image } from 'expo-image';
|
||||
import { useNavigation, StackActions } from '@react-navigation/native';
|
||||
import { NavigationProp } from '@react-navigation/native';
|
||||
import { RootStackParamList } from '../../navigation/AppNavigator';
|
||||
import { StreamingContent } from '../../types/metadata';
|
||||
import { StreamingContent } from '../../services/catalogService';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import { TMDBService } from '../../services/tmdbService';
|
||||
import { catalogService } from '../../services/catalogService';
|
||||
import CustomAlert from '../../components/CustomAlert';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
|
|
@ -59,6 +59,11 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
|
|||
const { currentTheme } = useTheme();
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
|
||||
const [alertVisible, setAlertVisible] = React.useState(false);
|
||||
const [alertTitle, setAlertTitle] = React.useState('');
|
||||
const [alertMessage, setAlertMessage] = React.useState('');
|
||||
const [alertActions, setAlertActions] = React.useState<any[]>([]);
|
||||
|
||||
const handleItemPress = async (item: StreamingContent) => {
|
||||
try {
|
||||
// Extract TMDB ID from the tmdb:123456 format
|
||||
|
|
@ -80,11 +85,10 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error navigating to recommendation:', error);
|
||||
Alert.alert(
|
||||
'Error',
|
||||
'Unable to load this content. Please try again later.',
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
setAlertTitle('Error');
|
||||
setAlertMessage('Unable to load this content. Please try again later.');
|
||||
setAlertActions([{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -128,6 +132,13 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
|
|||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.listContentContainer}
|
||||
/>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
@ -169,4 +180,4 @@ const styles = StyleSheet.create({
|
|||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Alert, Platform } from 'react-native';
|
||||
import { Platform } from 'react-native';
|
||||
import { toast, ToastPosition } from '@backpackapp-io/react-native-toast';
|
||||
import UpdateService, { UpdateInfo } from '../services/updateService';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
|
@ -78,19 +78,19 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
// The app will automatically reload with the new version
|
||||
console.log('Update installed successfully');
|
||||
} else {
|
||||
Alert.alert(
|
||||
'Update Failed',
|
||||
'Unable to install the update. Please try again later or check your internet connection.'
|
||||
);
|
||||
toast('Unable to install the update. Please try again later or check your internet connection.', {
|
||||
duration: 3000,
|
||||
position: ToastPosition.TOP,
|
||||
});
|
||||
// Show popup again after failed installation
|
||||
setShowUpdatePopup(true);
|
||||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error installing update:', error);
|
||||
Alert.alert(
|
||||
'Update Error',
|
||||
'An error occurred while installing the update. Please try again later.'
|
||||
);
|
||||
toast('An error occurred while installing the update. Please try again later.', {
|
||||
duration: 3000,
|
||||
position: ToastPosition.TOP,
|
||||
});
|
||||
// Show popup again after error
|
||||
setShowUpdatePopup(true);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ import {
|
|||
Platform,
|
||||
Dimensions,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Keyboard,
|
||||
} from 'react-native';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
// Removed duplicate AIChatScreen definition and alert state at the top. The correct component is defined after SuggestionChip.
|
||||
import { useRoute, useNavigation, RouteProp, useFocusEffect } from '@react-navigation/native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
|
|
@ -297,6 +298,34 @@ const SuggestionChip: React.FC<SuggestionChipProps> = React.memo(({ text, onPres
|
|||
}, (prev, next) => prev.text === next.text && prev.onPress === next.onPress);
|
||||
|
||||
const AIChatScreen: React.FC = () => {
|
||||
// CustomAlert state
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([
|
||||
{ label: 'OK', onPress: () => setAlertVisible(false) },
|
||||
]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress?: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
if (actions && actions.length > 0) {
|
||||
setAlertActions(
|
||||
actions.map(a => ({
|
||||
label: a.label,
|
||||
style: a.style,
|
||||
onPress: () => { a.onPress?.(); setAlertVisible(false); },
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]);
|
||||
}
|
||||
setAlertVisible(true);
|
||||
};
|
||||
const route = useRoute<AIChatScreenRouteProp>();
|
||||
const navigation = useNavigation();
|
||||
const { currentTheme } = useTheme();
|
||||
|
|
@ -438,9 +467,17 @@ const AIChatScreen: React.FC = () => {
|
|||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error loading context:', error);
|
||||
Alert.alert('Error', 'Failed to load content details for AI chat');
|
||||
openAlert('Error', 'Failed to load content details for AI chat');
|
||||
} finally {
|
||||
setIsLoadingContext(false);
|
||||
{/* CustomAlert at root */}
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
actions={alertActions}
|
||||
/>
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -786,6 +823,13 @@ const AIChatScreen: React.FC = () => {
|
|||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
actions={alertActions}
|
||||
/>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import {
|
|||
StatusBar,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
Linking,
|
||||
Platform,
|
||||
Dimensions,
|
||||
Switch,
|
||||
} from 'react-native';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
|
|
@ -26,6 +26,34 @@ const { width } = Dimensions.get('window');
|
|||
const isTablet = width >= 768;
|
||||
|
||||
const AISettingsScreen: React.FC = () => {
|
||||
// CustomAlert state (must be inside the component)
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([
|
||||
{ label: 'OK', onPress: () => setAlertVisible(false) },
|
||||
]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress?: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
if (actions && actions.length > 0) {
|
||||
setAlertActions(
|
||||
actions.map(a => ({
|
||||
label: a.label,
|
||||
style: a.style,
|
||||
onPress: () => { a.onPress?.(); },
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]);
|
||||
}
|
||||
setAlertVisible(true);
|
||||
};
|
||||
const navigation = useNavigation();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
|
@ -64,12 +92,12 @@ const AISettingsScreen: React.FC = () => {
|
|||
|
||||
const handleSaveApiKey = async () => {
|
||||
if (!apiKey.trim()) {
|
||||
Alert.alert('Error', 'Please enter a valid API key');
|
||||
openAlert('Error', 'Please enter a valid API key');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiKey.startsWith('sk-or-')) {
|
||||
Alert.alert('Error', 'OpenRouter API keys should start with "sk-or-"');
|
||||
openAlert('Error', 'OpenRouter API keys should start with "sk-or-"');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -77,9 +105,9 @@ const AISettingsScreen: React.FC = () => {
|
|||
try {
|
||||
await AsyncStorage.setItem('openrouter_api_key', apiKey.trim());
|
||||
setIsKeySet(true);
|
||||
Alert.alert('Success', 'OpenRouter API key saved successfully!');
|
||||
openAlert('Success', 'OpenRouter API key saved successfully!');
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Failed to save API key');
|
||||
openAlert('Error', 'Failed to save API key');
|
||||
if (__DEV__) console.error('Error saving OpenRouter API key:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
@ -87,22 +115,21 @@ const AISettingsScreen: React.FC = () => {
|
|||
};
|
||||
|
||||
const handleRemoveApiKey = () => {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Remove API Key',
|
||||
'Are you sure you want to remove your OpenRouter API key? This will disable AI chat features.',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: 'Remove',
|
||||
style: 'destructive',
|
||||
label: 'Remove',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.removeItem('openrouter_api_key');
|
||||
setApiKey('');
|
||||
setIsKeySet(false);
|
||||
Alert.alert('Success', 'API key removed successfully');
|
||||
openAlert('Success', 'API key removed successfully');
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Failed to remove API key');
|
||||
openAlert('Error', 'Failed to remove API key');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -115,7 +142,7 @@ const AISettingsScreen: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
|
||||
{/* Header */}
|
||||
|
|
@ -344,6 +371,13 @@ const AISettingsScreen: React.FC = () => {
|
|||
<SvgXml xml={OPENROUTER_SVG.replace(/CURRENTCOLOR/g, currentTheme.colors.mediumEmphasis)} width={180} height={60} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
actions={alertActions}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Alert, StatusBar, Platform, Animated, Easing, TextInput, ActivityIndicator } from 'react-native';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, StatusBar, Platform, Animated, Easing, TextInput, ActivityIndicator } from 'react-native';
|
||||
import { Image } from 'expo-image';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
|
@ -8,6 +8,7 @@ import { useAccount } from '../contexts/AccountContext';
|
|||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
const AccountManageScreen: React.FC = () => {
|
||||
const navigation = useNavigation();
|
||||
|
|
@ -34,6 +35,10 @@ const AccountManageScreen: React.FC = () => {
|
|||
const [avatarUrl, setAvatarUrl] = useState(user?.avatarUrl || '');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [avatarError, setAvatarError] = useState(false);
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Reset image error state when URL changes
|
||||
|
|
@ -45,31 +50,32 @@ const AccountManageScreen: React.FC = () => {
|
|||
setSaving(true);
|
||||
const err = await updateProfile({ displayName: displayName.trim() || undefined, avatarUrl: avatarUrl.trim() || undefined });
|
||||
if (err) {
|
||||
Alert.alert('Error', err);
|
||||
setAlertTitle('Error');
|
||||
setAlertMessage(err);
|
||||
setAlertActions([{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertVisible(true);
|
||||
}
|
||||
setSaving(false);
|
||||
};
|
||||
|
||||
const handleSignOut = () => {
|
||||
Alert.alert(
|
||||
'Sign out',
|
||||
'Are you sure you want to sign out?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Sign out',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await signOut();
|
||||
// Navigate back to root after sign out
|
||||
// @ts-ignore
|
||||
navigation.goBack();
|
||||
} catch (_) {}
|
||||
},
|
||||
setAlertTitle('Sign out');
|
||||
setAlertMessage('Are you sure you want to sign out?');
|
||||
setAlertActions([
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
label: 'Sign out',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await signOut();
|
||||
// @ts-ignore
|
||||
navigation.goBack();
|
||||
} catch (_) {}
|
||||
},
|
||||
]
|
||||
);
|
||||
style: { opacity: 1 },
|
||||
},
|
||||
]);
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -207,6 +213,13 @@ const AccountManageScreen: React.FC = () => {
|
|||
<Text style={styles.signOutText}>Sign out</Text>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
ActivityIndicator,
|
||||
Dimensions,
|
||||
Platform,
|
||||
Alert,
|
||||
FlatList,
|
||||
} from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
|
|
@ -35,6 +34,7 @@ import { NavigationProp } from '@react-navigation/native';
|
|||
import { RootStackParamList } from '../navigation/AppNavigator';
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { StackActions } from '@react-navigation/native';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
const isTablet = width >= 768;
|
||||
|
|
@ -71,6 +71,10 @@ const CastMoviesScreen: React.FC = () => {
|
|||
const scrollY = useSharedValue(0);
|
||||
const [displayLimit, setDisplayLimit] = useState(30); // Start with fewer items for performance
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (castMember) {
|
||||
|
|
@ -253,7 +257,7 @@ const CastMoviesScreen: React.FC = () => {
|
|||
if (__DEV__) console.warn('Stremio ID is null/undefined for movie:', movie.title);
|
||||
throw new Error('Could not find Stremio ID');
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: any) {
|
||||
if (__DEV__) {
|
||||
console.error('=== Error in handleMoviePress ===');
|
||||
console.error('Movie:', movie.title);
|
||||
|
|
@ -261,12 +265,10 @@ const CastMoviesScreen: React.FC = () => {
|
|||
console.error('Error message:', error.message);
|
||||
console.error('Error stack:', error.stack);
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Error',
|
||||
`Unable to load "${movie.title}". Please try again later.`,
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
setAlertTitle('Error');
|
||||
setAlertMessage(`Unable to load "${movie.title}". Please try again later.`);
|
||||
setAlertActions([{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -810,8 +812,18 @@ const CastMoviesScreen: React.FC = () => {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Inject CustomAlert component to display errors */}
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CastMoviesScreen;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {
|
|||
TextInput,
|
||||
Pressable,
|
||||
Button,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
|
@ -25,6 +24,7 @@ import { useCatalogContext } from '../contexts/CatalogContext';
|
|||
import { logger } from '../utils/logger';
|
||||
import { clearCustomNameCache } from '../utils/catalogNameUtils';
|
||||
import { BlurView } from 'expo-blur';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
interface CatalogSetting {
|
||||
addonId: string;
|
||||
|
|
@ -264,6 +264,10 @@ const CatalogSettingsScreen = () => {
|
|||
const [isRenameModalVisible, setIsRenameModalVisible] = useState(false);
|
||||
const [catalogToRename, setCatalogToRename] = useState<CatalogSetting | null>(null);
|
||||
const [currentRenameValue, setCurrentRenameValue] = useState('');
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<any[]>([]);
|
||||
|
||||
// Load saved settings and available catalogs
|
||||
const loadSettings = useCallback(async () => {
|
||||
|
|
@ -465,7 +469,10 @@ const CatalogSettingsScreen = () => {
|
|||
|
||||
} catch (error) {
|
||||
logger.error('Failed to save custom catalog name:', error);
|
||||
Alert.alert('Error', 'Could not save the custom name.'); // Inform user
|
||||
setAlertTitle('Error');
|
||||
setAlertMessage('Could not save the custom name.');
|
||||
setAlertActions([{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertVisible(true);
|
||||
} finally {
|
||||
setIsRenameModalVisible(false);
|
||||
setCatalogToRename(null);
|
||||
|
|
@ -688,8 +695,15 @@ const CatalogSettingsScreen = () => {
|
|||
)}
|
||||
</Modal>
|
||||
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default CatalogSettingsScreen;
|
||||
export default CatalogSettingsScreen;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
Switch,
|
||||
SafeAreaView,
|
||||
Image,
|
||||
Alert,
|
||||
StatusBar,
|
||||
Platform,
|
||||
ActivityIndicator,
|
||||
|
|
@ -21,6 +20,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|||
import { TMDBService } from '../services/tmdbService';
|
||||
import { logger } from '../utils/logger';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
// TMDB API key - since the default key might be private in the service, we'll use our own
|
||||
const TMDB_API_KEY = '439c478a771f35c05022f9feabcca01c';
|
||||
|
|
@ -358,7 +358,36 @@ const LogoSourceSettings = () => {
|
|||
const { currentTheme } = useTheme();
|
||||
const colors = currentTheme.colors;
|
||||
const styles = createStyles(colors);
|
||||
|
||||
|
||||
// CustomAlert state
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([
|
||||
{ label: 'OK', onPress: () => setAlertVisible(false) },
|
||||
]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress?: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
if (actions && actions.length > 0) {
|
||||
setAlertActions(
|
||||
actions.map(a => ({
|
||||
label: a.label,
|
||||
style: a.style,
|
||||
onPress: () => { a.onPress?.(); },
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]);
|
||||
}
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
// Get current preference
|
||||
const [logoSource, setLogoSource] = useState<'metahub' | 'tmdb'>(
|
||||
settings.logoSourcePreference || 'metahub'
|
||||
|
|
@ -560,10 +589,9 @@ const LogoSourceSettings = () => {
|
|||
}
|
||||
|
||||
// Show confirmation alert
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Settings Updated',
|
||||
`Logo and background source preference set to ${source === 'metahub' ? 'Metahub' : 'TMDB'}. Changes will apply when you navigate to content.`,
|
||||
[{ text: 'OK' }]
|
||||
`Logo and background source preference set to ${source === 'metahub' ? 'Metahub' : 'TMDB'}. Changes will apply when you navigate to content.`
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -599,19 +627,17 @@ const LogoSourceSettings = () => {
|
|||
await AsyncStorage.removeItem('_last_logos_');
|
||||
|
||||
// Show confirmation toast or feedback
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'TMDB Language Updated',
|
||||
`TMDB logo language preference set to ${languageCode.toUpperCase()}. Changes will apply when you navigate to content.`,
|
||||
[{ text: 'OK' }]
|
||||
`TMDB logo language preference set to ${languageCode.toUpperCase()}. Changes will apply when you navigate to content.`
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(`[LogoSourceSettings] Error in saveLanguagePreference:`, e);
|
||||
|
||||
// Show error notification
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Error Saving Preference',
|
||||
'There was a problem saving your language preference. Please try again.',
|
||||
[{ text: 'OK' }]
|
||||
'There was a problem saving your language preference. Please try again.'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -692,7 +718,6 @@ const LogoSourceSettings = () => {
|
|||
return (
|
||||
<SafeAreaView style={[styles.container]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity
|
||||
|
|
@ -703,14 +728,11 @@ const LogoSourceSettings = () => {
|
|||
<MaterialIcons name="arrow-back" size={24} color={colors.white} />
|
||||
<Text style={styles.backText}>Settings</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.headerActions}>
|
||||
{/* Empty for now, but ready for future actions */}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.headerTitle}>Logo Source</Text>
|
||||
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
|
|
@ -866,6 +888,13 @@ const LogoSourceSettings = () => {
|
|||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
actions={alertActions}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import {
|
|||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
Switch,
|
||||
TextInput,
|
||||
ScrollView,
|
||||
|
|
@ -16,6 +15,7 @@ import {
|
|||
Dimensions,
|
||||
Animated,
|
||||
} from 'react-native';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
import { Image } from 'expo-image';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
|
|
@ -826,6 +826,23 @@ const PluginsScreen: React.FC = () => {
|
|||
const colors = currentTheme.colors;
|
||||
const styles = createStyles(colors);
|
||||
|
||||
// CustomAlert state
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
// Core state
|
||||
const [repositoryUrl, setRepositoryUrl] = useState(settings.scraperRepositoryUrl);
|
||||
const [installedScrapers, setInstalledScrapers] = useState<ScraperInfo[]>([]);
|
||||
|
|
@ -915,10 +932,10 @@ const PluginsScreen: React.FC = () => {
|
|||
);
|
||||
await Promise.all(promises);
|
||||
await loadScrapers();
|
||||
Alert.alert('Success', `${enabled ? 'Enabled' : 'Disabled'} ${filteredScrapers.length} scrapers`);
|
||||
openAlert('Success', `${enabled ? 'Enabled' : 'Disabled'} ${filteredScrapers.length} scrapers`);
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to bulk toggle:', error);
|
||||
Alert.alert('Error', 'Failed to update scrapers');
|
||||
openAlert('Error', 'Failed to update scrapers');
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
|
|
@ -930,14 +947,14 @@ const PluginsScreen: React.FC = () => {
|
|||
|
||||
const handleAddRepository = async () => {
|
||||
if (!newRepositoryUrl.trim()) {
|
||||
Alert.alert('Error', 'Please enter a valid repository URL');
|
||||
openAlert('Error', 'Please enter a valid repository URL');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
const url = newRepositoryUrl.trim();
|
||||
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'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'
|
||||
);
|
||||
|
|
@ -956,7 +973,7 @@ const PluginsScreen: React.FC = () => {
|
|||
|
||||
// Additional validation for normalized URL
|
||||
if (!normalizedUrl.endsWith('/refs/heads/') && !normalizedUrl.includes('/refs/heads/')) {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Invalid Repository Structure',
|
||||
'The URL should point to a GitHub repository branch.\n\nExpected format:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch'
|
||||
);
|
||||
|
|
@ -981,10 +998,10 @@ const PluginsScreen: React.FC = () => {
|
|||
|
||||
setNewRepositoryUrl('');
|
||||
setShowAddRepositoryModal(false);
|
||||
Alert.alert('Success', 'Repository added and refreshed successfully');
|
||||
openAlert('Success', 'Repository added and refreshed successfully');
|
||||
} catch (error) {
|
||||
logger.error('[PluginsScreen] Failed to add repository:', error);
|
||||
Alert.alert('Error', 'Failed to add repository');
|
||||
openAlert('Error', 'Failed to add repository');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -996,10 +1013,10 @@ const PluginsScreen: React.FC = () => {
|
|||
await localScraperService.setCurrentRepository(repoId);
|
||||
await loadRepositories();
|
||||
await loadScrapers();
|
||||
Alert.alert('Success', 'Repository switched successfully');
|
||||
openAlert('Success', 'Repository switched successfully');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to switch repository:', error);
|
||||
Alert.alert('Error', 'Failed to switch repository');
|
||||
openAlert('Error', 'Failed to switch repository');
|
||||
} finally {
|
||||
setSwitchingRepository(null);
|
||||
}
|
||||
|
|
@ -1017,14 +1034,13 @@ const PluginsScreen: React.FC = () => {
|
|||
? `Are you sure you want to remove "${repo.name}"? This is your only repository, so you'll have no scrapers available until you add a new repository.`
|
||||
: `Are you sure you want to remove "${repo.name}"? This will also remove all scrapers from this repository.`;
|
||||
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
alertTitle,
|
||||
alertMessage,
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: 'Remove',
|
||||
style: 'destructive',
|
||||
label: 'Remove',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await localScraperService.removeRepository(repoId);
|
||||
|
|
@ -1033,10 +1049,10 @@ const PluginsScreen: React.FC = () => {
|
|||
const successMessage = isLastRepository
|
||||
? 'Repository removed successfully. You can add a new repository using the "Add Repository" button.'
|
||||
: 'Repository removed successfully';
|
||||
Alert.alert('Success', successMessage);
|
||||
openAlert('Success', successMessage);
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to remove repository:', error);
|
||||
Alert.alert('Error', error instanceof Error ? error.message : 'Failed to remove repository');
|
||||
openAlert('Error', error instanceof Error ? error.message : 'Failed to remove repository');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -1102,14 +1118,14 @@ const PluginsScreen: React.FC = () => {
|
|||
|
||||
const handleSaveRepository = async () => {
|
||||
if (!repositoryUrl.trim()) {
|
||||
Alert.alert('Error', 'Please enter a valid repository URL');
|
||||
openAlert('Error', 'Please enter a valid repository URL');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
const url = repositoryUrl.trim();
|
||||
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'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'
|
||||
);
|
||||
|
|
@ -1121,10 +1137,10 @@ const PluginsScreen: React.FC = () => {
|
|||
await localScraperService.setRepositoryUrl(url);
|
||||
await updateSetting('scraperRepositoryUrl', url);
|
||||
setHasRepository(true);
|
||||
Alert.alert('Success', 'Repository URL saved successfully');
|
||||
openAlert('Success', 'Repository URL saved successfully');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to save repository:', error);
|
||||
Alert.alert('Error', 'Failed to save repository URL');
|
||||
openAlert('Error', 'Failed to save repository URL');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -1132,7 +1148,7 @@ const PluginsScreen: React.FC = () => {
|
|||
|
||||
const handleRefreshRepository = async () => {
|
||||
if (!repositoryUrl.trim()) {
|
||||
Alert.alert('Error', 'Please set a repository URL first');
|
||||
openAlert('Error', 'Please set a repository URL first');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1146,11 +1162,11 @@ const PluginsScreen: React.FC = () => {
|
|||
// Load fresh scrapers from the updated repository
|
||||
await loadScrapers();
|
||||
|
||||
Alert.alert('Success', 'Repository refreshed successfully with latest files');
|
||||
openAlert('Success', 'Repository refreshed successfully with latest files');
|
||||
} catch (error) {
|
||||
logger.error('[PluginsScreen] Failed to refresh repository:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'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`
|
||||
);
|
||||
|
|
@ -1178,28 +1194,27 @@ const PluginsScreen: React.FC = () => {
|
|||
await loadScrapers();
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to toggle scraper:', error);
|
||||
Alert.alert('Error', 'Failed to update scraper status');
|
||||
openAlert('Error', 'Failed to update scraper status');
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearScrapers = () => {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Clear All Scrapers',
|
||||
'Are you sure you want to remove all installed scrapers? This action cannot be undone.',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: 'Clear',
|
||||
style: 'destructive',
|
||||
label: 'Clear',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await localScraperService.clearScrapers();
|
||||
await loadScrapers();
|
||||
Alert.alert('Success', 'All scrapers have been removed');
|
||||
openAlert('Success', 'All scrapers have been removed');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to clear scrapers:', error);
|
||||
Alert.alert('Error', 'Failed to clear scrapers');
|
||||
openAlert('Error', 'Failed to clear scrapers');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -1208,14 +1223,13 @@ const PluginsScreen: React.FC = () => {
|
|||
};
|
||||
|
||||
const handleClearCache = () => {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'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.',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: 'Clear Cache',
|
||||
style: 'destructive',
|
||||
label: 'Clear Cache',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await localScraperService.clearScrapers();
|
||||
|
|
@ -1224,10 +1238,10 @@ const PluginsScreen: React.FC = () => {
|
|||
setRepositoryUrl('');
|
||||
setHasRepository(false);
|
||||
await loadScrapers();
|
||||
Alert.alert('Success', 'Repository cache cleared successfully');
|
||||
openAlert('Success', 'Repository cache cleared successfully');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to clear cache:', error);
|
||||
Alert.alert('Error', 'Failed to clear repository cache');
|
||||
openAlert('Error', 'Failed to clear repository cache');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -1445,10 +1459,10 @@ const PluginsScreen: React.FC = () => {
|
|||
const tapframeInfo = localScraperService.getTapframeRepositoryInfo();
|
||||
const repoId = await localScraperService.addRepository(tapframeInfo);
|
||||
await loadRepositories();
|
||||
Alert.alert('Success', 'Official repository added successfully!');
|
||||
openAlert('Success', 'Official repository added successfully!');
|
||||
} catch (error) {
|
||||
logger.error('[PluginsScreen] Failed to add tapframe repository:', error);
|
||||
Alert.alert('Error', 'Failed to add official repository');
|
||||
openAlert('Error', 'Failed to add official repository');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -1662,7 +1676,7 @@ const PluginsScreen: React.FC = () => {
|
|||
style={[styles.button, styles.primaryButton]}
|
||||
onPress={async () => {
|
||||
await localScraperService.setScraperSettings('showboxog', { cookie: showboxCookie, region: showboxRegion });
|
||||
Alert.alert('Saved', 'ShowBox settings updated');
|
||||
openAlert('Saved', 'ShowBox settings updated');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.buttonText}>Save</Text>
|
||||
|
|
@ -1924,6 +1938,13 @@ const PluginsScreen: React.FC = () => {
|
|||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
FlatList,
|
||||
Alert,
|
||||
StatusBar,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
|
|
@ -17,6 +16,7 @@ import { MaterialIcons } from '@expo/vector-icons';
|
|||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useTraktContext } from '../contexts/TraktContext';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||
const PROFILE_STORAGE_KEY = 'user_profiles';
|
||||
|
|
@ -39,6 +39,23 @@ const ProfilesScreen: React.FC = () => {
|
|||
const [newProfileName, setNewProfileName] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// CustomAlert state
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
// Load profiles from AsyncStorage
|
||||
const loadProfiles = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -59,7 +76,7 @@ const ProfilesScreen: React.FC = () => {
|
|||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error loading profiles:', error);
|
||||
Alert.alert('Error', 'Failed to load profiles');
|
||||
openAlert('Error', 'Failed to load profiles');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -85,7 +102,7 @@ const ProfilesScreen: React.FC = () => {
|
|||
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify(updatedProfiles));
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error saving profiles:', error);
|
||||
Alert.alert('Error', 'Failed to save profiles');
|
||||
openAlert('Error', 'Failed to save profiles');
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -101,7 +118,7 @@ const ProfilesScreen: React.FC = () => {
|
|||
|
||||
const handleAddProfile = useCallback(() => {
|
||||
if (!newProfileName.trim()) {
|
||||
Alert.alert('Error', 'Please enter a profile name');
|
||||
openAlert('Error', 'Please enter a profile name');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -133,24 +150,23 @@ const ProfilesScreen: React.FC = () => {
|
|||
// Prevent deleting the active profile
|
||||
const isActiveProfile = profiles.find(p => p.id === id)?.isActive;
|
||||
if (isActiveProfile) {
|
||||
Alert.alert('Error', 'Cannot delete the active profile. Switch to another profile first.');
|
||||
openAlert('Error', 'Cannot delete the active profile. Switch to another profile first.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent deleting the last profile
|
||||
if (profiles.length <= 1) {
|
||||
Alert.alert('Error', 'Cannot delete the only profile');
|
||||
openAlert('Error', 'Cannot delete the only profile');
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Delete Profile',
|
||||
'Are you sure you want to delete this profile? This action cannot be undone.',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: 'Delete',
|
||||
style: 'destructive',
|
||||
label: 'Delete',
|
||||
onPress: () => {
|
||||
const updatedProfiles = profiles.filter(profile => profile.id !== id);
|
||||
setProfiles(updatedProfiles);
|
||||
|
|
@ -313,6 +329,14 @@ const ProfilesScreen: React.FC = () => {
|
|||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
ScrollView,
|
||||
SafeAreaView,
|
||||
StatusBar,
|
||||
Alert,
|
||||
Platform,
|
||||
Dimensions,
|
||||
Image,
|
||||
|
|
@ -31,6 +30,7 @@ import { useAccount } from '../contexts/AccountContext';
|
|||
import { catalogService } from '../services/catalogService';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import * as Sentry from '@sentry/react-native';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
const isTablet = width >= 768;
|
||||
|
|
@ -227,6 +227,22 @@ const Sidebar: React.FC<SidebarProps> = ({ selectedCategory, onCategorySelect, c
|
|||
const SettingsScreen: React.FC = () => {
|
||||
const { settings, updateSetting } = useSettings();
|
||||
const [hasUpdateBadge, setHasUpdateBadge] = useState(false);
|
||||
// CustomAlert state
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== 'android') return;
|
||||
|
|
@ -333,14 +349,13 @@ const SettingsScreen: React.FC = () => {
|
|||
}, [navigation, loadData]);
|
||||
|
||||
const handleResetSettings = useCallback(() => {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Reset Settings',
|
||||
'Are you sure you want to reset all settings to default values?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: 'Reset',
|
||||
style: 'destructive',
|
||||
label: 'Reset',
|
||||
onPress: () => {
|
||||
(Object.keys(DEFAULT_SETTINGS) as Array<keyof typeof DEFAULT_SETTINGS>).forEach(key => {
|
||||
updateSetting(key, DEFAULT_SETTINGS[key]);
|
||||
|
|
@ -352,20 +367,19 @@ const SettingsScreen: React.FC = () => {
|
|||
}, [updateSetting]);
|
||||
|
||||
const handleClearMDBListCache = () => {
|
||||
Alert.alert(
|
||||
"Clear MDBList Cache",
|
||||
"Are you sure you want to clear all cached MDBList data? This cannot be undone.",
|
||||
openAlert(
|
||||
'Clear MDBList Cache',
|
||||
'Are you sure you want to clear all cached MDBList data? This cannot be undone.',
|
||||
[
|
||||
{ text: "Cancel", style: "cancel" },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: "Clear",
|
||||
style: "destructive",
|
||||
label: 'Clear',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.removeItem('mdblist_cache');
|
||||
Alert.alert("Success", "MDBList cache has been cleared.");
|
||||
openAlert('Success', 'MDBList cache has been cleared.');
|
||||
} catch (error) {
|
||||
Alert.alert("Error", "Could not clear MDBList cache.");
|
||||
openAlert('Error', 'Could not clear MDBList cache.');
|
||||
if (__DEV__) console.error('Error clearing MDBList cache:', error);
|
||||
}
|
||||
}
|
||||
|
|
@ -637,9 +651,9 @@ const SettingsScreen: React.FC = () => {
|
|||
onPress={async () => {
|
||||
try {
|
||||
await AsyncStorage.removeItem('hasCompletedOnboarding');
|
||||
Alert.alert('Success', 'Onboarding has been reset. Restart the app to see the onboarding flow.');
|
||||
openAlert('Success', 'Onboarding has been reset. Restart the app to see the onboarding flow.');
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Failed to reset onboarding.');
|
||||
openAlert('Error', 'Failed to reset onboarding.');
|
||||
}
|
||||
}}
|
||||
renderControl={ChevronRight}
|
||||
|
|
@ -649,20 +663,19 @@ const SettingsScreen: React.FC = () => {
|
|||
title="Clear All Data"
|
||||
icon="delete-forever"
|
||||
onPress={() => {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Clear All Data',
|
||||
'This will reset all settings and clear all cached data. Are you sure?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: 'Clear',
|
||||
style: 'destructive',
|
||||
label: 'Clear',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.clear();
|
||||
Alert.alert('Success', 'All data cleared. Please restart the app.');
|
||||
openAlert('Success', 'All data cleared. Please restart the app.');
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Failed to clear data.');
|
||||
openAlert('Error', 'Failed to clear data.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -786,6 +799,13 @@ const SettingsScreen: React.FC = () => {
|
|||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -863,6 +883,13 @@ const SettingsScreen: React.FC = () => {
|
|||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
ImageBackground,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
Alert,
|
||||
Dimensions,
|
||||
Linking,
|
||||
Clipboard,
|
||||
|
|
@ -47,6 +46,7 @@ import { useSettings } from '../hooks/useSettings';
|
|||
import QualityBadge from '../components/metadata/QualityBadge';
|
||||
import { logger } from '../utils/logger';
|
||||
import { isMkvStream } from '../utils/mkvDetection';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
const TMDB_LOGO = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/89/Tmdb.new.logo.svg/512px-Tmdb.new.logo.svg.png?20200406190906';
|
||||
const HDR_ICON = 'https://uxwing.com/wp-content/themes/uxwing/download/video-photography-multimedia/hdr-icon.png';
|
||||
|
|
@ -176,7 +176,7 @@ const AnimatedView = memo(({
|
|||
});
|
||||
|
||||
// Extracted Components
|
||||
const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, theme, showLogos, scraperLogo }: {
|
||||
const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, theme, showLogos, scraperLogo, showAlert }: {
|
||||
stream: Stream;
|
||||
onPress: () => void;
|
||||
index: number;
|
||||
|
|
@ -185,6 +185,7 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
|||
theme: any;
|
||||
showLogos?: boolean;
|
||||
scraperLogo?: string | null;
|
||||
showAlert: (title: string, message: string) => void;
|
||||
}) => {
|
||||
|
||||
// Handle long press to copy stream URL to clipboard
|
||||
|
|
@ -192,18 +193,10 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
|||
if (stream.url) {
|
||||
try {
|
||||
await Clipboard.setString(stream.url);
|
||||
Alert.alert(
|
||||
'Copied!',
|
||||
'Stream URL has been copied to clipboard.',
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
showAlert('Copied!', 'Stream URL has been copied to clipboard.');
|
||||
} catch (error) {
|
||||
// Fallback: show URL in alert if clipboard fails
|
||||
Alert.alert(
|
||||
'Stream URL',
|
||||
stream.url,
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
showAlert('Stream URL', stream.url);
|
||||
}
|
||||
}
|
||||
}, [stream.url]);
|
||||
|
|
@ -413,6 +406,23 @@ export const StreamsScreen = () => {
|
|||
const loadStartTimeRef = useRef(0);
|
||||
const hasDoneInitialLoadRef = useRef(false);
|
||||
|
||||
// CustomAlert state
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Track when we started fetching streams so we can show an extended loading state
|
||||
|
|
@ -939,7 +949,7 @@ export const StreamsScreen = () => {
|
|||
// Block magnet links - not supported yet
|
||||
if (stream.url.startsWith('magnet:')) {
|
||||
try {
|
||||
Alert.alert('Not supported', 'Torrent streaming is not supported yet.');
|
||||
openAlert('Not supported', 'Torrent streaming is not supported yet.');
|
||||
} catch (_e) {}
|
||||
return;
|
||||
}
|
||||
|
|
@ -1847,6 +1857,7 @@ export const StreamsScreen = () => {
|
|||
theme={currentTheme}
|
||||
showLogos={settings.showScraperLogos}
|
||||
scraperLogo={(item.addonId && scraperLogos[item.addonId]) || (item as any).addon ? scraperLogoCache.get((item.addonId || (item as any).addon) as string) || null : null}
|
||||
showAlert={(t, m) => openAlert(t, m)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
|
@ -1885,6 +1896,13 @@ export const StreamsScreen = () => {
|
|||
</View>
|
||||
)}
|
||||
</View>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
actions={alertActions}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
SafeAreaView,
|
||||
StatusBar,
|
||||
Platform,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
Linking,
|
||||
ScrollView,
|
||||
|
|
@ -27,6 +26,7 @@ import { useSettings } from '../hooks/useSettings';
|
|||
import { logger } from '../utils/logger';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
const TMDB_API_KEY_STORAGE_KEY = 'tmdb_api_key';
|
||||
const USE_CUSTOM_TMDB_API_KEY = 'use_custom_tmdb_api_key';
|
||||
|
|
@ -39,10 +39,37 @@ const TMDBSettingsScreen = () => {
|
|||
const [useCustomKey, setUseCustomKey] = useState(false);
|
||||
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([
|
||||
{ label: 'OK', onPress: () => setAlertVisible(false) },
|
||||
]);
|
||||
const apiKeyInputRef = useRef<TextInput>(null);
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress?: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
if (actions && actions.length > 0) {
|
||||
setAlertActions(
|
||||
actions.map(a => ({
|
||||
label: a.label,
|
||||
style: a.style,
|
||||
onPress: () => { a.onPress?.(); },
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]);
|
||||
}
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
logger.log('[TMDBSettingsScreen] Component mounted');
|
||||
loadSettings();
|
||||
|
|
@ -135,18 +162,16 @@ const TMDBSettingsScreen = () => {
|
|||
|
||||
const clearApiKey = async () => {
|
||||
logger.log('[TMDBSettingsScreen] Clear API key requested');
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Clear API Key',
|
||||
'Are you sure you want to remove your custom API key and revert to the default?',
|
||||
[
|
||||
{
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
onPress: () => logger.log('[TMDBSettingsScreen] Clear API key cancelled')
|
||||
{
|
||||
label: 'Cancel',
|
||||
onPress: () => logger.log('[TMDBSettingsScreen] Clear API key cancelled'),
|
||||
},
|
||||
{
|
||||
text: 'Clear',
|
||||
style: 'destructive',
|
||||
label: 'Clear',
|
||||
onPress: async () => {
|
||||
logger.log('[TMDBSettingsScreen] Proceeding with API key clear');
|
||||
try {
|
||||
|
|
@ -159,10 +184,10 @@ const TMDBSettingsScreen = () => {
|
|||
logger.log('[TMDBSettingsScreen] API key cleared successfully');
|
||||
} catch (error) {
|
||||
logger.error('[TMDBSettingsScreen] Failed to clear API key:', error);
|
||||
Alert.alert('Error', 'Failed to clear API key');
|
||||
openAlert('Error', 'Failed to clear API key');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
|
@ -240,7 +265,7 @@ const TMDBSettingsScreen = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
<View style={[styles.headerContainer, { paddingTop: topSpacing }]}>
|
||||
<View style={styles.header}>
|
||||
|
|
@ -404,6 +429,13 @@ const TMDBSettingsScreen = () => {
|
|||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
actions={alertActions}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Image,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
|
|
@ -25,6 +24,7 @@ import { useTheme } from '../contexts/ThemeContext';
|
|||
import { useTraktIntegration } from '../hooks/useTraktIntegration';
|
||||
import { useTraktAutosyncSettings } from '../hooks/useTraktAutosyncSettings';
|
||||
import { colors } from '../styles';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||
|
||||
|
|
@ -68,6 +68,33 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
|
||||
const [showSyncFrequencyModal, setShowSyncFrequencyModal] = useState(false);
|
||||
const [showThresholdModal, setShowThresholdModal] = useState(false);
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([
|
||||
{ label: 'OK', onPress: () => setAlertVisible(false) },
|
||||
]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress?: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
if (actions && actions.length > 0) {
|
||||
setAlertActions(
|
||||
actions.map(a => ({
|
||||
label: a.label,
|
||||
style: a.style,
|
||||
onPress: () => { a.onPress?.(); },
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]);
|
||||
}
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
const checkAuthStatus = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
|
|
@ -120,32 +147,32 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
logger.log('[TraktSettingsScreen] Token exchange successful');
|
||||
checkAuthStatus().then(() => {
|
||||
// Show success message
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Successfully Connected',
|
||||
'Your Trakt account has been connected successfully.',
|
||||
[
|
||||
{
|
||||
text: 'OK',
|
||||
onPress: () => navigation.goBack()
|
||||
label: 'OK',
|
||||
onPress: () => navigation.goBack(),
|
||||
}
|
||||
]
|
||||
);
|
||||
});
|
||||
} else {
|
||||
logger.error('[TraktSettingsScreen] Token exchange failed');
|
||||
Alert.alert('Authentication Error', 'Failed to complete authentication with Trakt.');
|
||||
openAlert('Authentication Error', 'Failed to complete authentication with Trakt.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('[TraktSettingsScreen] Token exchange error:', error);
|
||||
Alert.alert('Authentication Error', 'An error occurred during authentication.');
|
||||
openAlert('Authentication Error', 'An error occurred during authentication.');
|
||||
})
|
||||
.finally(() => {
|
||||
setIsExchangingCode(false);
|
||||
});
|
||||
} else if (response.type === 'error') {
|
||||
logger.error('[TraktSettingsScreen] Authentication error:', response.error);
|
||||
Alert.alert('Authentication Error', response.error?.message || 'An error occurred during authentication.');
|
||||
openAlert('Authentication Error', response.error?.message || 'An error occurred during authentication.');
|
||||
setIsExchangingCode(false);
|
||||
} else {
|
||||
logger.log('[TraktSettingsScreen] Auth response type:', response.type);
|
||||
|
|
@ -159,14 +186,13 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
};
|
||||
|
||||
const handleSignOut = async () => {
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Sign Out',
|
||||
'Are you sure you want to sign out of your Trakt account?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
text: 'Sign Out',
|
||||
style: 'destructive',
|
||||
label: 'Sign Out',
|
||||
onPress: async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
|
|
@ -175,7 +201,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
setUserProfile(null);
|
||||
} catch (error) {
|
||||
logger.error('[TraktSettingsScreen] Error signing out:', error);
|
||||
Alert.alert('Error', 'Failed to sign out of Trakt.');
|
||||
openAlert('Error', 'Failed to sign out of Trakt.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -398,10 +424,9 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
disabled={isSyncing}
|
||||
onPress={async () => {
|
||||
const success = await performManualSync();
|
||||
Alert.alert(
|
||||
openAlert(
|
||||
'Sync Complete',
|
||||
success ? 'Successfully synced your watch progress with Trakt.' : 'Sync failed. Please try again.',
|
||||
[{ text: 'OK' }]
|
||||
success ? 'Successfully synced your watch progress with Trakt.' : 'Sync failed. Please try again.'
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
ScrollView,
|
||||
SafeAreaView,
|
||||
StatusBar,
|
||||
Alert,
|
||||
Platform,
|
||||
Dimensions
|
||||
} from 'react-native';
|
||||
|
|
@ -19,6 +18,7 @@ import { RootStackParamList } from '../navigation/AppNavigator';
|
|||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import UpdateService from '../services/updateService';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
|
|
@ -65,7 +65,36 @@ const UpdateScreen: React.FC = () => {
|
|||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
|
||||
// CustomAlert state
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertTitle, setAlertTitle] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertActions, setAlertActions] = useState<Array<{ label: string; onPress: () => void; style?: object }>>([
|
||||
{ label: 'OK', onPress: () => setAlertVisible(false) },
|
||||
]);
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
message: string,
|
||||
actions?: Array<{ label: string; onPress?: () => void; style?: object }>
|
||||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
if (actions && actions.length > 0) {
|
||||
setAlertActions(
|
||||
actions.map(a => ({
|
||||
label: a.label,
|
||||
style: a.style,
|
||||
onPress: () => { a.onPress?.(); },
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]);
|
||||
}
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
const [updateInfo, setUpdateInfo] = useState<any>(null);
|
||||
const [currentInfo, setCurrentInfo] = useState<any>(null);
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
|
|
@ -100,7 +129,7 @@ const UpdateScreen: React.FC = () => {
|
|||
if (__DEV__) console.error('Error checking for updates:', error);
|
||||
setUpdateStatus('error');
|
||||
setLastOperation(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
Alert.alert('Error', 'Failed to check for updates');
|
||||
openAlert('Error', 'Failed to check for updates');
|
||||
} finally {
|
||||
setIsChecking(false);
|
||||
}
|
||||
|
|
@ -149,17 +178,17 @@ const UpdateScreen: React.FC = () => {
|
|||
if (success) {
|
||||
setUpdateStatus('success');
|
||||
setLastOperation('Update installed successfully');
|
||||
Alert.alert('Success', 'Update will be applied on next app restart');
|
||||
openAlert('Success', 'Update will be applied on next app restart');
|
||||
} else {
|
||||
setUpdateStatus('error');
|
||||
setLastOperation('No update available to install');
|
||||
Alert.alert('No Update', 'No update available to install');
|
||||
openAlert('No Update', 'No update available to install');
|
||||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error installing update:', error);
|
||||
setUpdateStatus('error');
|
||||
setLastOperation(`Installation error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
Alert.alert('Error', 'Failed to install update');
|
||||
openAlert('Error', 'Failed to install update');
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
}
|
||||
|
|
@ -569,6 +598,13 @@ const UpdateScreen: React.FC = () => {
|
|||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
message={alertMessage}
|
||||
onClose={() => setAlertVisible(false)}
|
||||
actions={alertActions}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue