diff --git a/native/account/registration/password-selection.react.js b/native/account/registration/password-selection.react.js
index f3b81798a..5e679368b 100644
--- a/native/account/registration/password-selection.react.js
+++ b/native/account/registration/password-selection.react.js
@@ -1,160 +1,206 @@
// @flow
import * as React from 'react';
import { View, Text, Platform } from 'react-native';
import sleep from 'lib/utils/sleep.js';
import RegistrationButtonContainer from './registration-button-container.react.js';
import RegistrationButton from './registration-button.react.js';
import RegistrationContainer from './registration-container.react.js';
import RegistrationContentContainer from './registration-content-container.react.js';
import type { RegistrationNavigationProp } from './registration-navigator.react.js';
import RegistrationTextInput from './registration-text-input.react.js';
import type { CoolOrNerdMode } from './registration-types.js';
import type { NavigationRoute } from '../../navigation/route-names.js';
import { useStyles } from '../../themes/colors.js';
+import type { KeyPressEvent } from '../../types/react-native.js';
export type PasswordSelectionParams = {
+userSelections: {
+coolOrNerdMode: CoolOrNerdMode,
+keyserverUsername: string,
+username: string,
},
};
type PasswordError = 'passwords_dont_match' | 'empty_password';
type Props = {
+navigation: RegistrationNavigationProp<'PasswordSelection'>,
+route: NavigationRoute<'PasswordSelection'>,
};
// eslint-disable-next-line no-unused-vars
function PasswordSelection(props: Props): React.Node {
const [password, setPassword] = React.useState('');
const [confirmPassword, setConfirmPassword] = React.useState('');
const passwordsMatch = password === confirmPassword;
const passwordIsEmpty = password === '';
const [passwordError, setPasswordError] = React.useState();
const potentiallyClearErrors = React.useCallback(() => {
if (!passwordsMatch || passwordIsEmpty) {
return false;
}
setPasswordError(null);
return true;
}, [passwordsMatch, passwordIsEmpty]);
const checkPasswordValidity = React.useCallback(() => {
if (!passwordsMatch) {
setPasswordError('passwords_dont_match');
return false;
} else if (passwordIsEmpty) {
setPasswordError('empty_password');
return false;
}
return potentiallyClearErrors();
}, [passwordsMatch, passwordIsEmpty, potentiallyClearErrors]);
const onProceed = React.useCallback(() => {
if (!checkPasswordValidity()) {
return;
}
}, [checkPasswordValidity]);
const styles = useStyles(unboundStyles);
let errorText;
if (passwordError === 'passwords_dont_match') {
errorText = (
Passwords don’t match
);
} else if (passwordError === 'empty_password') {
errorText = Password cannot be empty;
}
const confirmPasswordInputRef = React.useRef();
const focusConfirmPasswordInput = React.useCallback(() => {
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 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') {
// It's okay to call this hook conditionally because
// the condition is guaranteed to never change
React.useEffect(() => {
(async () => {
await sleep(250);
passwordInputRef.current?.focus();
})();
}, []);
}
/* eslint-enable react-hooks/rules-of-hooks */
return (
Pick a password
{errorText}
);
}
const unboundStyles = {
header: {
fontSize: 24,
color: 'panelForegroundLabel',
paddingBottom: 16,
},
error: {
marginTop: 16,
},
errorText: {
fontFamily: 'Arial',
fontSize: 15,
lineHeight: 20,
color: 'redText',
},
confirmPassword: {
marginTop: 16,
},
};
export default PasswordSelection;