Changeset View
Changeset View
Standalone View
Standalone View
native/account/registration/password-selection.react.js
// @flow | // @flow | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { View, Text, Platform } from 'react-native'; | import { View, Text, Platform } from 'react-native'; | ||||
import sleep from 'lib/utils/sleep.js'; | import sleep from 'lib/utils/sleep.js'; | ||||
import RegistrationButtonContainer from './registration-button-container.react.js'; | import RegistrationButtonContainer from './registration-button-container.react.js'; | ||||
import RegistrationButton from './registration-button.react.js'; | import RegistrationButton from './registration-button.react.js'; | ||||
import RegistrationContainer from './registration-container.react.js'; | import RegistrationContainer from './registration-container.react.js'; | ||||
import RegistrationContentContainer from './registration-content-container.react.js'; | import RegistrationContentContainer from './registration-content-container.react.js'; | ||||
import type { RegistrationNavigationProp } from './registration-navigator.react.js'; | import type { RegistrationNavigationProp } from './registration-navigator.react.js'; | ||||
import RegistrationTextInput from './registration-text-input.react.js'; | import RegistrationTextInput from './registration-text-input.react.js'; | ||||
import type { CoolOrNerdMode } from './registration-types.js'; | import type { CoolOrNerdMode } from './registration-types.js'; | ||||
import type { NavigationRoute } from '../../navigation/route-names.js'; | import type { NavigationRoute } from '../../navigation/route-names.js'; | ||||
import { useStyles } from '../../themes/colors.js'; | import { useStyles } from '../../themes/colors.js'; | ||||
import type { KeyPressEvent } from '../../types/react-native.js'; | |||||
export type PasswordSelectionParams = { | export type PasswordSelectionParams = { | ||||
+userSelections: { | +userSelections: { | ||||
+coolOrNerdMode: CoolOrNerdMode, | +coolOrNerdMode: CoolOrNerdMode, | ||||
+keyserverUsername: string, | +keyserverUsername: string, | ||||
+username: string, | +username: string, | ||||
}, | }, | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | if (passwordError === 'passwords_dont_match') { | ||||
errorText = <Text style={styles.errorText}>Password cannot be empty</Text>; | errorText = <Text style={styles.errorText}>Password cannot be empty</Text>; | ||||
} | } | ||||
const confirmPasswordInputRef = React.useRef(); | const confirmPasswordInputRef = React.useRef(); | ||||
const focusConfirmPasswordInput = React.useCallback(() => { | const focusConfirmPasswordInput = React.useCallback(() => { | ||||
confirmPasswordInputRef.current?.focus(); | confirmPasswordInputRef.current?.focus(); | ||||
}, []); | }, []); | ||||
/* eslint-disable react-hooks/rules-of-hooks */ | const iosPasswordBeingAutoFilled = React.useRef(false); | ||||
const confirmPasswordEmpty = confirmPassword.length === 0; | |||||
const onPasswordKeyPress = React.useCallback( | |||||
(event: KeyPressEvent) => { | |||||
const { key } = event.nativeEvent; | |||||
// On iOS, paste doesn't trigger onKeyPress, but password autofill does | |||||
// Password autofill calls onKeyPress with `key` set to the whole password | |||||
if ( | |||||
key.length > 1 && | |||||
key !== 'Backspace' && | |||||
key !== 'Enter' && | |||||
confirmPasswordEmpty | |||||
) { | |||||
iosPasswordBeingAutoFilled.current = true; | |||||
} | |||||
}, | |||||
[confirmPasswordEmpty], | |||||
); | |||||
const passwordInputRef = React.useRef(); | const passwordInputRef = React.useRef(); | ||||
const passwordLength = password.length; | |||||
const onChangePasswordInput = React.useCallback( | |||||
(input: string) => { | |||||
setPassword(input); | |||||
if (iosPasswordBeingAutoFilled.current) { | |||||
// On iOS, paste doesn't trigger onKeyPress, but password autofill does | |||||
iosPasswordBeingAutoFilled.current = false; | |||||
setConfirmPassword(input); | |||||
passwordInputRef.current?.blur(); | |||||
} else if ( | |||||
Platform.OS === 'android' && | |||||
input.length - passwordLength > 1 && | |||||
confirmPasswordEmpty | |||||
) { | |||||
// On Android, password autofill doesn't trigger onKeyPress. Instead we | |||||
// rely on observing when the password field changes by more than one | |||||
// character at a time. This means we treat paste the same way as | |||||
// password autofill | |||||
setConfirmPassword(input); | |||||
passwordInputRef.current?.blur(); | |||||
} | |||||
}, | |||||
[passwordLength, confirmPasswordEmpty], | |||||
); | |||||
/* eslint-disable react-hooks/rules-of-hooks */ | |||||
if (Platform.OS === 'android') { | if (Platform.OS === 'android') { | ||||
// It's okay to call this hook conditionally because | // It's okay to call this hook conditionally because | ||||
// the condition is guaranteed to never change | // the condition is guaranteed to never change | ||||
React.useEffect(() => { | React.useEffect(() => { | ||||
(async () => { | (async () => { | ||||
await sleep(250); | await sleep(250); | ||||
passwordInputRef.current?.focus(); | passwordInputRef.current?.focus(); | ||||
})(); | })(); | ||||
}, []); | }, []); | ||||
} | } | ||||
/* eslint-enable react-hooks/rules-of-hooks */ | /* eslint-enable react-hooks/rules-of-hooks */ | ||||
return ( | return ( | ||||
<RegistrationContainer> | <RegistrationContainer> | ||||
<RegistrationContentContainer> | <RegistrationContentContainer> | ||||
<Text style={styles.header}>Pick a password</Text> | <Text style={styles.header}>Pick a password</Text> | ||||
<RegistrationTextInput | <RegistrationTextInput | ||||
value={password} | value={password} | ||||
onChangeText={setPassword} | onChangeText={onChangePasswordInput} | ||||
placeholder="Password" | placeholder="Password" | ||||
autoFocus={Platform.select({ | autoFocus={Platform.select({ | ||||
android: false, | android: false, | ||||
default: true, | default: true, | ||||
})} | })} | ||||
secureTextEntry={true} | secureTextEntry={true} | ||||
textContentType="newPassword" | textContentType="newPassword" | ||||
autoComplete="password-new" | autoComplete="password-new" | ||||
returnKeyType="next" | returnKeyType="next" | ||||
onSubmitEditing={focusConfirmPasswordInput} | onSubmitEditing={focusConfirmPasswordInput} | ||||
onKeyPress={onPasswordKeyPress} | |||||
onBlur={potentiallyClearErrors} | onBlur={potentiallyClearErrors} | ||||
ref={passwordInputRef} | ref={passwordInputRef} | ||||
/> | /> | ||||
<RegistrationTextInput | <RegistrationTextInput | ||||
value={confirmPassword} | value={confirmPassword} | ||||
onChangeText={setConfirmPassword} | onChangeText={setConfirmPassword} | ||||
placeholder="Confirm password" | placeholder="Confirm password" | ||||
secureTextEntry={true} | secureTextEntry={true} | ||||
▲ Show 20 Lines • Show All 42 Lines • Show Last 20 Lines |