diff --git a/native/account/legacy-register-panel.react.js b/native/account/legacy-register-panel.react.js index 1d56702fa..1016ac585 100644 --- a/native/account/legacy-register-panel.react.js +++ b/native/account/legacy-register-panel.react.js @@ -1,506 +1,508 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { Text, View, StyleSheet, Platform, Keyboard, Linking, } from 'react-native'; import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js'; import { legacyKeyserverRegisterActionTypes, legacyKeyserverRegister, getOlmSessionInitializationDataActionTypes, } from 'lib/actions/user-actions.js'; import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js'; import { createLoadingStatusSelector, combineLoadingStatuses, } from 'lib/selectors/loading-selectors.js'; import { validUsernameRegex } from 'lib/shared/account-utils.js'; import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js'; import type { LegacyRegisterInfo, LegacyLogInExtraInfo, LegacyRegisterResult, LegacyLogInStartingPayload, } from 'lib/types/account-types.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { Dispatch } from 'lib/types/redux-types.js'; import { useDispatchActionPromise, type DispatchActionPromise, } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { TextInput } from './modal-components.react.js'; import { setNativeCredentials } from './native-credentials.js'; import { PanelButton, Panel } from './panel-components.react.js'; import { authoritativeKeyserverID } from '../authoritative-keyserver.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { nativeLegacyLogInExtraInfoSelector } from '../selectors/account-selectors.js'; import type { KeyPressEvent } from '../types/react-native.js'; import type { ViewStyle } from '../types/styles.js'; import { appOutOfDateAlertDetails, usernameReservedAlertDetails, usernameTakenAlertDetails, unknownErrorAlertDetails, } from '../utils/alert-messages.js'; import Alert from '../utils/alert.js'; import { type StateContainer } from '../utils/state-container.js'; type WritableLegacyRegisterState = { usernameInputText: string, passwordInputText: string, confirmPasswordInputText: string, }; export type LegacyRegisterState = $ReadOnly; type BaseProps = { +setActiveAlert: (activeAlert: boolean) => void, +opacityStyle: ViewStyle, +legacyRegisterState: StateContainer, }; type Props = { ...BaseProps, +loadingStatus: LoadingStatus, +legacyLogInExtraInfo: () => Promise, +dispatch: Dispatch, +dispatchActionPromise: DispatchActionPromise, +legacyRegister: ( registerInfo: LegacyRegisterInfo, ) => Promise, +getInitialNotificationsEncryptedMessage: () => Promise, }; type State = { +confirmPasswordFocused: boolean, }; class LegacyRegisterPanel extends React.PureComponent { state: State = { confirmPasswordFocused: false, }; usernameInput: ?TextInput; passwordInput: ?TextInput; confirmPasswordInput: ?TextInput; passwordBeingAutoFilled = false; render(): React.Node { let confirmPasswordTextInputExtraProps; if ( Platform.OS !== 'ios' || this.state.confirmPasswordFocused || this.props.legacyRegisterState.state.confirmPasswordInputText.length > 0 ) { confirmPasswordTextInputExtraProps = { secureTextEntry: true, textContentType: 'password', }; } let onPasswordKeyPress; if (Platform.OS === 'ios') { onPasswordKeyPress = this.onPasswordKeyPress; } const privatePolicyNotice = ( By signing up, you agree to our{' '} Terms {' & '} Privacy Policy . ); return ( {privatePolicyNotice} ); } usernameInputRef = (usernameInput: ?TextInput) => { this.usernameInput = usernameInput; }; passwordInputRef = (passwordInput: ?TextInput) => { this.passwordInput = passwordInput; }; confirmPasswordInputRef = (confirmPasswordInput: ?TextInput) => { this.confirmPasswordInput = confirmPasswordInput; }; focusUsernameInput = () => { invariant(this.usernameInput, 'ref should be set'); this.usernameInput.focus(); }; focusPasswordInput = () => { invariant(this.passwordInput, 'ref should be set'); this.passwordInput.focus(); }; focusConfirmPasswordInput = () => { invariant(this.confirmPasswordInput, 'ref should be set'); this.confirmPasswordInput.focus(); }; onTermsOfUsePressed = () => { void Linking.openURL('https://comm.app/terms'); }; onPrivacyPolicyPressed = () => { void Linking.openURL('https://comm.app/privacy'); }; onChangeUsernameInputText = (text: string) => { this.props.legacyRegisterState.setState({ usernameInputText: text }); }; onChangePasswordInputText = (text: string) => { const stateUpdate: Partial = {}; stateUpdate.passwordInputText = text; if (this.passwordBeingAutoFilled) { this.passwordBeingAutoFilled = false; stateUpdate.confirmPasswordInputText = text; } this.props.legacyRegisterState.setState(stateUpdate); }; onPasswordKeyPress = (event: KeyPressEvent) => { const { key } = event.nativeEvent; if ( key.length > 1 && key !== 'Backspace' && key !== 'Enter' && this.props.legacyRegisterState.state.confirmPasswordInputText.length === 0 ) { this.passwordBeingAutoFilled = true; } }; onChangeConfirmPasswordInputText = (text: string) => { this.props.legacyRegisterState.setState({ confirmPasswordInputText: text }); }; onConfirmPasswordFocus = () => { this.setState({ confirmPasswordFocused: true }); }; onSubmit = async () => { this.props.setActiveAlert(true); if (this.props.legacyRegisterState.state.passwordInputText === '') { Alert.alert( 'Empty password', 'Password cannot be empty', [{ text: 'OK', onPress: this.onPasswordAlertAcknowledged }], { cancelable: false }, ); } else if ( this.props.legacyRegisterState.state.passwordInputText !== this.props.legacyRegisterState.state.confirmPasswordInputText ) { Alert.alert( 'Passwords don’t match', 'Password fields must contain the same password', [{ text: 'OK', onPress: this.onPasswordAlertAcknowledged }], { cancelable: false }, ); } else if ( this.props.legacyRegisterState.state.usernameInputText.search( validUsernameRegex, ) === -1 ) { Alert.alert( 'Invalid username', 'Usernames must be at least six characters long, start with either a ' + 'letter or a number, and may contain only letters, numbers, or the ' + 'characters “-” and “_”', [{ text: 'OK', onPress: this.onUsernameAlertAcknowledged }], { cancelable: false }, ); } else { Keyboard.dismiss(); const extraInfo = await this.props.legacyLogInExtraInfo(); const initialNotificationsEncryptedMessage = await this.props.getInitialNotificationsEncryptedMessage(); void this.props.dispatchActionPromise( legacyKeyserverRegisterActionTypes, this.legacyRegisterAction({ ...extraInfo, initialNotificationsEncryptedMessage, }), undefined, ({ calendarQuery: extraInfo.calendarQuery, }: LegacyLogInStartingPayload), ); } }; onPasswordAlertAcknowledged = () => { this.props.setActiveAlert(false); this.props.legacyRegisterState.setState( { passwordInputText: '', confirmPasswordInputText: '', }, () => { invariant(this.passwordInput, 'ref should exist'); this.passwordInput.focus(); }, ); }; onUsernameAlertAcknowledged = () => { this.props.setActiveAlert(false); this.props.legacyRegisterState.setState( { usernameInputText: '', }, () => { invariant(this.usernameInput, 'ref should exist'); this.usernameInput.focus(); }, ); }; async legacyRegisterAction( extraInfo: LegacyLogInExtraInfo, ): Promise { try { const result = await this.props.legacyRegister({ ...extraInfo, username: this.props.legacyRegisterState.state.usernameInputText, password: this.props.legacyRegisterState.state.passwordInputText, }); this.props.setActiveAlert(false); this.props.dispatch({ type: setDataLoadedActionType, payload: { dataLoaded: true, }, }); await setNativeCredentials({ username: result.currentUserInfo.username, password: this.props.legacyRegisterState.state.passwordInputText, }); return result; } catch (e) { if (e.message === 'username_reserved') { Alert.alert( usernameReservedAlertDetails.title, usernameReservedAlertDetails.message, [{ text: 'OK', onPress: this.onUsernameAlertAcknowledged }], { cancelable: false }, ); } else if (e.message === 'username_taken') { Alert.alert( usernameTakenAlertDetails.title, usernameTakenAlertDetails.message, [{ text: 'OK', onPress: this.onUsernameAlertAcknowledged }], { cancelable: false }, ); } else if (e.message === 'client_version_unsupported') { Alert.alert( appOutOfDateAlertDetails.title, appOutOfDateAlertDetails.message, [{ text: 'OK', onPress: this.onOtherErrorAlertAcknowledged }], { cancelable: false }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: this.onOtherErrorAlertAcknowledged }], { cancelable: false }, ); } throw e; } } onOtherErrorAlertAcknowledged = () => { this.props.setActiveAlert(false); }; } const styles = StyleSheet.create({ container: { zIndex: 2, }, footer: { alignItems: 'stretch', flexDirection: 'row', flexShrink: 1, justifyContent: 'space-between', paddingLeft: 24, }, hyperlinkText: { color: '#036AFF', fontWeight: 'bold', }, icon: { bottom: 10, left: 4, position: 'absolute', }, input: { paddingLeft: 35, }, notice: { alignSelf: 'center', display: 'flex', flexShrink: 1, maxWidth: 190, paddingBottom: 18, paddingRight: 8, paddingTop: 12, }, noticeText: { color: '#444', fontSize: 13, lineHeight: 20, textAlign: 'center', }, row: { marginHorizontal: 24, }, }); const registerLoadingStatusSelector = createLoadingStatusSelector( legacyKeyserverRegisterActionTypes, ); const olmSessionInitializationDataLoadingStatusSelector = createLoadingStatusSelector(getOlmSessionInitializationDataActionTypes); const ConnectedLegacyRegisterPanel: React.ComponentType = React.memo(function ConnectedLegacyRegisterPanel( props: BaseProps, ) { const registerLoadingStatus = useSelector(registerLoadingStatusSelector); const olmSessionInitializationDataLoadingStatus = useSelector( olmSessionInitializationDataLoadingStatusSelector, ); const loadingStatus = combineLoadingStatuses( registerLoadingStatus, olmSessionInitializationDataLoadingStatus, ); const legacyLogInExtraInfo = useSelector( nativeLegacyLogInExtraInfoSelector, ); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const callLegacyRegister = useLegacyAshoatKeyserverCall( legacyKeyserverRegister, ); const getInitialNotificationsEncryptedMessage = useInitialNotificationsEncryptedMessage(authoritativeKeyserverID); return ( ); }); export default ConnectedLegacyRegisterPanel; diff --git a/native/chat/settings/delete-thread.react.js b/native/chat/settings/delete-thread.react.js index 084000b83..aad28e2a7 100644 --- a/native/chat/settings/delete-thread.react.js +++ b/native/chat/settings/delete-thread.react.js @@ -1,299 +1,288 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { ActivityIndicator, Text, TextInput as BaseTextInput, View, } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; import type { DeleteThreadInput } from 'lib/actions/thread-actions.js'; import { deleteThreadActionTypes, useDeleteThread, } from 'lib/actions/thread-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { containedThreadInfos, threadInfoSelector, } from 'lib/selectors/thread-selectors.js'; import { getThreadsToDeleteText, identifyInvalidatedThreads, } from 'lib/shared/thread-utils.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { ResolvedThreadInfo, ThreadInfo, } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import type { LeaveThreadPayload } from 'lib/types/thread-types.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; import { type DispatchActionPromise, useDispatchActionPromise, } from 'lib/utils/redux-promise-utils.js'; import Button from '../../components/button.react.js'; import { clearThreadsActionType } from '../../navigation/action-types.js'; import { type NavAction, NavContext, } from '../../navigation/navigation-context.js'; import type { NavigationRoute } from '../../navigation/route-names.js'; import { useSelector } from '../../redux/redux-utils.js'; import { type Colors, useColors, useStyles } from '../../themes/colors.js'; import { unknownErrorAlertDetails } from '../../utils/alert-messages.js'; import Alert from '../../utils/alert.js'; import type { ChatNavigationProp } from '../chat.react.js'; export type DeleteThreadParams = { +threadInfo: ThreadInfo, }; const unboundStyles = { deleteButton: { backgroundColor: 'vibrantRedButton', borderRadius: 5, flex: 1, marginHorizontal: 24, marginVertical: 12, padding: 12, }, deleteText: { color: 'white', fontSize: 18, textAlign: 'center', }, header: { color: 'panelBackgroundLabel', fontSize: 12, fontWeight: '400', paddingBottom: 3, paddingHorizontal: 24, }, input: { color: 'panelForegroundLabel', flex: 1, fontFamily: 'Arial', fontSize: 16, paddingVertical: 0, borderBottomColor: 'transparent', }, scrollView: { backgroundColor: 'panelBackground', }, scrollViewContentContainer: { paddingTop: 24, }, section: { backgroundColor: 'panelForeground', borderBottomWidth: 1, borderColor: 'panelForegroundBorder', borderTopWidth: 1, flexDirection: 'row', justifyContent: 'space-between', marginBottom: 24, paddingHorizontal: 24, paddingVertical: 12, }, warningText: { color: 'panelForegroundLabel', fontSize: 16, marginBottom: 24, marginHorizontal: 24, textAlign: 'center', }, }; type BaseProps = { +navigation: ChatNavigationProp<'DeleteThread'>, +route: NavigationRoute<'DeleteThread'>, }; type Props = { ...BaseProps, // Redux state +threadInfo: ResolvedThreadInfo, +shouldUseDeleteConfirmationAlert: boolean, +loadingStatus: LoadingStatus, +colors: Colors, +styles: $ReadOnly, // Redux dispatch functions +dispatchActionPromise: DispatchActionPromise, // async functions that hit server APIs +deleteThread: (input: DeleteThreadInput) => Promise, // withNavContext +navDispatch: (action: NavAction) => void, }; class DeleteThread extends React.PureComponent { mounted = false; passwordInput: ?React.ElementRef; componentDidMount() { this.mounted = true; } componentWillUnmount() { this.mounted = false; } render(): React.Node { const buttonContent = this.props.loadingStatus === 'loading' ? ( ) : ( Delete chat ); const { threadInfo } = this.props; return ( {`The chat "${threadInfo.uiName}" will be permanently deleted. `} There is no way to reverse this. ); } - passwordInputRef = ( - passwordInput: ?React.ElementRef, - ) => { - this.passwordInput = passwordInput; - }; - - focusPasswordInput = () => { - invariant(this.passwordInput, 'passwordInput should be set'); - this.passwordInput.focus(); - }; - dispatchDeleteThreadAction = () => { void this.props.dispatchActionPromise( deleteThreadActionTypes, this.deleteThread(), ); }; submitDeletion = () => { if (!this.props.shouldUseDeleteConfirmationAlert) { this.dispatchDeleteThreadAction(); return; } Alert.alert( 'Warning', `${getThreadsToDeleteText( this.props.threadInfo, )} will also be permanently deleted.`, [ { text: 'Cancel', style: 'cancel' }, { text: 'Continue', onPress: this.dispatchDeleteThreadAction }, ], { cancelable: false }, ); }; async deleteThread(): Promise { const { threadInfo, navDispatch } = this.props; navDispatch({ type: clearThreadsActionType, payload: { threadIDs: [threadInfo.id] }, }); try { const result = await this.props.deleteThread({ threadID: threadInfo.id }); const invalidated = identifyInvalidatedThreads( result.updatesResult.newUpdates, ); navDispatch({ type: clearThreadsActionType, payload: { threadIDs: [...invalidated] }, }); return result; } catch (e) { if (e.message === 'invalid_credentials') { Alert.alert( 'Permission not granted', 'You do not have permission to delete this thread', [{ text: 'OK' }], { cancelable: false }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK' }], { cancelable: false, }, ); } throw e; } } } const loadingStatusSelector = createLoadingStatusSelector( deleteThreadActionTypes, ); const ConnectedDeleteThread: React.ComponentType = React.memo(function ConnectedDeleteThread(props: BaseProps) { const threadID = props.route.params.threadInfo.id; const reduxThreadInfo = useSelector( state => threadInfoSelector(state)[threadID], ); const reduxContainedThreadInfos = useSelector( state => containedThreadInfos(state)[threadID], ); const { setParams } = props.navigation; React.useEffect(() => { if (reduxThreadInfo) { setParams({ threadInfo: reduxThreadInfo }); } }, [reduxThreadInfo, setParams]); const threadInfo = reduxThreadInfo ?? props.route.params.threadInfo; const resolvedThreadInfo = useResolvedThreadInfo(threadInfo); const loadingStatus = useSelector(loadingStatusSelector); const colors = useColors(); const styles = useStyles(unboundStyles); const dispatchActionPromise = useDispatchActionPromise(); const callDeleteThread = useDeleteThread(); const navContext = React.useContext(NavContext); invariant(navContext, 'NavContext should be set in DeleteThread'); const navDispatch = navContext.dispatch; const shouldUseDeleteConfirmationAlert = reduxContainedThreadInfos && reduxContainedThreadInfos.length > 0; return ( ); }); export default ConnectedDeleteThread; diff --git a/native/profile/edit-password.react.js b/native/profile/edit-password.react.js index 9ad6b3a87..1dc363c24 100644 --- a/native/profile/edit-password.react.js +++ b/native/profile/edit-password.react.js @@ -1,402 +1,405 @@ // @flow import { CommonActions } from '@react-navigation/native'; import invariant from 'invariant'; import * as React from 'react'; import { Text, View, TextInput as BaseTextInput, ActivityIndicator, } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; import { changeKeyserverUserPasswordActionTypes, changeKeyserverUserPassword, useChangeIdentityUserPassword, changeIdentityUserPasswordActionTypes, } from 'lib/actions/user-actions.js'; import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { PasswordUpdate } from 'lib/types/user-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatchActionPromise, type DispatchActionPromise, } from 'lib/utils/redux-promise-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; import { setNativeCredentials } from '../account/native-credentials.js'; import Button from '../components/button.react.js'; import TextInput from '../components/text-input.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { type Colors, useColors, useStyles } from '../themes/colors.js'; import { unknownErrorAlertDetails } from '../utils/alert-messages.js'; import Alert from '../utils/alert.js'; const unboundStyles = { header: { color: 'panelBackgroundLabel', fontSize: 12, fontWeight: '400', paddingBottom: 3, paddingHorizontal: 24, }, hr: { backgroundColor: 'panelForegroundBorder', height: 1, marginHorizontal: 15, }, input: { color: 'panelForegroundLabel', flex: 1, fontFamily: 'Arial', fontSize: 16, paddingVertical: 0, borderBottomColor: 'transparent', }, row: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 9, }, saveButton: { backgroundColor: 'vibrantGreenButton', borderRadius: 5, flex: 1, marginHorizontal: 24, marginVertical: 12, padding: 12, }, saveText: { color: 'white', fontSize: 18, textAlign: 'center', }, scrollView: { backgroundColor: 'panelBackground', }, scrollViewContentContainer: { paddingTop: 24, }, section: { backgroundColor: 'panelForeground', borderBottomWidth: 1, borderColor: 'panelForegroundBorder', borderTopWidth: 1, marginBottom: 24, paddingVertical: 3, }, }; type BaseProps = { +navigation: ProfileNavigationProp<'EditPassword'>, +route: NavigationRoute<'EditPassword'>, }; type Props = { ...BaseProps, // Redux state +loadingStatus: LoadingStatus, +username: ?string, +colors: Colors, +styles: $ReadOnly, // Redux dispatch functions +dispatchActionPromise: DispatchActionPromise, // async functions that hit server APIs +changeKeyserverUserPassword: ( passwordUpdate: PasswordUpdate, ) => Promise, +changeIdentityUserPassword: ( oldPassword: string, newPassword: string, ) => Promise, }; type State = { +currentPassword: string, +newPassword: string, +confirmPassword: string, }; class EditPassword extends React.PureComponent { state: State = { currentPassword: '', newPassword: '', confirmPassword: '', }; mounted = false; currentPasswordInput: ?React.ElementRef; newPasswordInput: ?React.ElementRef; confirmPasswordInput: ?React.ElementRef; componentDidMount() { this.mounted = true; } componentWillUnmount() { this.mounted = false; } render(): React.Node { const buttonContent = this.props.loadingStatus === 'loading' ? ( ) : ( Save ); const { panelForegroundTertiaryLabel } = this.props.colors; return ( CURRENT PASSWORD NEW PASSWORD ); } onChangeCurrentPassword = (currentPassword: string) => { this.setState({ currentPassword }); }; currentPasswordRef = ( currentPasswordInput: ?React.ElementRef, ) => { this.currentPasswordInput = currentPasswordInput; }; focusCurrentPassword = () => { invariant(this.currentPasswordInput, 'currentPasswordInput should be set'); this.currentPasswordInput.focus(); }; onChangeNewPassword = (newPassword: string) => { this.setState({ newPassword }); }; newPasswordRef = ( newPasswordInput: ?React.ElementRef, ) => { this.newPasswordInput = newPasswordInput; }; focusNewPassword = () => { invariant(this.newPasswordInput, 'newPasswordInput should be set'); this.newPasswordInput.focus(); }; onChangeConfirmPassword = (confirmPassword: string) => { this.setState({ confirmPassword }); }; confirmPasswordRef = ( confirmPasswordInput: ?React.ElementRef, ) => { this.confirmPasswordInput = confirmPasswordInput; }; focusConfirmPassword = () => { invariant(this.confirmPasswordInput, 'confirmPasswordInput should be set'); this.confirmPasswordInput.focus(); }; goBackOnce() { this.props.navigation.dispatch(state => ({ ...CommonActions.goBack(), target: state.key, })); } submitPassword = () => { if (this.state.newPassword === '') { Alert.alert( 'Empty password', 'New password cannot be empty', [{ text: 'OK', onPress: this.onNewPasswordAlertAcknowledged }], { cancelable: false }, ); } else if (this.state.newPassword !== this.state.confirmPassword) { Alert.alert( 'Passwords don’t match', 'New password fields must contain the same password', [{ text: 'OK', onPress: this.onNewPasswordAlertAcknowledged }], { cancelable: false }, ); } else if (this.state.newPassword === this.state.currentPassword) { this.goBackOnce(); } else if (usingCommServicesAccessToken) { void this.props.dispatchActionPromise( changeIdentityUserPasswordActionTypes, this.savePassword(), ); } else { void this.props.dispatchActionPromise( changeKeyserverUserPasswordActionTypes, this.savePassword(), ); } }; async savePassword() { const { username } = this.props; if (!username) { return; } try { if (usingCommServicesAccessToken) { await this.props.changeIdentityUserPassword( this.state.currentPassword, this.state.newPassword, ); } else { await this.props.changeKeyserverUserPassword({ updatedFields: { password: this.state.newPassword, }, currentPassword: this.state.currentPassword, }); } await setNativeCredentials({ username, password: this.state.newPassword, }); this.goBackOnce(); } catch (e) { const messageForException = getMessageForException(e); if ( messageForException === 'invalid_credentials' || messageForException === 'login_failed' ) { Alert.alert( 'Incorrect password', 'The current password you entered is incorrect', [{ text: 'OK', onPress: this.onCurrentPasswordAlertAcknowledged }], { cancelable: false }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: this.onUnknownErrorAlertAcknowledged }], { cancelable: false }, ); } } } onNewPasswordAlertAcknowledged = () => { this.setState( { newPassword: '', confirmPassword: '' }, this.focusNewPassword, ); }; onCurrentPasswordAlertAcknowledged = () => { this.setState({ currentPassword: '' }, this.focusCurrentPassword); }; onUnknownErrorAlertAcknowledged = () => { this.setState( { currentPassword: '', newPassword: '', confirmPassword: '' }, this.focusCurrentPassword, ); }; } const loadingStatusSelector = usingCommServicesAccessToken ? createLoadingStatusSelector(changeIdentityUserPasswordActionTypes) : createLoadingStatusSelector(changeKeyserverUserPasswordActionTypes); const ConnectedEditPassword: React.ComponentType = React.memo(function ConnectedEditPassword(props: BaseProps) { const loadingStatus = useSelector(loadingStatusSelector); const username = useSelector(state => { if (state.currentUserInfo && !state.currentUserInfo.anonymous) { return state.currentUserInfo.username; } return undefined; }); const colors = useColors(); const styles = useStyles(unboundStyles); const dispatchActionPromise = useDispatchActionPromise(); const callChangeKeyserverUserPassword = useLegacyAshoatKeyserverCall( changeKeyserverUserPassword, ); const callChangeIdentityUserPassword = useChangeIdentityUserPassword(); return ( ); }); export default ConnectedEditPassword;