diff --git a/native/account/registration/avatar-selection.react.js b/native/account/registration/avatar-selection.react.js --- a/native/account/registration/avatar-selection.react.js +++ b/native/account/registration/avatar-selection.react.js @@ -10,10 +10,11 @@ import RegistrationContentContainer from './registration-content-container.react.js'; import { RegistrationContext } from './registration-context.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; -import type { - CoolOrNerdMode, - AccountSelection, - AvatarData, +import { + type CoolOrNerdMode, + type AccountSelection, + type AvatarData, + ensAvatarSelection, } from './registration-types.js'; import { EditUserAvatarContext, @@ -31,12 +32,6 @@ }, }; -const ensDefaultSelection = { - needsUpload: false, - updateUserAvatarRequest: { type: 'ens' }, - clientAvatar: { type: 'ens' }, -}; - type Props = { +navigation: RegistrationNavigationProp<'AvatarSelection'>, +route: NavigationRoute<'AvatarSelection'>, @@ -44,11 +39,16 @@ function AvatarSelection(props: Props): React.Node { const { userSelections } = props.route.params; const { accountSelection } = userSelections; - const username = + const usernameOrETHAddress = accountSelection.accountType === 'username' ? accountSelection.username : accountSelection.address; + const registrationContext = React.useContext(RegistrationContext); + invariant(registrationContext, 'registrationContext should be set'); + const { cachedSelections, setCachedSelections, register } = + registrationContext; + const editUserAvatarContext = React.useContext(EditUserAvatarContext); invariant(editUserAvatarContext, 'editUserAvatarContext should be set'); const { setRegistrationMode } = editUserAvatarContext; @@ -58,35 +58,53 @@ ? accountSelection.avatarURI : undefined; - const [avatarData, setAvatarData] = React.useState( - prefetchedAvatarURI ? ensDefaultSelection : undefined, - ); + let initialAvatarData = cachedSelections.avatarData; + if (!initialAvatarData && prefetchedAvatarURI) { + initialAvatarData = ensAvatarSelection; + } + + const [avatarData, setAvatarData] = + React.useState(initialAvatarData); const setClientAvatarFromSelection = React.useCallback( (selection: UserAvatarSelection) => { if (selection.needsUpload) { - setAvatarData({ + const newAvatarData = { ...selection, clientAvatar: { type: 'image', uri: selection.mediaSelection.uri, }, - }); + }; + setAvatarData(newAvatarData); + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + avatarData: newAvatarData, + })); } else if (selection.updateUserAvatarRequest.type !== 'remove') { const clientRequest = selection.updateUserAvatarRequest; invariant( clientRequest.type !== 'image', 'image avatars need to be uploaded', ); - setAvatarData({ + const newAvatarData = { ...selection, clientAvatar: clientRequest, - }); + }; + setAvatarData(newAvatarData); + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + avatarData: newAvatarData, + })); } else { setAvatarData(undefined); + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + avatarData: undefined, + })); } }, - [], + [setCachedSelections], ); const [registrationInProgress, setRegistrationInProgress] = @@ -109,10 +127,6 @@ setClientAvatarFromSelection, ]); - const registrationContext = React.useContext(RegistrationContext); - invariant(registrationContext, 'registrationContext should be set'); - const { register } = registrationContext; - const onProceed = React.useCallback(async () => { setRegistrationInProgress(true); try { @@ -128,10 +142,10 @@ const clientAvatar = avatarData?.clientAvatar; const userInfoOverride = React.useMemo( () => ({ - username, + username: usernameOrETHAddress, avatar: clientAvatar, }), - [username, clientAvatar], + [usernameOrETHAddress, clientAvatar], ); const styles = useStyles(unboundStyles); diff --git a/native/account/registration/connect-ethereum.react.js b/native/account/registration/connect-ethereum.react.js --- a/native/account/registration/connect-ethereum.react.js +++ b/native/account/registration/connect-ethereum.react.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { Text, View } from 'react-native'; @@ -19,8 +20,12 @@ import RegistrationButton from './registration-button.react.js'; import RegistrationContainer from './registration-container.react.js'; import RegistrationContentContainer from './registration-content-container.react.js'; +import { RegistrationContext } from './registration-context.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; -import type { CoolOrNerdMode } from './registration-types.js'; +import { + type CoolOrNerdMode, + ensAvatarSelection, +} from './registration-types.js'; import { type NavigationRoute, ExistingEthereumAccountRouteName, @@ -52,6 +57,11 @@ function ConnectEthereum(props: Props): React.Node { const { params } = props.route; const { userSelections } = props.route.params; + + const registrationContext = React.useContext(RegistrationContext); + invariant(registrationContext, 'registrationContext should be set'); + const { setCachedSelections } = registrationContext; + const isNerdMode = userSelections.coolOrNerdMode === 'nerd'; const styles = useStyles(unboundStyles); @@ -157,13 +167,29 @@ const avatarURI = await avatarURIPromise; + const ethereumAccount = { + accountType: 'ethereum', + ...result, + avatarURI, + }; + + setCachedSelections(oldUserSelections => { + const base = { + ...oldUserSelections, + ethereumAccount, + }; + if (base.avatarData || !avatarURI) { + return base; + } + return { + ...base, + avatarData: ensAvatarSelection, + }; + }); + const newUserSelections = { ...userSelections, - accountSelection: { - accountType: 'ethereum', - ...result, - avatarURI, - }, + accountSelection: ethereumAccount, }; navigate<'AvatarSelection'>({ name: AvatarSelectionRouteName, @@ -174,6 +200,7 @@ userSelections, exactSearchUserCall, dispatchActionPromise, + setCachedSelections, navigate, ensCache, ], diff --git a/native/account/registration/cool-or-nerd-mode-selection.react.js b/native/account/registration/cool-or-nerd-mode-selection.react.js --- a/native/account/registration/cool-or-nerd-mode-selection.react.js +++ b/native/account/registration/cool-or-nerd-mode-selection.react.js @@ -8,6 +8,7 @@ import RegistrationButton from './registration-button.react.js'; import RegistrationContainer from './registration-container.react.js'; import RegistrationContentContainer from './registration-content-container.react.js'; +import { RegistrationContext } from './registration-context.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; import { RegistrationTile, @@ -25,14 +26,27 @@ +route: NavigationRoute<'CoolOrNerdModeSelection'>, }; function CoolOrNerdModeSelection(props: Props): React.Node { + const registrationContext = React.useContext(RegistrationContext); + invariant(registrationContext, 'registrationContext should be set'); + const { cachedSelections, setCachedSelections } = registrationContext; + const [currentSelection, setCurrentSelection] = - React.useState(); + React.useState(cachedSelections.coolOrNerdMode); + const selectCool = React.useCallback(() => { setCurrentSelection('cool'); - }, []); + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + coolOrNerdMode: 'cool', + })); + }, [setCachedSelections]); const selectNerd = React.useCallback(() => { setCurrentSelection('nerd'); - }, []); + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + coolOrNerdMode: 'nerd', + })); + }, [setCachedSelections]); const { navigate } = props.navigation; const onSubmit = React.useCallback(() => { diff --git a/native/account/registration/keyserver-selection.react.js b/native/account/registration/keyserver-selection.react.js --- a/native/account/registration/keyserver-selection.react.js +++ b/native/account/registration/keyserver-selection.react.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { Text } from 'react-native'; @@ -7,6 +8,7 @@ import RegistrationButton from './registration-button.react.js'; import RegistrationContainer from './registration-container.react.js'; import RegistrationContentContainer from './registration-content-container.react.js'; +import { RegistrationContext } from './registration-context.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; import RegistrationTextInput from './registration-text-input.react.js'; import { @@ -33,12 +35,26 @@ +navigation: RegistrationNavigationProp<'KeyserverSelection'>, +route: NavigationRoute<'KeyserverSelection'>, }; -// eslint-disable-next-line no-unused-vars function KeyserverSelection(props: Props): React.Node { - const [customKeyserver, setCustomKeyserver] = React.useState(''); + const registrationContext = React.useContext(RegistrationContext); + invariant(registrationContext, 'registrationContext should be set'); + const { cachedSelections, setCachedSelections } = registrationContext; + + const initialKeyserverUsername = cachedSelections.keyserverUsername; + const [customKeyserver, setCustomKeyserver] = React.useState( + initialKeyserverUsername === 'ashoat' ? '' : initialKeyserverUsername, + ); const customKeyserverTextInputRef = React.useRef(); - const [currentSelection, setCurrentSelection] = React.useState(); + let initialSelection; + if (initialKeyserverUsername === 'ashoat') { + initialSelection = 'ashoat'; + } else if (initialKeyserverUsername) { + initialSelection = 'custom'; + } + + const [currentSelection, setCurrentSelection] = + React.useState(initialSelection); const selectAshoat = React.useCallback(() => { setCurrentSelection('ashoat'); customKeyserverTextInputRef.current?.blur(); @@ -69,11 +85,15 @@ if (!keyserverUsername) { return; } + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + keyserverUsername, + })); navigate<'ConnectEthereum'>({ name: ConnectEthereumRouteName, params: { userSelections: { coolOrNerdMode, keyserverUsername } }, }); - }, [navigate, coolOrNerdMode, keyserverUsername]); + }, [navigate, coolOrNerdMode, keyserverUsername, setCachedSelections]); const styles = useStyles(unboundStyles); const colors = useColors(); diff --git a/native/account/registration/password-selection.react.js b/native/account/registration/password-selection.react.js --- a/native/account/registration/password-selection.react.js +++ b/native/account/registration/password-selection.react.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { View, Text, Platform } from 'react-native'; @@ -9,6 +10,7 @@ import RegistrationButton from './registration-button.react.js'; import RegistrationContainer from './registration-container.react.js'; import RegistrationContentContainer from './registration-content-container.react.js'; +import { RegistrationContext } from './registration-context.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; import RegistrationTextInput from './registration-text-input.react.js'; import type { CoolOrNerdMode } from './registration-types.js'; @@ -34,8 +36,16 @@ +route: NavigationRoute<'PasswordSelection'>, }; function PasswordSelection(props: Props): React.Node { - const [password, setPassword] = React.useState(''); - const [confirmPassword, setConfirmPassword] = React.useState(''); + const registrationContext = React.useContext(RegistrationContext); + invariant(registrationContext, 'registrationContext should be set'); + const { cachedSelections, setCachedSelections } = registrationContext; + + const [password, setPassword] = React.useState( + cachedSelections.password ?? '', + ); + const [confirmPassword, setConfirmPassword] = React.useState( + cachedSelections.password ?? '', + ); const passwordsMatch = password === confirmPassword; const passwordIsEmpty = password === ''; @@ -76,11 +86,21 @@ password, }, }; + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + password, + })); navigate<'AvatarSelection'>({ name: AvatarSelectionRouteName, params: { userSelections: newUserSelections }, }); - }, [checkPasswordValidity, userSelections, password, navigate]); + }, [ + checkPasswordValidity, + userSelections, + password, + setCachedSelections, + navigate, + ]); const styles = useStyles(unboundStyles); let errorText; @@ -142,6 +162,8 @@ [passwordLength, confirmPasswordEmpty], ); + const shouldAutoFocus = React.useRef(!cachedSelections.password); + /* eslint-disable react-hooks/rules-of-hooks */ if (Platform.OS === 'android') { // It's okay to call this hook conditionally because @@ -149,12 +171,16 @@ React.useEffect(() => { (async () => { await sleep(250); - passwordInputRef.current?.focus(); + if (shouldAutoFocus.current) { + passwordInputRef.current?.focus(); + } })(); }, []); } /* eslint-enable react-hooks/rules-of-hooks */ + const autoFocus = Platform.OS !== 'android' && shouldAutoFocus.current; + return ( @@ -163,10 +189,7 @@ value={password} onChangeText={onChangePasswordInput} placeholder="Password" - autoFocus={Platform.select({ - android: false, - default: true, - })} + autoFocus={autoFocus} secureTextEntry={true} textContentType="newPassword" autoComplete="password-new" diff --git a/native/account/registration/registration-context-provider.react.js b/native/account/registration/registration-context-provider.react.js --- a/native/account/registration/registration-context-provider.react.js +++ b/native/account/registration/registration-context-provider.react.js @@ -4,17 +4,25 @@ import { RegistrationContext } from './registration-context.js'; import { useRegistrationServerCall } from './registration-server-call.js'; +import type { CachedUserSelections } from './registration-types.js'; + +const emptyObj: CachedUserSelections = Object.freeze({}); type Props = { +children: React.Node, }; function RegistrationContextProvider(props: Props): React.Node { + const [cachedSelections, setCachedSelections] = + React.useState(emptyObj); + const registrationServerCall = useRegistrationServerCall(); const contextValue = React.useMemo( () => ({ register: registrationServerCall, + cachedSelections, + setCachedSelections, }), - [registrationServerCall], + [registrationServerCall, cachedSelections], ); return ( diff --git a/native/account/registration/registration-context.js b/native/account/registration/registration-context.js --- a/native/account/registration/registration-context.js +++ b/native/account/registration/registration-context.js @@ -2,10 +2,17 @@ import * as React from 'react'; -import type { RegistrationServerCallInput } from './registration-types.js'; +import type { SetState } from 'lib/types/hook-types.js'; + +import type { + RegistrationServerCallInput, + CachedUserSelections, +} from './registration-types.js'; export type RegistrationContextType = { +register: RegistrationServerCallInput => Promise, + +cachedSelections: CachedUserSelections, + +setCachedSelections: SetState, }; const RegistrationContext: React.Context = diff --git a/native/account/registration/registration-types.js b/native/account/registration/registration-types.js --- a/native/account/registration/registration-types.js +++ b/native/account/registration/registration-types.js @@ -43,3 +43,18 @@ +accountSelection: AccountSelection, +avatarData: ?AvatarData, }; + +export type CachedUserSelections = { + +coolOrNerdMode?: CoolOrNerdMode, + +keyserverUsername?: string, + +username?: string, + +password?: string, + +avatarData?: ?AvatarData, + +ethereumAccount?: EthereumAccountSelection, +}; + +export const ensAvatarSelection: AvatarData = { + needsUpload: false, + updateUserAvatarRequest: { type: 'ens' }, + clientAvatar: { type: 'ens' }, +}; diff --git a/native/account/registration/username-selection.react.js b/native/account/registration/username-selection.react.js --- a/native/account/registration/username-selection.react.js +++ b/native/account/registration/username-selection.react.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { View, Text } from 'react-native'; @@ -18,6 +19,7 @@ import RegistrationButton from './registration-button.react.js'; import RegistrationContainer from './registration-container.react.js'; import RegistrationContentContainer from './registration-content-container.react.js'; +import { RegistrationContext } from './registration-context.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; import RegistrationTextInput from './registration-text-input.react.js'; import type { CoolOrNerdMode } from './registration-types.js'; @@ -46,7 +48,13 @@ +route: NavigationRoute<'UsernameSelection'>, }; function UsernameSelection(props: Props): React.Node { - const [username, setUsername] = React.useState(''); + const registrationContext = React.useContext(RegistrationContext); + invariant(registrationContext, 'registrationContext should be set'); + const { cachedSelections, setCachedSelections } = registrationContext; + + const [username, setUsername] = React.useState( + cachedSelections.username ?? '', + ); const validUsername = username.search(validUsernameRegex) > -1; const [usernameError, setUsernameError] = React.useState(); @@ -78,6 +86,10 @@ } setUsernameError(undefined); + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + username, + })); navigate<'PasswordSelection'>({ name: PasswordSelectionRouteName, params: { @@ -92,6 +104,7 @@ username, exactSearchUserCall, dispatchActionPromise, + setCachedSelections, navigate, userSelections, ]); @@ -140,6 +153,7 @@ ); } + const shouldAutoFocus = React.useRef(!cachedSelections.username); return ( @@ -148,7 +162,7 @@ value={username} onChangeText={setUsername} placeholder="Username" - autoFocus={true} + autoFocus={shouldAutoFocus.current} autoCorrect={false} autoCapitalize="none" keyboardType="ascii-capable"