diff --git a/native/chat/settings/add-users-modal.react.js b/native/chat/settings/add-users-modal.react.js index 0e43520ee..c32406720 100644 --- a/native/chat/settings/add-users-modal.react.js +++ b/native/chat/settings/add-users-modal.react.js @@ -1,295 +1,301 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { View, Text, ActivityIndicator, Alert } from 'react-native'; import { changeThreadSettingsActionTypes, changeThreadSettings, } from 'lib/actions/thread-actions'; +import { useENSNames } from 'lib/hooks/ens-cache'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { userInfoSelectorForPotentialMembers, userSearchIndexForPotentialMembers, } from 'lib/selectors/user-selectors'; import { getPotentialMemberItems } from 'lib/shared/search-utils'; import { threadActualMembers } from 'lib/shared/thread-utils'; import { type ThreadInfo } from 'lib/types/thread-types'; import { type AccountUserInfo } from 'lib/types/user-types'; import { useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils'; import Button from '../../components/button.react'; import Modal from '../../components/modal.react'; import { createTagInput } from '../../components/tag-input.react'; import UserList from '../../components/user-list.react'; import type { RootNavigationProp } from '../../navigation/root-navigator.react'; import type { NavigationRoute } from '../../navigation/route-names'; import { useSelector } from '../../redux/redux-utils'; import { useStyles } from '../../themes/colors'; const TagInput = createTagInput(); const tagInputProps = { placeholder: 'Select users to add', autoFocus: true, returnKeyType: 'go', }; const tagDataLabelExtractor = (userInfo: AccountUserInfo) => userInfo.username; export type AddUsersModalParams = { +presentedFrom: string, +threadInfo: ThreadInfo, }; type Props = { +navigation: RootNavigationProp<'AddUsersModal'>, +route: NavigationRoute<'AddUsersModal'>, }; function AddUsersModal(props: Props): React.Node { const [usernameInputText, setUsernameInputText] = React.useState(''); const [userInfoInputArray, setUserInfoInputArray] = React.useState< $ReadOnlyArray, >([]); const tagInputRef = React.useRef(); const onUnknownErrorAlertAcknowledged = React.useCallback(() => { setUsernameInputText(''); setUserInfoInputArray([]); invariant(tagInputRef.current, 'tagInput should be set'); tagInputRef.current.focus(); }, []); const { navigation } = props; const { goBackOnce } = navigation; const close = React.useCallback(() => { goBackOnce(); }, [goBackOnce]); const callChangeThreadSettings = useServerCall(changeThreadSettings); const userInfoInputIDs = userInfoInputArray.map(userInfo => userInfo.id); const { route } = props; const { threadInfo } = route.params; const threadID = threadInfo.id; const addUsersToThread = React.useCallback(async () => { try { const result = await callChangeThreadSettings({ threadID: threadID, changes: { newMemberIDs: userInfoInputIDs }, }); close(); return result; } catch (e) { Alert.alert( 'Unknown error', 'Uhh... try again?', [{ text: 'OK', onPress: onUnknownErrorAlertAcknowledged }], { cancelable: false }, ); throw e; } }, [ callChangeThreadSettings, threadID, userInfoInputIDs, close, onUnknownErrorAlertAcknowledged, ]); const inputLength = userInfoInputArray.length; const dispatchActionPromise = useDispatchActionPromise(); const userInfoInputArrayEmpty = inputLength === 0; const onPressAdd = React.useCallback(() => { if (userInfoInputArrayEmpty) { return; } dispatchActionPromise(changeThreadSettingsActionTypes, addUsersToThread()); }, [userInfoInputArrayEmpty, dispatchActionPromise, addUsersToThread]); const changeThreadSettingsLoadingStatus = useSelector( createLoadingStatusSelector(changeThreadSettingsActionTypes), ); const isLoading = changeThreadSettingsLoadingStatus === 'loading'; const styles = useStyles(unboundStyles); let addButton = null; if (inputLength > 0) { let activityIndicator = null; if (isLoading) { activityIndicator = ( ); } const addButtonText = `Add (${inputLength})`; addButton = ( ); } let cancelButton; if (!isLoading) { cancelButton = ( ); } else { cancelButton = ; } const threadMemberIDs = React.useMemo( () => threadActualMembers(threadInfo.members), [threadInfo.members], ); const excludeUserIDs = React.useMemo( () => userInfoInputIDs.concat(threadMemberIDs), [userInfoInputIDs, threadMemberIDs], ); const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const { parentThreadID, community } = props.route.params.threadInfo; const parentThreadInfo = useSelector(state => parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, ); const communityThreadInfo = useSelector(state => community ? threadInfoSelector(state)[community] : null, ); const userSearchResults = React.useMemo( () => getPotentialMemberItems( usernameInputText, otherUserInfos, userSearchIndex, excludeUserIDs, parentThreadInfo, communityThreadInfo, threadInfo.type, ), [ usernameInputText, otherUserInfos, userSearchIndex, excludeUserIDs, parentThreadInfo, communityThreadInfo, threadInfo.type, ], ); const onChangeTagInput = React.useCallback( (newUserInfoInputArray: $ReadOnlyArray) => { if (!isLoading) { setUserInfoInputArray(newUserInfoInputArray); } }, [isLoading], ); const onChangeTagInputText = React.useCallback( (text: string) => { if (!isLoading) { setUsernameInputText(text); } }, [isLoading], ); const onUserSelect = React.useCallback( (userID: string) => { if (isLoading) { return; } if (userInfoInputIDs.some(existingUserID => userID === existingUserID)) { return; } setUserInfoInputArray(oldUserInfoInputArray => [ ...oldUserInfoInputArray, otherUserInfos[userID], ]); setUsernameInputText(''); }, [isLoading, userInfoInputIDs, otherUserInfos], ); const inputProps = React.useMemo( () => ({ ...tagInputProps, onSubmitEditing: onPressAdd, }), [onPressAdd], ); + const userSearchResultWithENSNames = useENSNames(userSearchResults); + const userInfoInputArrayWithENSNames = useENSNames(userInfoInputArray); return ( - + {cancelButton} {addButton} ); } const unboundStyles = { activityIndicator: { paddingRight: 6, }, addButton: { backgroundColor: 'greenButton', borderRadius: 3, flexDirection: 'row', paddingHorizontal: 10, paddingVertical: 4, }, addText: { color: 'white', fontSize: 18, }, buttons: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 12, }, cancelButton: { backgroundColor: 'modalButton', borderRadius: 3, paddingHorizontal: 10, paddingVertical: 4, }, cancelText: { color: 'modalButtonLabel', fontSize: 18, }, }; const MemoizedAddUsersModal: React.ComponentType = React.memo( AddUsersModal, ); export default MemoizedAddUsersModal;