From eb76543229444ca827fcaa30041fc9155ac51659 Mon Sep 17 00:00:00 2001 From: Jason Frerich Date: Mon, 14 Nov 2022 05:38:54 -0600 Subject: [PATCH] move search and search functionality into the screen and pass the result to the users modal for rendering --- app/components/users_modal/users_modal.tsx | 120 +++++------------- .../create_direct_message.tsx | 114 ++++++++++++++--- 2 files changed, 126 insertions(+), 108 deletions(-) diff --git a/app/components/users_modal/users_modal.tsx b/app/components/users_modal/users_modal.tsx index a5529e0ac1..42249d32b3 100644 --- a/app/components/users_modal/users_modal.tsx +++ b/app/components/users_modal/users_modal.tsx @@ -1,22 +1,18 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; +import React, {forwardRef, MutableRefObject, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; import {MessageDescriptor, useIntl} from 'react-intl'; import {Keyboard, Platform, StyleSheet, View} from 'react-native'; -import {SafeAreaView} from 'react-native-safe-area-context'; import CompassIcon from '@components/compass_icon'; import Loading from '@components/loading'; -import Search from '@components/search'; import SelectedUsersPanel from '@components/selected_users_panel'; import UserList from '@components/user_list'; -import {General} from '@constants'; import {useTheme} from '@context/theme'; import {debounce} from '@helpers/api/general'; import useNavButtonPressed from '@hooks/navigation_button_pressed'; import {dismissModal, setButtons} from '@screens/navigation'; -import {changeOpacity, getKeyboardAppearanceFromTheme} from '@utils/theme'; import {filterProfilesMatchingTerm} from '@utils/user'; const style = StyleSheet.create({ @@ -38,16 +34,6 @@ const close = () => { dismissModal(); }; -type searchError = { - data?: undefined; - error: unknown; -} - -type searchSuccess = { - data: UserProfile[]; - error?: undefined; -} - type getProfilesError = { users?: undefined; error: unknown; @@ -60,17 +46,19 @@ type getProfilesSuccess = { type Props = { buttonText: MessageDescriptor; + clearSearch: () => void; componentId: string; currentUserId: string; getProfiles: () => Promise; + loading: boolean; maxSelectedUsers: number; - page: number; - searchUsers: (searchTerm: string) => Promise; - selectedIds: {[id: string]: UserProfile}; - setPage: (page: number) => void; - setSelectedIds: (ids: {[id: string]: UserProfile}) => void; onButtonTap: (selectedId?: {[id: string]: boolean}) => Promise; + searchResults: UserProfile[]; + selectedIds: {[id: string]: UserProfile}; + setLoading: (loading: boolean) => void; + setSelectedIds: (ids: {[id: string]: UserProfile}) => void; teammateNameDisplay: string; + term: string; tutorialWatched: boolean; } @@ -81,58 +69,53 @@ function reduceProfiles(state: UserProfile[], action: {type: 'add'; values?: Use return state; } -const UsersModal = ({ +const UsersModal = forwardRef(({ buttonText, + clearSearch, componentId, currentUserId, getProfiles, + loading, maxSelectedUsers, - page, - searchUsers, - selectedIds, - setPage, - setSelectedIds, onButtonTap, + searchResults, + selectedIds, + setLoading, + setSelectedIds, teammateNameDisplay, + term, tutorialWatched, -}: Props) => { +}: Props, pageRef: MutableRefObject) => { const theme = useTheme(); const {formatMessage, locale} = useIntl(); - - const searchTimeoutId = useRef(null); const mounted = useRef(false); const next = useRef(true); const selectedCount = Object.keys(selectedIds).length; - const [term, setTerm] = useState(''); const [startingButtonAction, setStartingButtonAction] = useState(false); - const [searchResults, setSearchResults] = useState([]); const [profiles, dispatchProfiles] = useReducer(reduceProfiles, []); - const [loading, setLoading] = useState(false); const isSearch = Boolean(term); + let hasUsers = false; + if (pageRef != null && typeof pageRef !== 'function') { + hasUsers = pageRef?.current !== -1; + } + const loadedProfiles = useCallback(({users}: {users?: UserProfile[]}) => { if (mounted.current) { if (users && !users.length) { next.current = false; } - setPage(page + 1); + if (pageRef != null && typeof pageRef !== 'function') { + pageRef.current += 1; + } setLoading(false); dispatchProfiles({type: 'add', values: users}); } - }, [page, setPage]); - - const handleSearchUsers = useCallback(async (searchTerm: string) => { - setLoading(true); - - const results = await searchUsers(searchTerm); - - setSearchResults(results?.data || []); - setLoading(false); - }, [searchUsers]); + }, []); const handleButtonTap = useCallback(async (selectedId?: {[id: string]: boolean}) => { if (startingButtonAction) { @@ -159,31 +142,6 @@ const UsersModal = ({ } }, 100), [getProfiles, loading, term]); - const search = useCallback(() => { - handleSearchUsers(term); - }, [handleSearchUsers, term]); - - const clearSearch = useCallback(() => { - setTerm(''); - setSearchResults([]); - }, []); - - const onSearch = useCallback((text: string) => { - if (text) { - setTerm(text); - if (searchTimeoutId.current) { - clearTimeout(searchTimeoutId.current); - } - - searchTimeoutId.current = setTimeout(() => { - handleSearchUsers(text); - }, General.SEARCH_TIMEOUT_MILLISECONDS); - return; - } - - clearSearch(); - }, [clearSearch, handleSearchUsers]); - const handleRemoveProfile = useCallback((id: string) => { const newSelectedIds = Object.assign({}, selectedIds); @@ -285,24 +243,7 @@ const UsersModal = ({ } return ( - - - - + <> {selectedCount > 0 && - + ); -}; +}); +UsersModal.displayName = 'UsersModal'; export default UsersModal; diff --git a/app/screens/create_direct_message/create_direct_message.tsx b/app/screens/create_direct_message/create_direct_message.tsx index df816e46ca..d37b619a42 100644 --- a/app/screens/create_direct_message/create_direct_message.tsx +++ b/app/screens/create_direct_message/create_direct_message.tsx @@ -1,18 +1,33 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useState} from 'react'; +import React, {useCallback, useRef, useState} from 'react'; import {defineMessages, useIntl} from 'react-intl'; +import {Platform, SafeAreaView, StyleSheet, View} from 'react-native'; import {makeDirectChannel, makeGroupChannel} from '@actions/remote/channel'; import {fetchProfiles, fetchProfilesInTeam, searchProfiles} from '@actions/remote/user'; +import Search from '@components/search'; import UsersModal from '@components/users_modal'; import {General} from '@constants'; import {useServerUrl} from '@context/server'; +import {useTheme} from '@context/theme'; import {t} from '@i18n'; import {alertErrorWithFallback} from '@utils/draft'; +import {changeOpacity, getKeyboardAppearanceFromTheme} from '@utils/theme'; import {displayUsername} from '@utils/user'; +const style = StyleSheet.create({ + container: { + flex: 1, + }, + searchBar: { + marginLeft: 12, + marginRight: Platform.select({ios: 4, default: 12}), + marginVertical: 12, + }, +}); + const messages = defineMessages({ dm: { id: t('mobile.open_dm.error'), @@ -44,10 +59,16 @@ export default function CreateDirectMessage({ teammateNameDisplay, }: Props) { const serverUrl = useServerUrl(); + const theme = useTheme(); const intl = useIntl(); const [selectedIds, setSelectedIds] = useState<{[id: string]: UserProfile}>({}); - const [page, setPage] = useState(-1); + const [searchResults, setSearchResults] = useState([]); + const [term, setTerm] = useState(''); + const [loading, setLoading] = useState(false); + + const pageRef = useRef(-1); + const searchTimeoutId = useRef(null); const createDirectChannel = useCallback(async (id: string): Promise => { const user = selectedIds[id]; @@ -70,9 +91,9 @@ export default function CreateDirectMessage({ const getProfiles = useCallback(async () => { if (restrictDirectMessage) { - return fetchProfilesInTeam(serverUrl, currentTeamId, page + 1, General.PROFILE_CHUNK_SIZE); + return fetchProfilesInTeam(serverUrl, currentTeamId, pageRef.current + 1, General.PROFILE_CHUNK_SIZE); } - return fetchProfiles(serverUrl, page + 1, General.PROFILE_CHUNK_SIZE); + return fetchProfiles(serverUrl, pageRef.current + 1, General.PROFILE_CHUNK_SIZE); }, [restrictDirectMessage, serverUrl, currentTeamId]); const onButtonTap = useCallback(async (selectedId?: {[id: string]: boolean}) => { @@ -91,21 +112,76 @@ export default function CreateDirectMessage({ return searchProfiles(serverUrl, lowerCasedTerm, {allow_inactive: true}); }, [restrictDirectMessage, serverUrl, currentTeamId]); + const handleSearchUsers = useCallback(async (searchTerm: string) => { + setLoading(true); + + const results = await searchUsers(searchTerm); + + setSearchResults(results?.data || []); + setLoading(false); + }, [searchUsers]); + + const search = useCallback(() => { + handleSearchUsers(term); + }, [handleSearchUsers, term]); + + const clearSearch = useCallback(() => { + setTerm(''); + setSearchResults([]); + }, []); + + const onSearch = useCallback((text: string) => { + if (text) { + setTerm(text); + if (searchTimeoutId.current) { + clearTimeout(searchTimeoutId.current); + } + + searchTimeoutId.current = setTimeout(() => { + handleSearchUsers(text); + }, General.SEARCH_TIMEOUT_MILLISECONDS); + return; + } + + clearSearch(); + }, [clearSearch, handleSearchUsers]); + return ( - + + + + + + ); } -