diff --git a/native/account/qr-auth-progress-screen.react.js b/native/account/qr-auth-progress-screen.react.js --- a/native/account/qr-auth-progress-screen.react.js +++ b/native/account/qr-auth-progress-screen.react.js @@ -71,7 +71,10 @@ React.useCallback(() => { if (userDataRestoreStatus === 'user_data_restore_failed') { props.navigation.navigate(RestoreBackupErrorScreenRouteName, { - deviceType: 'secondary', + errorInfo: { + type: 'restore_failed', + restoreType: 'secondary', + }, }); } }, [props.navigation, userDataRestoreStatus]), diff --git a/native/account/restore-backup-error-screen.react.js b/native/account/restore-backup-error-screen.react.js --- a/native/account/restore-backup-error-screen.react.js +++ b/native/account/restore-backup-error-screen.react.js @@ -8,8 +8,12 @@ resetBackupRestoreStateActionType, } from 'lib/actions/backup-actions.js'; import { logOutActionTypes, useLogOut } from 'lib/actions/user-actions.js'; +import { useDeviceKind } from 'lib/hooks/primary-device-hooks.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; -import { getMessageForException } from 'lib/utils/errors.js'; +import { + getMessageForException, + BackupIsNewerError, +} from 'lib/utils/errors.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; @@ -18,42 +22,55 @@ import AuthContentContainer from './auth-components/auth-content-container.react.js'; import type { AuthNavigationProp } from './registration/auth-navigator.react.js'; import PrimaryButton from '../components/primary-button.react.js'; +import type { NavAction } from '../navigation/navigation-context.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { LoggedOutModalRouteName } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; +import { backupIsNewerThanAppAlertDetails } from '../utils/alert-messages.js'; import Alert from '../utils/alert.js'; -type Props = { +type ScreenProps = { +navigation: AuthNavigationProp<'RestoreBackupErrorScreen'>, +route: NavigationRoute<'RestoreBackupErrorScreen'>, }; +type ErrorInfo = + | { + +type: 'restore_failed', + +restoreType?: 'primary' | 'secondary', + } + | { + +type: 'generic_error', + +errorTitle: string, + +errorMessage: string, + +rawErrorMessage?: ?string, + +returnNavAction?: ?NavAction, + }; + export type RestoreBackupErrorScreenParams = { - +deviceType: 'primary' | 'secondary', - +errorDetails?: ?string, + +errorInfo: ErrorInfo, }; -function RestoreBackupErrorScreen(props: Props): React.Node { +type RestorationFailedViewProps = { + +navigation: ScreenProps['navigation'], + +error: ?Error, + +restoreType?: 'primary' | 'secondary', +}; +function RestorationFailedView(props: RestorationFailedViewProps): React.Node { const styles = useStyles(unboundStyles); - const { deviceType, errorDetails: errorDetailsProp } = props.route.params; + const { error, navigation } = props; - const storedError = useSelector(state => - state.restoreBackupState.status === 'user_data_restore_failed' - ? state.restoreBackupState.payload.error - : null, - ); const errorDetails = React.useMemo(() => { - if (errorDetailsProp) { - return errorDetailsProp; - } else if (storedError) { - const messageForException = getMessageForException(storedError); - return messageForException ?? 'unknown_error'; + const errorMessage = getMessageForException(error); + if (!errorMessage && navigation.isFocused()) { + console.warn('Restore error screen shown but no error is stored'); } + return errorMessage ?? 'unknown_error'; + }, [error, navigation]); - console.warn('Restore error screen shown but no error details provided'); - return 'unknown_error'; - }, [errorDetailsProp, storedError]); + const deviceKind = useDeviceKind(); + const deviceType = props.restoreType ?? deviceKind; const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); @@ -91,8 +108,8 @@ }); } - props.navigation.navigate(LoggedOutModalRouteName); - }, [props.navigation, loggedIn, logOut, dispatch, dispatchActionPromise]); + navigation.navigate(LoggedOutModalRouteName); + }, [navigation, loggedIn, logOut, dispatch, dispatchActionPromise]); const deviceTypeWarning = React.useMemo(() => { if (deviceType === 'secondary') { @@ -141,6 +158,94 @@ ); } +type SimpleErrorViewProps = { + +title: string, + +message: string, + +rawErrorMessage?: ?string, + +onTryAgain?: () => void, +}; +function SimpleErrorView(props: SimpleErrorViewProps): React.Node { + const { onTryAgain, title, message, rawErrorMessage } = props; + + const styles = useStyles(unboundStyles); + + let rawErrorContents; + if (rawErrorMessage) { + rawErrorContents = ( + + Error message: + {rawErrorMessage} + + ); + } + + let tryAgainButton; + if (onTryAgain) { + tryAgainButton = ( + + ); + } + + return ( + + + {title} + {message} + {rawErrorContents} + + {tryAgainButton} + + ); +} + +function RestoreBackupErrorScreen(props: ScreenProps): React.Node { + const { navigation } = props; + const { errorInfo } = props.route.params; + + const userDataError = useSelector(state => + state.restoreBackupState.status === 'user_data_restore_failed' + ? state.restoreBackupState.payload.error + : null, + ); + + const handleTryAgain = React.useCallback(() => { + if (!errorInfo.returnNavAction) { + return; + } + navigation.dispatch(errorInfo.returnNavAction); + }, [errorInfo.returnNavAction, navigation]); + + if (errorInfo.type === 'restore_failed') { + const { restoreType } = errorInfo; + + if (userDataError && userDataError instanceof BackupIsNewerError) { + return ( + + ); + } + + return ( + + ); + } + + const { errorTitle, errorMessage } = errorInfo; + return ( + + ); +} + const unboundStyles = { header: { fontSize: 24, diff --git a/native/account/restore-backup-screen.react.js b/native/account/restore-backup-screen.react.js --- a/native/account/restore-backup-screen.react.js +++ b/native/account/restore-backup-screen.react.js @@ -75,7 +75,10 @@ }); if (isRestoreError && fullBackupSupport) { props.navigation.navigate(RestoreBackupErrorScreenRouteName, { - deviceType: 'primary', + errorInfo: { + type: 'restore_failed', + restoreType: 'primary', + }, }); return removeListener; } @@ -147,8 +150,10 @@ alertDetails = userKeysRestoreErrorAlertDetails; } else if (step === 'user_data_restore') { props.navigation.navigate(RestoreBackupErrorScreenRouteName, { - deviceType: 'primary', - errorDetails: messageForException, + errorInfo: { + type: 'restore_failed', + restoreType: 'primary', + }, }); return; }