mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-11 08:48:33 -04:00
Fix app initialize and reinitialize layout (#1274)
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Alert, StyleSheet } from 'react-native';
|
||||
import { Alert, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
import { useVaultSync } from '@/hooks/useVaultSync';
|
||||
|
||||
import LoadingIndicator from '@/components/LoadingIndicator';
|
||||
@@ -17,13 +19,14 @@ import NativeVaultManager from '@/specs/NativeVaultManager';
|
||||
export default function Initialize() : React.ReactNode {
|
||||
const router = useRouter();
|
||||
const [status, setStatus] = useState('');
|
||||
const [showOfflineButton, setShowOfflineButton] = useState(false);
|
||||
const [showSkipButton, setShowSkipButton] = useState(false);
|
||||
const hasInitialized = useRef(false);
|
||||
const offlineButtonTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const skipButtonTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const { t } = useTranslation();
|
||||
const app = useApp();
|
||||
const { syncVault } = useVaultSync();
|
||||
const dbContext = useDb();
|
||||
const colors = useColors();
|
||||
|
||||
/**
|
||||
* Handle offline scenario - show alert with options to open local vault or retry sync.
|
||||
@@ -97,12 +100,12 @@ export default function Initialize() : React.ReactNode {
|
||||
*/
|
||||
onPress: () : void => {
|
||||
setStatus(t('app.status.retryingConnection'));
|
||||
setShowOfflineButton(false);
|
||||
setShowSkipButton(false);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
offlineButtonTimeoutRef.current = null;
|
||||
if (skipButtonTimeoutRef.current) {
|
||||
clearTimeout(skipButtonTimeoutRef.current);
|
||||
skipButtonTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,17 +198,16 @@ export default function Initialize() : React.ReactNode {
|
||||
setStatus(message);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
if (skipButtonTimeoutRef.current) {
|
||||
clearTimeout(skipButtonTimeoutRef.current);
|
||||
skipButtonTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
// Show offline button after 2 seconds if we're checking vault updates
|
||||
if (message === t('vault.checkingVaultUpdates')) {
|
||||
offlineButtonTimeoutRef.current = setTimeout(() => {
|
||||
setShowOfflineButton(true);
|
||||
}, 2000) as unknown as NodeJS.Timeout;
|
||||
} else {
|
||||
setShowOfflineButton(false);
|
||||
// Show skip button after 5 seconds when we start loading
|
||||
if (message && !showSkipButton) {
|
||||
skipButtonTimeoutRef.current = setTimeout(() => {
|
||||
setShowSkipButton(true);
|
||||
}, 5000) as unknown as NodeJS.Timeout;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -247,22 +249,22 @@ export default function Initialize() : React.ReactNode {
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
return (): void => {
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
if (skipButtonTimeoutRef.current) {
|
||||
clearTimeout(skipButtonTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [dbContext, syncVault, app, router, t, handleOfflineFlow]);
|
||||
}, [dbContext, syncVault, app, router, t, handleOfflineFlow, showSkipButton]);
|
||||
|
||||
/**
|
||||
* Handle offline button press by calling the stored offline handler.
|
||||
* Handle skip button press by calling the offline handler.
|
||||
*/
|
||||
const handleOfflinePress = (): void => {
|
||||
const handleSkipPress = (): void => {
|
||||
// Clear any existing timeout
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
if (skipButtonTimeoutRef.current) {
|
||||
clearTimeout(skipButtonTimeoutRef.current);
|
||||
}
|
||||
|
||||
setShowOfflineButton(false);
|
||||
setShowSkipButton(false);
|
||||
|
||||
handleOfflineFlow();
|
||||
};
|
||||
@@ -272,18 +274,37 @@ export default function Initialize() : React.ReactNode {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
skipButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: colors.accentBackground,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 8,
|
||||
width: 200,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.accentBorder,
|
||||
},
|
||||
skipButtonText: {
|
||||
marginLeft: 8,
|
||||
fontSize: 16,
|
||||
color: colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
{status ? (
|
||||
<LoadingIndicator
|
||||
status={status}
|
||||
showOfflineButton={showOfflineButton}
|
||||
onOfflinePress={handleOfflinePress}
|
||||
/>
|
||||
) : null}
|
||||
<View>
|
||||
<LoadingIndicator status={status || ''} />
|
||||
</View>
|
||||
{showSkipButton && (
|
||||
<TouchableOpacity style={styles.skipButton} onPress={handleSkipPress}>
|
||||
<Ionicons name="close" size={20} color={colors.textMuted} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Href, router } from 'expo-router';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StyleSheet, View, Alert } from 'react-native';
|
||||
import { StyleSheet, View, Alert, TouchableOpacity } from 'react-native';
|
||||
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
import { useVaultSync } from '@/hooks/useVaultSync';
|
||||
@@ -22,9 +23,9 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
const dbContext = useDb();
|
||||
const { syncVault } = useVaultSync();
|
||||
const [status, setStatus] = useState('');
|
||||
const [showOfflineButton, setShowOfflineButton] = useState(false);
|
||||
const [showSkipButton, setShowSkipButton] = useState(false);
|
||||
const hasInitialized = useRef(false);
|
||||
const offlineButtonTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const skipButtonTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const colors = useColors();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -109,11 +110,11 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
router.replace('/(tabs)/credentials');
|
||||
setTimeout(() => {
|
||||
if (params.serviceUrl) {
|
||||
router.push(path + '?serviceUrl=' + params.serviceUrl);
|
||||
router.push(`${path}?serviceUrl=${params.serviceUrl}` as Href);
|
||||
} else if (params.id) {
|
||||
router.push(path + '?id=' + params.id);
|
||||
router.push(`${path}?id=${params.id}` as Href);
|
||||
} else {
|
||||
router.push(path);
|
||||
router.push(path as Href);
|
||||
}
|
||||
}, 0);
|
||||
app.setReturnUrl(null);
|
||||
@@ -130,12 +131,12 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
*/
|
||||
onPress: () : void => {
|
||||
setStatus(t('app.status.retryingConnection'));
|
||||
setShowOfflineButton(false);
|
||||
setShowSkipButton(false);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
offlineButtonTimeoutRef.current = null;
|
||||
if (skipButtonTimeoutRef.current) {
|
||||
clearTimeout(skipButtonTimeoutRef.current);
|
||||
skipButtonTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,11 +181,11 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
const params = app.returnUrl.params as Record<string, string>;
|
||||
|
||||
if (params.serviceUrl) {
|
||||
simulateStackNavigation('/(tabs)/credentials', path + '?serviceUrl=' + params.serviceUrl);
|
||||
simulateStackNavigation('/(tabs)/credentials', `${path}?serviceUrl=${params.serviceUrl}`);
|
||||
} else if (params.id) {
|
||||
simulateStackNavigation('/(tabs)/credentials', path + '?id=' + params.id);
|
||||
simulateStackNavigation('/(tabs)/credentials', `${path}?id=${params.id}`);
|
||||
} else {
|
||||
simulateStackNavigation('/(tabs)/credentials', path);
|
||||
simulateStackNavigation('/(tabs)/credentials', path as string);
|
||||
}
|
||||
} else {
|
||||
router.replace({
|
||||
@@ -267,18 +268,11 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
onStatus: (message) => {
|
||||
setStatus(message);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Show offline button after 2 seconds if we're checking vault updates
|
||||
if (message === t('vault.checkingVaultUpdates')) {
|
||||
offlineButtonTimeoutRef.current = setTimeout(() => {
|
||||
setShowOfflineButton(true);
|
||||
}, 2000) as unknown as NodeJS.Timeout;
|
||||
} else {
|
||||
setShowOfflineButton(false);
|
||||
// Show skip button after 5 seconds when we start loading
|
||||
if (message && !skipButtonTimeoutRef.current) {
|
||||
skipButtonTimeoutRef.current = setTimeout(() => {
|
||||
setShowSkipButton(true);
|
||||
}, 5000) as unknown as NodeJS.Timeout;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -312,25 +306,18 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
};
|
||||
|
||||
initialize();
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
return (): void => {
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [syncVault, app, dbContext, t, handleOfflineFlow]);
|
||||
|
||||
/**
|
||||
* Handle offline button press by calling the stored offline handler.
|
||||
* Handle skip button press by calling the offline handler.
|
||||
*/
|
||||
const handleOfflinePress = (): void => {
|
||||
const handleSkipPress = (): void => {
|
||||
// Clear any existing timeout
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
if (skipButtonTimeoutRef.current) {
|
||||
clearTimeout(skipButtonTimeoutRef.current);
|
||||
}
|
||||
|
||||
setShowOfflineButton(false);
|
||||
setShowSkipButton(false);
|
||||
|
||||
handleOfflineFlow();
|
||||
};
|
||||
@@ -340,6 +327,11 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
contentWrapper: {
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
},
|
||||
message1: {
|
||||
marginTop: 5,
|
||||
@@ -347,26 +339,48 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
},
|
||||
message2: {
|
||||
textAlign: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
messageContainer: {
|
||||
backgroundColor: colors.accentBackground,
|
||||
borderRadius: 10,
|
||||
padding: 20,
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
maxWidth: 300,
|
||||
},
|
||||
skipButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: colors.accentBackground,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 8,
|
||||
width: 200,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.accentBorder,
|
||||
},
|
||||
skipButtonText: {
|
||||
marginLeft: 8,
|
||||
fontSize: 16,
|
||||
color: colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
<View style={styles.messageContainer}>
|
||||
<ThemedText style={styles.message1}>{t('app.reinitialize.vaultAutoLockedMessage')}</ThemedText>
|
||||
<ThemedText style={styles.message2}>{t('app.reinitialize.attemptingToUnlockMessage')}</ThemedText>
|
||||
{status ? (
|
||||
<LoadingIndicator
|
||||
status={status}
|
||||
showOfflineButton={showOfflineButton}
|
||||
onOfflinePress={handleOfflinePress}
|
||||
/>
|
||||
) : null}
|
||||
<View style={styles.contentWrapper}>
|
||||
<View style={styles.messageContainer}>
|
||||
<ThemedText style={styles.message1}>{t('app.reinitialize.vaultAutoLockedMessage')}</ThemedText>
|
||||
<ThemedText style={styles.message2}>{t('app.reinitialize.attemptingToUnlockMessage')}</ThemedText>
|
||||
{status ? <LoadingIndicator status={status} /> : null}
|
||||
{showSkipButton && (
|
||||
<TouchableOpacity style={styles.skipButton} onPress={handleSkipPress}>
|
||||
<Ionicons name="close" size={20} color={colors.textMuted} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</ThemedView>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { StyleSheet, View, Text, Animated, useColorScheme, TouchableOpacity } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { StyleSheet, View, Text, Animated, useColorScheme } from 'react-native';
|
||||
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
|
||||
type LoadingIndicatorProps = {
|
||||
status: string;
|
||||
showOfflineButton?: boolean;
|
||||
onOfflinePress?: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Loading indicator component.
|
||||
*/
|
||||
export default function LoadingIndicator({ status, showOfflineButton, onOfflinePress }: LoadingIndicatorProps): React.ReactNode {
|
||||
export default function LoadingIndicator({ status }: LoadingIndicatorProps): React.ReactNode {
|
||||
const colors = useColors();
|
||||
const dot1Anim = useRef(new Animated.Value(0)).current;
|
||||
const dot2Anim = useRef(new Animated.Value(0)).current;
|
||||
@@ -103,20 +100,6 @@ export default function LoadingIndicator({ status, showOfflineButton, onOfflineP
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 20,
|
||||
...StyleSheet.absoluteFillObject,
|
||||
},
|
||||
closeIconContainer: {
|
||||
position: 'absolute',
|
||||
right: 20,
|
||||
top: 60,
|
||||
zIndex: 10,
|
||||
},
|
||||
closeIcon: {
|
||||
padding: 12,
|
||||
borderRadius: 32,
|
||||
backgroundColor: colors.accentBackground,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.accentBorder,
|
||||
},
|
||||
contentContainer: {
|
||||
alignItems: 'center',
|
||||
@@ -156,13 +139,6 @@ export default function LoadingIndicator({ status, showOfflineButton, onOfflineP
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{showOfflineButton && (
|
||||
<View style={styles.closeIconContainer}>
|
||||
<TouchableOpacity style={styles.closeIcon} onPress={onOfflinePress}>
|
||||
<Ionicons name="close" size={24} color={colors.text} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.contentContainer}>
|
||||
<View style={styles.dotsContainer}>
|
||||
<Animated.View
|
||||
|
||||
Reference in New Issue
Block a user