mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-19 13:57:18 -04:00
Update offline banner UX (#1267)
This commit is contained in:
committed by
Leendert de Borst
parent
7cb7c02bb2
commit
ad2028e473
@@ -22,7 +22,7 @@ export default function Initialize() : React.ReactNode {
|
||||
const hasInitialized = useRef(false);
|
||||
const offlineButtonTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const { t } = useTranslation();
|
||||
const { initializeAuth } = useAuth();
|
||||
const { initializeAuth, setOfflineMode } = useAuth();
|
||||
const { syncVault } = useVaultSync();
|
||||
const dbContext = useDb();
|
||||
const webApi = useWebApi();
|
||||
@@ -81,6 +81,9 @@ export default function Initialize() : React.ReactNode {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set offline mode
|
||||
setOfflineMode(true);
|
||||
|
||||
// Success - navigate to credentials
|
||||
router.replace('/(tabs)/credentials');
|
||||
} catch {
|
||||
@@ -94,13 +97,26 @@ export default function Initialize() : React.ReactNode {
|
||||
* Handle retrying the connection.
|
||||
*/
|
||||
onPress: () : void => {
|
||||
// Re-trigger initialization
|
||||
setStatus(t('app.status.retryingConnection'));
|
||||
setShowOfflineButton(false);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
offlineButtonTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the hasInitialized flag and navigate to the same route
|
||||
* to force a re-render and trigger the useEffect again
|
||||
*/
|
||||
hasInitialized.current = false;
|
||||
router.replace('/initialize');
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
}, [dbContext, router, initializeAuth, t]);
|
||||
}, [dbContext, router, initializeAuth, t, setOfflineMode]);
|
||||
|
||||
useEffect(() => {
|
||||
// Ensure this only runs once.
|
||||
@@ -187,7 +203,7 @@ export default function Initialize() : React.ReactNode {
|
||||
if (message === t('vault.checkingVaultUpdates')) {
|
||||
offlineButtonTimeoutRef.current = setTimeout(() => {
|
||||
setShowOfflineButton(true);
|
||||
}, 2000) as NodeJS.Timeout;
|
||||
}, 2000) as unknown as NodeJS.Timeout;
|
||||
} else {
|
||||
setShowOfflineButton(false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Href, router } from 'expo-router';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StyleSheet, View, Alert } from 'react-native';
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
/**
|
||||
* Handle offline scenario - show alert with options to open local vault or retry sync.
|
||||
*/
|
||||
const handleOfflineFlow = (): void => {
|
||||
const handleOfflineFlow = useCallback((): void => {
|
||||
Alert.alert(
|
||||
t('app.alerts.syncIssue'),
|
||||
t('app.alerts.syncIssueMessage'),
|
||||
@@ -125,13 +125,26 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
* Handle retrying the connection.
|
||||
*/
|
||||
onPress: () : void => {
|
||||
// Re-trigger initialization
|
||||
setStatus(t('app.status.retryingConnection'));
|
||||
setShowOfflineButton(false);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (offlineButtonTimeoutRef.current) {
|
||||
clearTimeout(offlineButtonTimeoutRef.current);
|
||||
offlineButtonTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the hasInitialized flag and navigate to reinitialize route
|
||||
* to force a re-render and trigger the useEffect again
|
||||
*/
|
||||
hasInitialized.current = false;
|
||||
router.replace('/reinitialize');
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
};
|
||||
}, [authContext, dbContext, t, router]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasInitialized.current) {
|
||||
@@ -258,7 +271,7 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
if (message === t('vault.checkingVaultUpdates')) {
|
||||
offlineButtonTimeoutRef.current = setTimeout(() => {
|
||||
setShowOfflineButton(true);
|
||||
}, 2000) as NodeJS.Timeout;
|
||||
}, 2000) as unknown as NodeJS.Timeout;
|
||||
} else {
|
||||
setShowOfflineButton(false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { useState } from 'react';
|
||||
import { ActivityIndicator, StyleSheet, View } from 'react-native';
|
||||
import Toast from 'react-native-toast-message';
|
||||
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
@@ -20,6 +21,7 @@ export function OfflineBanner(): React.ReactNode {
|
||||
const colors = useColors();
|
||||
const { t } = useTranslation();
|
||||
const { syncVault } = useVaultSync();
|
||||
const [isRetrying, setIsRetrying] = useState(false);
|
||||
|
||||
if (!isOffline) {
|
||||
return null;
|
||||
@@ -30,47 +32,62 @@ export function OfflineBanner(): React.ReactNode {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const handleRetry = async (): Promise<void> => {
|
||||
await syncVault({
|
||||
/**
|
||||
* Handle status updates during sync.
|
||||
* @param {string} _message - The status message
|
||||
*/
|
||||
onStatus: (_message: string) => {
|
||||
// Status updates will be shown in the toast
|
||||
},
|
||||
/**
|
||||
* Handle successful sync.
|
||||
*/
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1: t('app.offline.backOnline'),
|
||||
position: 'bottom'
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Handle offline.
|
||||
*/
|
||||
onOffline: () => {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: t('app.offline.stillOffline'),
|
||||
position: 'bottom'
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Handle sync errors.
|
||||
* @param {string} error - The error message
|
||||
*/
|
||||
onError: (error: string) => {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: t('app.offline.stillOffline'),
|
||||
text2: error,
|
||||
position: 'bottom'
|
||||
});
|
||||
}
|
||||
});
|
||||
// Prevent multiple simultaneous retry attempts
|
||||
if (isRetrying) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRetrying(true);
|
||||
|
||||
try {
|
||||
await syncVault({
|
||||
/**
|
||||
* Handle status updates during sync.
|
||||
* @param {string} _message - The status message
|
||||
*/
|
||||
onStatus: (_message: string) => {
|
||||
// Status updates will be shown in the toast
|
||||
},
|
||||
/**
|
||||
* Handle successful sync.
|
||||
*/
|
||||
onSuccess: () => {
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1: t('app.offline.backOnline'),
|
||||
position: 'bottom'
|
||||
});
|
||||
setIsRetrying(false);
|
||||
},
|
||||
/**
|
||||
* Handle offline.
|
||||
*/
|
||||
onOffline: () => {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: t('app.offline.stillOffline'),
|
||||
position: 'bottom'
|
||||
});
|
||||
setIsRetrying(false);
|
||||
},
|
||||
/**
|
||||
* Handle sync errors.
|
||||
* @param {string} error - The error message
|
||||
*/
|
||||
onError: (error: string) => {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: t('app.offline.stillOffline'),
|
||||
text2: error,
|
||||
position: 'bottom'
|
||||
});
|
||||
setIsRetrying(false);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// In case of unexpected errors, ensure loading state is cleared
|
||||
setIsRetrying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -88,6 +105,10 @@ export function OfflineBanner(): React.ReactNode {
|
||||
retryButton: {
|
||||
marginLeft: 8,
|
||||
padding: 4,
|
||||
minWidth: 28,
|
||||
minHeight: 28,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
text: {
|
||||
color: colors.primarySurfaceText,
|
||||
@@ -106,8 +127,13 @@ export function OfflineBanner(): React.ReactNode {
|
||||
<RobustPressable
|
||||
style={styles.retryButton}
|
||||
onPress={handleRetry}
|
||||
disabled={isRetrying}
|
||||
>
|
||||
<Ionicons name="refresh" size={20} color={colors.primarySurfaceText} />
|
||||
{isRetrying ? (
|
||||
<ActivityIndicator size="small" color={colors.primarySurfaceText} />
|
||||
) : (
|
||||
<Ionicons name="refresh" size={20} color={colors.primarySurfaceText} />
|
||||
)}
|
||||
</RobustPressable>
|
||||
</View>
|
||||
</ThemedView>
|
||||
|
||||
Reference in New Issue
Block a user