diff --git a/lib/utils/services-utils.js b/lib/utils/services-utils.js --- a/lib/utils/services-utils.js +++ b/lib/utils/services-utils.js @@ -21,7 +21,7 @@ // If this is true, then we're using the login 2.0, which means that a user // can either restore an account (primary login) or log in using the QR code // (secondary login). -const usingRestoreFlow = false; +const usingRestoreFlow = true; function handleHTTPResponseError(response: Response): void { if (!response.ok) { diff --git a/native/account/logged-out-modal-wrapper.react.js b/native/account/logged-out-modal-wrapper.react.js new file mode 100644 --- /dev/null +++ b/native/account/logged-out-modal-wrapper.react.js @@ -0,0 +1,24 @@ +// @flow + +import * as React from 'react'; + +import LoggedOutModal from './logged-out-modal.react.js'; +import type { RootNavigationProp } from '../navigation/root-navigator.react'; +import type { NavigationRoute } from '../navigation/route-names'; + +type Props = { + +navigation: RootNavigationProp<'LoggedOutModal'>, + +route: NavigationRoute<'LoggedOutModal'>, +}; + +function LoggedOutModalWrapper(props: Props): React.Node { + return ( + + ); +} + +export default LoggedOutModalWrapper; diff --git a/native/account/logged-out-modal.react.js b/native/account/logged-out-modal.react.js --- a/native/account/logged-out-modal.react.js +++ b/native/account/logged-out-modal.react.js @@ -37,6 +37,7 @@ import LogInPanel from './log-in-panel.react.js'; import type { LogInState } from './log-in-panel.react.js'; import LoggedOutStaffInfo from './logged-out-staff-info.react.js'; +import type { SignInNavigationProp } from './sign-in-navigator.react'; import { authoritativeKeyserverID } from '../authoritative-keyserver.js'; import KeyboardAvoidingView from '../components/keyboard-avoiding-view.react.js'; import ConnectedStatusBar from '../connected-status-bar.react.js'; @@ -101,17 +102,19 @@ // prettier-ignore function getPanelOpacity( modeValue /*: string */, - finishResettingToPrompt/*: () => void */, + finishResetting/*: () => void */, ) /*: number */ { 'worklet'; const targetPanelOpacity = - modeValue === 'loading' || modeValue === 'prompt' ? 0 : 1; + modeValue === 'loading' || modeValue === 'prompt' || modeValue === 'restore' + ? 0 + : 1; return withTiming( targetPanelOpacity, timingConfig, (succeeded /*?: boolean */) => { if (succeeded && targetPanelOpacity === 0) { - runOnJS(finishResettingToPrompt)(); + runOnJS(finishResetting)(); } }, ); @@ -240,11 +243,19 @@ +nextMode: LoggedOutMode, }; -type Props = { - +navigation: RootNavigationProp<'LoggedOutModal'>, - +route: NavigationRoute<'LoggedOutModal'>, -}; +type Props = + | { + +navigation: RootNavigationProp<'LoggedOutModal'>, + +route: NavigationRoute<'LoggedOutModal'>, + +defaultMode: 'prompt', + } + | { + +navigation: SignInNavigationProp<'RestoreScreen'>, + +route: NavigationRoute<'RestoreScreen'>, + +defaultMode: 'restore', + }; function LoggedOutModal(props: Props) { + const { defaultMode } = props; const mountedRef = React.useRef(false); React.useEffect(() => { mountedRef.current = true; @@ -276,7 +287,7 @@ ); const persistedStateLoaded = usePersistedStateLoaded(); - const initialMode = persistedStateLoaded ? 'prompt' : 'loading'; + const initialMode = persistedStateLoaded ? defaultMode : 'loading'; const [mode, baseSetMode] = React.useState(() => ({ curMode: initialMode, nextMode: initialMode, @@ -298,16 +309,16 @@ const modeValue = useSharedValue(initialMode); const buttonOpacity = useSharedValue(persistedStateLoaded ? 1 : 0); - const onPrompt = mode.curMode === 'prompt'; - const prevOnPromptRef = React.useRef(onPrompt); + const onDefault = mode.curMode === defaultMode; + const prevOnDefaultRef = React.useRef(onDefault); React.useEffect(() => { - if (onPrompt && !prevOnPromptRef.current) { + if (onDefault && !prevOnDefaultRef.current) { buttonOpacity.value = withTiming(1, { easing: Easing.out(Easing.ease), }); } - prevOnPromptRef.current = onPrompt; - }, [onPrompt, buttonOpacity]); + prevOnDefaultRef.current = onDefault; + }, [buttonOpacity, onDefault]); const curContentHeight = dimensions.safeAreaHeight; const prevContentHeightRef = React.useRef(curContentHeight); @@ -328,20 +339,20 @@ [setMode, modeValue], ); - const goBackToPrompt = React.useCallback(() => { - nextModeRef.current = 'prompt'; - setMode({ nextMode: 'prompt' }); - modeValue.value = 'prompt'; + const goBackToDefault = React.useCallback(() => { + nextModeRef.current = defaultMode; + setMode({ nextMode: defaultMode }); + modeValue.value = defaultMode; Keyboard.dismiss(); - }, [setMode, modeValue]); + }, [defaultMode, setMode, modeValue]); const loadingCompleteRef = React.useRef(persistedStateLoaded); React.useEffect(() => { if (!loadingCompleteRef.current && persistedStateLoaded) { - combinedSetMode('prompt'); + combinedSetMode(defaultMode); loadingCompleteRef.current = true; } - }, [persistedStateLoaded, combinedSetMode]); + }, [combinedSetMode, defaultMode, persistedStateLoaded]); const [activeAlert, setActiveAlert] = React.useState(false); @@ -366,19 +377,19 @@ const temporarilyHiddenPassword = React.useRef(); const curLogInPassword = logInState.passwordInputText; - const resetToPrompt = React.useCallback(() => { - if (nextModeRef.current === 'prompt') { + const resetToDefault = React.useCallback(() => { + if (nextModeRef.current === defaultMode) { return false; } if (Platform.OS === 'ios' && curLogInPassword) { temporarilyHiddenPassword.current = curLogInPassword; setLogInState({ passwordInputText: null }); } - goBackToPrompt(); + goBackToDefault(); return true; - }, [goBackToPrompt, curLogInPassword, setLogInState]); + }, [curLogInPassword, defaultMode, goBackToDefault, setLogInState]); - const finishResettingToPrompt = React.useCallback(() => { + const finishResetting = React.useCallback(() => { setMode({ curMode: nextModeRef.current }); if (temporarilyHiddenPassword.current) { setLogInState({ passwordInputText: temporarilyHiddenPassword.current }); @@ -390,11 +401,11 @@ if (!isForeground) { return undefined; } - BackHandler.addEventListener('hardwareBackPress', resetToPrompt); + BackHandler.addEventListener('hardwareBackPress', resetToDefault); return () => { - BackHandler.removeEventListener('hardwareBackPress', resetToPrompt); + BackHandler.removeEventListener('hardwareBackPress', resetToDefault); }; - }, [isForeground, resetToPrompt]); + }, [isForeground, resetToDefault]); const rehydrateConcluded = useSelector( state => !!(state._persist && state._persist.rehydrated && navContext), @@ -448,7 +459,7 @@ }, [navigate]); const opacityStyle = useAnimatedStyle(() => ({ - opacity: getPanelOpacity(modeValue.value, finishResettingToPrompt), + opacity: getPanelOpacity(modeValue.value, finishResetting), })); const styles = useStyles(unboundStyles); @@ -641,7 +652,7 @@ Comm - + @@ -653,7 +664,7 @@ animatedContentStyle, styles.header, backButtonStyle, - resetToPrompt, + resetToDefault, panel, ], ); @@ -666,11 +677,11 @@ } return ( ); - }, [curModeIsSIWE, goBackToPrompt, nextModeIsPrompt]); + }, [curModeIsSIWE, goBackToDefault, nextModeIsPrompt]); const splashStyle = useSelector(splashStyleSelector); const backgroundStyle = React.useMemo( diff --git a/native/account/qr-code-screen.react.js b/native/account/qr-code-screen.react.js --- a/native/account/qr-code-screen.react.js +++ b/native/account/qr-code-screen.react.js @@ -8,9 +8,12 @@ import { qrCodeLinkURL } from 'lib/facts/links.js'; import { platformToIdentityDeviceType } from 'lib/types/identity-service-types.js'; import { getConfig } from 'lib/utils/config.js'; +import { usingRestoreFlow } from 'lib/utils/services-utils.js'; import type { SignInNavigationProp } from './sign-in-navigator.react.js'; +import LinkButton from '../components/link-button.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; +import { RestoreScreenRouteName } from '../navigation/route-names.js'; import { useStyles } from '../themes/colors.js'; type QRCodeScreenProps = { @@ -18,7 +21,6 @@ +route: NavigationRoute<'QRCodeScreen'>, }; -// eslint-disable-next-line no-unused-vars function QRCodeScreen(props: QRCodeScreenProps): React.Node { const { qrData, generateQRCode } = useQRAuthContext(); @@ -37,34 +39,56 @@ }, [platform, qrData]); const styles = useStyles(unboundStyles); + + let primaryRestoreButton = null; + const goToRestoreFlow = React.useCallback(() => { + props.navigation.navigate(RestoreScreenRouteName); + }, [props.navigation]); + if (usingRestoreFlow) { + primaryRestoreButton = ( + + + + ); + } + return ( - - Log in to Comm - - Open the Comm app on your logged-in phone and scan the QR code below - - - - How to find the scanner: - - Go to - Profile - - - Select - Linked devices - - - Click - Add - on the top right + + + Log in to Comm + + Open the Comm app on your logged-in phone and scan the QR code below + + + How to find the scanner: + + Go to + Profile + + + Select + Linked devices + + + Click + Add + on the top right + + + {primaryRestoreButton} ); } const unboundStyles = { + screenContainer: { + flex: 1, + }, container: { flex: 1, alignItems: 'center', @@ -104,6 +128,10 @@ instructionsBold: { fontWeight: 'bold', }, + primaryRestoreButton: { + alignItems: 'center', + marginBottom: 20, + }, }; export default QRCodeScreen; diff --git a/native/account/restore-screen-wrapper.react.js b/native/account/restore-screen-wrapper.react.js new file mode 100644 --- /dev/null +++ b/native/account/restore-screen-wrapper.react.js @@ -0,0 +1,24 @@ +// @flow + +import * as React from 'react'; + +import LoggedOutModal from './logged-out-modal.react.js'; +import type { SignInNavigationProp } from './sign-in-navigator.react'; +import type { NavigationRoute } from '../navigation/route-names'; + +type Props = { + +navigation: SignInNavigationProp<'RestoreScreen'>, + +route: NavigationRoute<'RestoreScreen'>, +}; + +function RestoreScreen(props: Props): React.Node { + return ( + + ); +} + +export default RestoreScreen; diff --git a/native/account/sign-in-navigator.react.js b/native/account/sign-in-navigator.react.js --- a/native/account/sign-in-navigator.react.js +++ b/native/account/sign-in-navigator.react.js @@ -9,11 +9,13 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import QRCodeScreen from './qr-code-screen.react.js'; +import RestoreScreen from './restore-screen-wrapper.react.js'; import type { RootNavigationProp } from '../navigation/root-navigator.react.js'; import { type ScreenParamList, type SignInParamList, QRCodeScreenRouteName, + RestoreScreenRouteName, } from '../navigation/route-names.js'; import { useStyles, useColors } from '../themes/colors.js'; @@ -58,6 +60,10 @@ name={QRCodeScreenRouteName} component={QRCodeScreen} /> + ); diff --git a/native/navigation/root-navigator.react.js b/native/navigation/root-navigator.react.js --- a/native/navigation/root-navigator.react.js +++ b/native/navigation/root-navigator.react.js @@ -58,7 +58,7 @@ RestoreSIWEBackupRouteName, LinkedDevicesBottomSheetRouteName, } from './route-names.js'; -import LoggedOutModal from '../account/logged-out-modal.react.js'; +import LoggedOutModalWrapper from '../account/logged-out-modal-wrapper.react.js'; import CreateMissingSIWEBackupMessage from '../account/registration/missing-registration-data/missing-siwe-backup-message.react.js'; import RegistrationNavigator from '../account/registration/registration-navigator.react.js'; import SignInNavigator from '../account/sign-in-navigator.react.js'; @@ -205,7 +205,7 @@