mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 16:51:57 +00:00
local backup improvements
This commit is contained in:
parent
88313e6d06
commit
60e27da57d
5 changed files with 320 additions and 33 deletions
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import * as DocumentPicker from 'expo-document-picker';
|
import * as DocumentPicker from 'expo-document-picker';
|
||||||
import * as Sharing from 'expo-sharing';
|
import * as Sharing from 'expo-sharing';
|
||||||
|
import * as Updates from 'expo-updates';
|
||||||
import { backupService, BackupOptions } from '../services/backupService';
|
import { backupService, BackupOptions } from '../services/backupService';
|
||||||
import { useTheme } from '../contexts/ThemeContext';
|
import { useTheme } from '../contexts/ThemeContext';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
|
|
@ -40,6 +41,20 @@ const BackupRestoreSettings: React.FC<BackupRestoreSettingsProps> = ({ isTablet
|
||||||
setAlertVisible(true);
|
setAlertVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const restartApp = async () => {
|
||||||
|
try {
|
||||||
|
await Updates.reloadAsync();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[BackupRestoreSettings] Failed to restart app:', error);
|
||||||
|
// Fallback: show error message
|
||||||
|
openAlert(
|
||||||
|
'Restart Failed',
|
||||||
|
'Failed to restart the app. Please manually close and reopen the app to see your restored data.',
|
||||||
|
[{ label: 'OK', onPress: () => {} }]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Create backup
|
// Create backup
|
||||||
const handleCreateBackup = useCallback(async () => {
|
const handleCreateBackup = useCallback(async () => {
|
||||||
|
|
@ -162,7 +177,14 @@ const BackupRestoreSettings: React.FC<BackupRestoreSettingsProps> = ({ isTablet
|
||||||
openAlert(
|
openAlert(
|
||||||
'Restore Complete',
|
'Restore Complete',
|
||||||
'Your data has been successfully restored. Please restart the app to see all changes.',
|
'Your data has been successfully restored. Please restart the app to see all changes.',
|
||||||
[{ label: 'OK', onPress: () => {} }]
|
[
|
||||||
|
{ label: 'Cancel', onPress: () => {} },
|
||||||
|
{
|
||||||
|
label: 'Restart App',
|
||||||
|
onPress: restartApp,
|
||||||
|
style: { fontWeight: 'bold' }
|
||||||
|
}
|
||||||
|
]
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[BackupRestoreSettings] Failed to restore backup:', error);
|
logger.error('[BackupRestoreSettings] Failed to restore backup:', error);
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,11 @@ const AuthScreen: React.FC = () => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
const [mode, setMode] = useState<'signin' | 'signup'>('signin');
|
const [mode, setMode] = useState<'signin' | 'signup'>('signin');
|
||||||
|
const signupDisabled = true; // Signup disabled due to upcoming system replacement
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [showWarningDetails, setShowWarningDetails] = useState(false);
|
||||||
|
const authCardOpacity = useRef(new Animated.Value(1)).current;
|
||||||
|
|
||||||
// Subtle, performant animations
|
// Subtle, performant animations
|
||||||
const introOpacity = useRef(new Animated.Value(0)).current;
|
const introOpacity = useRef(new Animated.Value(0)).current;
|
||||||
|
|
@ -141,6 +144,16 @@ const AuthScreen: React.FC = () => {
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
|
|
||||||
|
// Prevent signup if disabled
|
||||||
|
if (mode === 'signup' && signupDisabled) {
|
||||||
|
const msg = 'Sign up is currently disabled due to upcoming system changes';
|
||||||
|
setError(msg);
|
||||||
|
Toast.error(msg);
|
||||||
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isEmailValid) {
|
if (!isEmailValid) {
|
||||||
const msg = 'Enter a valid email address';
|
const msg = 'Enter a valid email address';
|
||||||
setError(msg);
|
setError(msg);
|
||||||
|
|
@ -187,6 +200,27 @@ const AuthScreen: React.FC = () => {
|
||||||
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' as never }] } as any);
|
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' as never }] } as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleWarningDetails = () => {
|
||||||
|
if (showWarningDetails) {
|
||||||
|
// Fade in auth card
|
||||||
|
Animated.timing(authCardOpacity, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 300,
|
||||||
|
easing: Easing.out(Easing.cubic),
|
||||||
|
useNativeDriver: true,
|
||||||
|
}).start();
|
||||||
|
} else {
|
||||||
|
// Fade out auth card
|
||||||
|
Animated.timing(authCardOpacity, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 300,
|
||||||
|
easing: Easing.out(Easing.cubic),
|
||||||
|
useNativeDriver: true,
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
setShowWarningDetails(!showWarningDetails);
|
||||||
|
};
|
||||||
|
|
||||||
// showToast helper replaced with direct calls to toast.* API
|
// showToast helper replaced with direct calls to toast.* API
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -254,13 +288,72 @@ const AuthScreen: React.FC = () => {
|
||||||
</Text>
|
</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
|
{/* Important Warning Message */}
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.warningContainer,
|
||||||
|
{
|
||||||
|
opacity: introOpacity,
|
||||||
|
transform: [{ translateY: introTranslateY }],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.warningCard, { backgroundColor: 'rgba(255, 193, 7, 0.1)', borderColor: 'rgba(255, 193, 7, 0.3)' }]}
|
||||||
|
onPress={toggleWarningDetails}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<MaterialIcons name="warning" size={20} color="#FFC107" style={styles.warningIcon} />
|
||||||
|
<View style={styles.warningContent}>
|
||||||
|
<Text style={[styles.warningTitle, { color: '#FFC107' }]}>
|
||||||
|
Important Notice
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.warningText, { color: currentTheme.colors.white }]}>
|
||||||
|
This authentication system will be completely replaced by local backup/restore functionality by October 8th. Please create backup files as your cloud data will be permanently destroyed.
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.readMoreText, { color: '#FFC107' }]}>
|
||||||
|
Read more {showWarningDetails ? '▼' : '▶'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Expanded Details */}
|
||||||
|
{showWarningDetails && (
|
||||||
|
<Animated.View style={[styles.warningDetails, { backgroundColor: 'rgba(255, 193, 7, 0.05)', borderColor: 'rgba(255, 193, 7, 0.2)' }]}>
|
||||||
|
<View style={styles.detailsContent}>
|
||||||
|
<Text style={[styles.detailsTitle, { color: '#FFC107' }]}>
|
||||||
|
Why is this system being discontinued?
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.detailsText, { color: currentTheme.colors.white }]}>
|
||||||
|
• Lack of real-time support for addon synchronization{'\n'}
|
||||||
|
• Database synchronization issues with addons and settings{'\n'}
|
||||||
|
• Unreliable cloud data management{'\n'}
|
||||||
|
• Performance problems with remote data access
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text style={[styles.detailsTitle, { color: '#FFC107', marginTop: 16 }]}>
|
||||||
|
Benefits of Local Backup System:
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.detailsText, { color: currentTheme.colors.white }]}>
|
||||||
|
• Instant addon synchronization across devices{'\n'}
|
||||||
|
• Reliable offline access to all your data{'\n'}
|
||||||
|
• Complete control over your backup files{'\n'}
|
||||||
|
• Faster performance with local data storage{'\n'}
|
||||||
|
• No dependency on external servers{'\n'}
|
||||||
|
• Easy migration between devices
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
keyboardVerticalOffset={Platform.OS === 'ios' ? headerHeight : 0}
|
keyboardVerticalOffset={Platform.OS === 'ios' ? headerHeight : 0}
|
||||||
>
|
>
|
||||||
{/* Main Card */}
|
{/* Main Card - Hide when warning details are expanded */}
|
||||||
<View style={styles.centerContainer}>
|
<Animated.View style={[styles.centerContainer, { opacity: authCardOpacity }]}>
|
||||||
<Animated.View style={[styles.card, {
|
<Animated.View style={[styles.card, {
|
||||||
backgroundColor: Platform.OS === 'android' ? '#121212' : 'rgba(255,255,255,0.02)',
|
backgroundColor: Platform.OS === 'android' ? '#121212' : 'rgba(255,255,255,0.02)',
|
||||||
borderColor: Platform.OS === 'android' ? '#1f1f1f' : 'rgba(255,255,255,0.06)',
|
borderColor: Platform.OS === 'android' ? '#1f1f1f' : 'rgba(255,255,255,0.06)',
|
||||||
|
|
@ -312,12 +405,19 @@ const AuthScreen: React.FC = () => {
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.switchButton,
|
styles.switchButton,
|
||||||
|
signupDisabled && styles.disabledButton,
|
||||||
]}
|
]}
|
||||||
onPress={() => setMode('signup')}
|
onPress={() => !signupDisabled && setMode('signup')}
|
||||||
activeOpacity={0.8}
|
activeOpacity={signupDisabled ? 1 : 0.8}
|
||||||
|
disabled={signupDisabled}
|
||||||
>
|
>
|
||||||
<Text style={[styles.switchText, { color: mode === 'signup' ? '#fff' : currentTheme.colors.textMuted }]}>
|
<Text style={[
|
||||||
Sign Up
|
styles.switchText,
|
||||||
|
{
|
||||||
|
color: mode === 'signup' ? '#fff' : (signupDisabled ? 'rgba(255,255,255,0.3)' : currentTheme.colors.textMuted)
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
Sign Up {signupDisabled && '(Disabled)'}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -482,18 +582,29 @@ const AuthScreen: React.FC = () => {
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
{/* Switch Mode */}
|
{/* Switch Mode */}
|
||||||
<TouchableOpacity
|
{!signupDisabled && (
|
||||||
onPress={() => setMode(mode === 'signin' ? 'signup' : 'signin')}
|
<TouchableOpacity
|
||||||
activeOpacity={0.7}
|
onPress={() => setMode(mode === 'signin' ? 'signup' : 'signin')}
|
||||||
style={{ marginTop: 16 }}
|
activeOpacity={0.7}
|
||||||
>
|
style={{ marginTop: 16 }}
|
||||||
<Text style={[styles.switchModeText, { color: currentTheme.colors.textMuted }]}>
|
>
|
||||||
{mode === 'signin' ? "Don't have an account? " : 'Already have an account? '}
|
<Text style={[styles.switchModeText, { color: currentTheme.colors.textMuted }]}>
|
||||||
<Text style={{ color: currentTheme.colors.primary, fontWeight: '600' }}>
|
{mode === 'signin' ? "Don't have an account? " : 'Already have an account? '}
|
||||||
{mode === 'signin' ? 'Sign up' : 'Sign in'}
|
<Text style={{ color: currentTheme.colors.primary, fontWeight: '600' }}>
|
||||||
|
{mode === 'signin' ? 'Sign up' : 'Sign in'}
|
||||||
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
)}
|
||||||
|
|
||||||
|
{/* Signup disabled message */}
|
||||||
|
{signupDisabled && mode === 'signin' && (
|
||||||
|
<View style={{ marginTop: 16, alignItems: 'center' }}>
|
||||||
|
<Text style={[styles.switchModeText, { color: 'rgba(255,255,255,0.5)', fontSize: 13 }]}>
|
||||||
|
New account creation is temporarily disabled
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Skip sign in - more prominent when coming from onboarding */}
|
{/* Skip sign in - more prominent when coming from onboarding */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|
@ -520,7 +631,7 @@ const AuthScreen: React.FC = () => {
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
</View>
|
</Animated.View>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
{/* Toasts rendered globally in App root */}
|
{/* Toasts rendered globally in App root */}
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|
@ -580,8 +691,9 @@ const styles = StyleSheet.create({
|
||||||
centerContainer: {
|
centerContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'flex-start',
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
|
paddingTop: 20,
|
||||||
paddingBottom: 28,
|
paddingBottom: 28,
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
|
|
@ -686,6 +798,63 @@ const styles = StyleSheet.create({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
|
warningContainer: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
marginTop: 24,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
warningCard: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
padding: 16,
|
||||||
|
borderRadius: 12,
|
||||||
|
borderWidth: 1,
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
warningIcon: {
|
||||||
|
marginRight: 12,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
warningContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
warningTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
warningText: {
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 20,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
disabledButton: {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
readMoreText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginTop: 8,
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
},
|
||||||
|
warningDetails: {
|
||||||
|
marginTop: 8,
|
||||||
|
borderRadius: 12,
|
||||||
|
borderWidth: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
detailsContent: {
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
detailsTitle: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '700',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
detailsText: {
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 18,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default AuthScreen;
|
export default AuthScreen;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import * as DocumentPicker from 'expo-document-picker';
|
import * as DocumentPicker from 'expo-document-picker';
|
||||||
import * as Sharing from 'expo-sharing';
|
import * as Sharing from 'expo-sharing';
|
||||||
|
import * as Updates from 'expo-updates';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { backupService, BackupOptions } from '../services/backupService';
|
import { backupService, BackupOptions } from '../services/backupService';
|
||||||
import { useTheme } from '../contexts/ThemeContext';
|
import { useTheme } from '../contexts/ThemeContext';
|
||||||
|
|
@ -41,6 +42,20 @@ const BackupScreen: React.FC = () => {
|
||||||
setAlertVisible(true);
|
setAlertVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const restartApp = async () => {
|
||||||
|
try {
|
||||||
|
await Updates.reloadAsync();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[BackupScreen] Failed to restart app:', error);
|
||||||
|
// Fallback: show error message
|
||||||
|
openAlert(
|
||||||
|
'Restart Failed',
|
||||||
|
'Failed to restart the app. Please manually close and reopen the app to see your restored data.',
|
||||||
|
[{ label: 'OK', onPress: () => {} }]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create backup
|
// Create backup
|
||||||
const handleCreateBackup = useCallback(async () => {
|
const handleCreateBackup = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -162,7 +177,14 @@ const BackupScreen: React.FC = () => {
|
||||||
openAlert(
|
openAlert(
|
||||||
'Restore Complete',
|
'Restore Complete',
|
||||||
'Your data has been successfully restored. Please restart the app to see all changes.',
|
'Your data has been successfully restored. Please restart the app to see all changes.',
|
||||||
[{ label: 'OK', onPress: () => {} }]
|
[
|
||||||
|
{ label: 'Cancel', onPress: () => {} },
|
||||||
|
{
|
||||||
|
label: 'Restart App',
|
||||||
|
onPress: restartApp,
|
||||||
|
style: { fontWeight: 'bold' }
|
||||||
|
}
|
||||||
|
]
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[BackupScreen] Failed to restore backup:', error);
|
logger.error('[BackupScreen] Failed to restore backup:', error);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import Animated, {
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { useTheme } from '../contexts/ThemeContext';
|
import { useTheme } from '../contexts/ThemeContext';
|
||||||
import { NavigationProp, useNavigation } from '@react-navigation/native';
|
import { NavigationProp, useNavigation } from '@react-navigation/native';
|
||||||
import { useAccount } from '../contexts/AccountContext';
|
|
||||||
import { RootStackParamList } from '../navigation/AppNavigator';
|
import { RootStackParamList } from '../navigation/AppNavigator';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
|
||||||
|
|
@ -79,7 +78,6 @@ const onboardingData: OnboardingSlide[] = [
|
||||||
const OnboardingScreen = () => {
|
const OnboardingScreen = () => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||||
const { user } = useAccount();
|
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
const flatListRef = useRef<FlatList>(null);
|
const flatListRef = useRef<FlatList>(null);
|
||||||
const progressValue = useSharedValue(0);
|
const progressValue = useSharedValue(0);
|
||||||
|
|
@ -137,15 +135,11 @@ const OnboardingScreen = () => {
|
||||||
const handleGetStarted = async () => {
|
const handleGetStarted = async () => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem('hasCompletedOnboarding', 'true');
|
await AsyncStorage.setItem('hasCompletedOnboarding', 'true');
|
||||||
// After onboarding, route to login if no user; otherwise go to app
|
// After onboarding, go directly to main app
|
||||||
if (!user) {
|
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });
|
||||||
navigation.reset({ index: 0, routes: [{ name: 'Account', params: { fromOnboarding: true } as any }] });
|
|
||||||
} else {
|
|
||||||
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (__DEV__) console.error('Error saving onboarding status:', error);
|
if (__DEV__) console.error('Error saving onboarding status:', error);
|
||||||
navigation.reset({ index: 0, routes: [{ name: user ? 'MainTabs' : 'Account', params: !user ? ({ fromOnboarding: true } as any) : undefined }] });
|
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -453,8 +453,43 @@ export class BackupService {
|
||||||
|
|
||||||
private async getTraktSettings(): Promise<any> {
|
private async getTraktSettings(): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
// Get general Trakt settings
|
||||||
const traktSettingsJson = await AsyncStorage.getItem('trakt_settings');
|
const traktSettingsJson = await AsyncStorage.getItem('trakt_settings');
|
||||||
return traktSettingsJson ? JSON.parse(traktSettingsJson) : {};
|
const traktSettings = traktSettingsJson ? JSON.parse(traktSettingsJson) : {};
|
||||||
|
|
||||||
|
// Get authentication tokens
|
||||||
|
const [
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
tokenExpiry,
|
||||||
|
autosyncEnabled,
|
||||||
|
syncFrequency,
|
||||||
|
completionThreshold
|
||||||
|
] = await Promise.all([
|
||||||
|
AsyncStorage.getItem('trakt_access_token'),
|
||||||
|
AsyncStorage.getItem('trakt_refresh_token'),
|
||||||
|
AsyncStorage.getItem('trakt_token_expiry'),
|
||||||
|
AsyncStorage.getItem('trakt_autosync_enabled'),
|
||||||
|
AsyncStorage.getItem('trakt_sync_frequency'),
|
||||||
|
AsyncStorage.getItem('trakt_completion_threshold')
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...traktSettings,
|
||||||
|
authentication: {
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
tokenExpiry: tokenExpiry ? parseInt(tokenExpiry, 10) : null
|
||||||
|
},
|
||||||
|
autosync: {
|
||||||
|
enabled: autosyncEnabled ? (() => {
|
||||||
|
try { return JSON.parse(autosyncEnabled); }
|
||||||
|
catch { return true; }
|
||||||
|
})() : true,
|
||||||
|
frequency: syncFrequency ? parseInt(syncFrequency, 10) : 60000,
|
||||||
|
completionThreshold: completionThreshold ? parseInt(completionThreshold, 10) : 95
|
||||||
|
}
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[BackupService] Failed to get Trakt settings:', error);
|
logger.error('[BackupService] Failed to get Trakt settings:', error);
|
||||||
return {};
|
return {};
|
||||||
|
|
@ -607,8 +642,53 @@ export class BackupService {
|
||||||
|
|
||||||
private async restoreTraktSettings(traktSettings: any): Promise<void> {
|
private async restoreTraktSettings(traktSettings: any): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem('trakt_settings', JSON.stringify(traktSettings));
|
// Restore general Trakt settings
|
||||||
logger.info('[BackupService] Trakt settings restored');
|
if (traktSettings && typeof traktSettings === 'object') {
|
||||||
|
const { authentication, autosync, ...generalSettings } = traktSettings;
|
||||||
|
|
||||||
|
// Restore general settings
|
||||||
|
await AsyncStorage.setItem('trakt_settings', JSON.stringify(generalSettings));
|
||||||
|
|
||||||
|
// Restore authentication tokens if available
|
||||||
|
if (authentication) {
|
||||||
|
const tokenPromises = [];
|
||||||
|
|
||||||
|
if (authentication.accessToken) {
|
||||||
|
tokenPromises.push(AsyncStorage.setItem('trakt_access_token', authentication.accessToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authentication.refreshToken) {
|
||||||
|
tokenPromises.push(AsyncStorage.setItem('trakt_refresh_token', authentication.refreshToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authentication.tokenExpiry) {
|
||||||
|
tokenPromises.push(AsyncStorage.setItem('trakt_token_expiry', authentication.tokenExpiry.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(tokenPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore autosync settings if available
|
||||||
|
if (autosync) {
|
||||||
|
const autosyncPromises = [];
|
||||||
|
|
||||||
|
if (autosync.enabled !== undefined) {
|
||||||
|
autosyncPromises.push(AsyncStorage.setItem('trakt_autosync_enabled', JSON.stringify(autosync.enabled)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autosync.frequency !== undefined) {
|
||||||
|
autosyncPromises.push(AsyncStorage.setItem('trakt_sync_frequency', autosync.frequency.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autosync.completionThreshold !== undefined) {
|
||||||
|
autosyncPromises.push(AsyncStorage.setItem('trakt_completion_threshold', autosync.completionThreshold.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(autosyncPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('[BackupService] Trakt settings and authentication restored');
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[BackupService] Failed to restore Trakt settings:', error);
|
logger.error('[BackupService] Failed to restore Trakt settings:', error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue