diff --git a/apps/mobile-app/app/initialize.tsx b/apps/mobile-app/app/initialize.tsx index d679e5537..dca834dee 100644 --- a/apps/mobile-app/app/initialize.tsx +++ b/apps/mobile-app/app/initialize.tsx @@ -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(null); + const skipButtonTimeoutRef = useRef(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 ( - {status ? ( - - ) : null} + + + + {showSkipButton && ( + + + + )} ); } \ No newline at end of file diff --git a/apps/mobile-app/app/reinitialize.tsx b/apps/mobile-app/app/reinitialize.tsx index 24f9b0c67..8aa3a071d 100644 --- a/apps/mobile-app/app/reinitialize.tsx +++ b/apps/mobile-app/app/reinitialize.tsx @@ -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(null); + const skipButtonTimeoutRef = useRef(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; 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 ( - - {t('app.reinitialize.vaultAutoLockedMessage')} - {t('app.reinitialize.attemptingToUnlockMessage')} - {status ? ( - - ) : null} + + + {t('app.reinitialize.vaultAutoLockedMessage')} + {t('app.reinitialize.attemptingToUnlockMessage')} + {status ? : null} + {showSkipButton && ( + + + + )} + ); diff --git a/apps/mobile-app/components/LoadingIndicator.tsx b/apps/mobile-app/components/LoadingIndicator.tsx index e40b19579..142570027 100644 --- a/apps/mobile-app/components/LoadingIndicator.tsx +++ b/apps/mobile-app/components/LoadingIndicator.tsx @@ -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 ( - {showOfflineButton && ( - - - - - - )}