diff --git a/app/screens/manage_channel_members/index.tsx b/app/screens/manage_channel_members/index.tsx index d1b7dca7a..b8ed1a308 100644 --- a/app/screens/manage_channel_members/index.tsx +++ b/app/screens/manage_channel_members/index.tsx @@ -10,7 +10,7 @@ import {observeTutorialWatched} from '@queries/app/global'; import {observeCurrentChannel} from '@queries/servers/channel'; import {observeCanManageChannelMembers, observePermissionForChannel} from '@queries/servers/role'; import {observeCurrentChannelId, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system'; -import {observeCurrentUser} from '@queries/servers/user'; +import {observeCurrentUser, observeTeammateNameDisplay} from '@queries/servers/user'; import ManageChannelMembers from './manage_channel_members'; @@ -22,10 +22,14 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { const currentChannel = observeCurrentChannel(database); const canManageAndRemoveMembers = combineLatest([currentChannelId, currentUser]).pipe( - switchMap(([cId, u]) => (cId && u ? observeCanManageChannelMembers(database, cId, u) : of$(false)))); + switchMap(([cId, u]) => (cId && u ? observeCanManageChannelMembers(database, cId, u) : of$(false))), + ); const canChangeMemberRoles = combineLatest([currentChannel, currentUser, canManageAndRemoveMembers]).pipe( - switchMap(([c, u, m]) => (of$(c) && of$(u) && of$(m) && observePermissionForChannel(database, c, u, Permissions.MANAGE_CHANNEL_ROLES, true)))); + switchMap(([c, u, m]) => (of$(c) && of$(u) && of$(m) && observePermissionForChannel(database, c, u, Permissions.MANAGE_CHANNEL_ROLES, true))), + ); + + const teammateDisplayNameSetting = observeTeammateNameDisplay(database); return { currentUserId: observeCurrentUserId(database), @@ -33,6 +37,7 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { canManageAndRemoveMembers, tutorialWatched: observeTutorialWatched(Tutorial.PROFILE_LONG_PRESS), canChangeMemberRoles, + teammateDisplayNameSetting, }; }); diff --git a/app/screens/manage_channel_members/manage_channel_members.tsx b/app/screens/manage_channel_members/manage_channel_members.tsx index b78bf90fc..8ecb395c8 100644 --- a/app/screens/manage_channel_members/manage_channel_members.tsx +++ b/app/screens/manage_channel_members/manage_channel_members.tsx @@ -8,18 +8,18 @@ import {SafeAreaView} from 'react-native-safe-area-context'; import {fetchChannelMemberships} from '@actions/remote/channel'; import {fetchUsersByIds, searchProfiles} from '@actions/remote/user'; +import {PER_PAGE_DEFAULT} from '@client/rest/constants'; import Search from '@components/search'; import UserList from '@components/user_list'; import {Events, General, Screens} from '@constants'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; -import {debounce} from '@helpers/api/general'; import useNavButtonPressed from '@hooks/navigation_button_pressed'; import {openAsBottomSheet, setButtons} from '@screens/navigation'; import NavigationStore from '@store/navigation_store'; import {showRemoveChannelUserSnackbar} from '@utils/snack_bar'; import {changeOpacity, getKeyboardAppearanceFromTheme} from '@utils/theme'; -import {filterProfilesMatchingTerm} from '@utils/user'; +import {displayUsername, filterProfilesMatchingTerm} from '@utils/user'; import type {AvailableScreens} from '@typings/screens/navigation'; @@ -30,6 +30,7 @@ type Props = { currentTeamId: string; currentUserId: string; tutorialWatched: boolean; + teammateDisplayNameSetting: string; } const styles = StyleSheet.create({ @@ -54,6 +55,12 @@ const messages = defineMessages({ }, }); +const sortUsers = (a: UserProfile, b: UserProfile, locale: string, teammateDisplayNameSetting: string) => { + const aName = displayUsername(a, locale, teammateDisplayNameSetting); + const bName = displayUsername(b, locale, teammateDisplayNameSetting); + return aName.localeCompare(bName, locale); +}; + const MANAGE_BUTTON = 'manage-button'; const EMPTY: UserProfile[] = []; const EMPTY_MEMBERS: ChannelMembership[] = []; @@ -68,47 +75,29 @@ export default function ManageChannelMembers({ currentTeamId, currentUserId, tutorialWatched, + teammateDisplayNameSetting, }: Props) { const serverUrl = useServerUrl(); const theme = useTheme(); - const {formatMessage} = useIntl(); + const {formatMessage, locale} = useIntl(); const searchTimeoutId = useRef(null); const mounted = useRef(false); const [isManageMode, setIsManageMode] = useState(false); const [profiles, setProfiles] = useState(EMPTY); + const hasMoreProfiles = useRef(false); const [channelMembers, setChannelMembers] = useState(EMPTY_MEMBERS); const [searchResults, setSearchResults] = useState(EMPTY); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [term, setTerm] = useState(''); - - const loadedProfiles = (users: UserProfile[], members: ChannelMembership[]) => { - if (mounted.current) { - setLoading(false); - setProfiles(users); - setChannelMembers(members); - } - }; + const [searchedTerm, setSearchedTerm] = useState(''); const clearSearch = useCallback(() => { setTerm(''); setSearchResults(EMPTY); }, []); - const getProfiles = useCallback(debounce(async () => { - const hasTerm = Boolean(term); - if (!loading && !hasTerm && mounted.current) { - setLoading(true); - const options = {sort: 'admin', active: true}; - const {users, members} = await fetchChannelMemberships(serverUrl, channelId, options, true); - if (users.length) { - loadedProfiles(users, members); - } - setLoading(false); - } - }, 100), [channelId, loading, serverUrl, term]); - const handleSelectProfile = useCallback(async (profile: UserProfile) => { if (profile.id === currentUserId && isManageMode) { return; @@ -133,15 +122,19 @@ export default function ManageChannelMembers({ }, [canManageAndRemoveMembers, channelId, isManageMode, currentUserId]); const searchUsers = useCallback(async (searchTerm: string) => { + setSearchedTerm(searchTerm); + if (!hasMoreProfiles.current) { + return; + } const lowerCasedTerm = searchTerm.toLowerCase(); setLoading(true); const options: SearchUserOptions = {team_id: currentTeamId, in_channel_id: channelId, allow_inactive: false}; const {data = EMPTY} = await searchProfiles(serverUrl, lowerCasedTerm, options); - setSearchResults(data); + setSearchResults(data.sort((a, b) => sortUsers(a, b, locale, teammateDisplayNameSetting))); setLoading(false); - }, [serverUrl, channelId, currentTeamId]); + }, [serverUrl, channelId, currentTeamId, locale, teammateDisplayNameSetting]); const search = useCallback(() => { searchUsers(term); @@ -210,19 +203,44 @@ export default function ManageChannelMembers({ setChannelMembers(clone); }, [channelMembers]); + const sortedProfiles = useMemo(() => [...profiles].sort((a, b) => { + return sortUsers(a, b, locale, teammateDisplayNameSetting); + }), [profiles, locale, teammateDisplayNameSetting]); + const data = useMemo(() => { - const isSearch = Boolean(term); + const isSearch = Boolean(searchedTerm); if (isSearch) { - return filterProfilesMatchingTerm(searchResults, term); + return filterProfilesMatchingTerm(searchResults.length ? searchResults : sortedProfiles, searchedTerm); } return profiles; - }, [term, searchResults, profiles]); + }, [searchResults, profiles, searchedTerm, sortedProfiles]); + + useEffect(() => { + if (!term) { + setSearchResults(EMPTY); + setSearchedTerm(''); + } + }, [Boolean(term)]); useNavButtonPressed(MANAGE_BUTTON, componentId, toggleManageEnabled, [toggleManageEnabled]); useEffect(() => { mounted.current = true; - getProfiles(); + const options: GetUsersOptions = {sort: 'admin', active: true, per_page: PER_PAGE_DEFAULT}; + fetchChannelMemberships(serverUrl, channelId, options, true).then(({users, members}) => { + if (!mounted.current) { + return; + } + + if (users.length >= PER_PAGE_DEFAULT) { + hasMoreProfiles.current = true; + } + if (users.length) { + setProfiles(users); + setChannelMembers(members); + } + setLoading(false); + }); return () => { mounted.current = false; }; @@ -272,7 +290,7 @@ export default function ManageChannelMembers({ selectedIds={EMPTY_IDS} showManageMode={canManageAndRemoveMembers && isManageMode} showNoResults={!loading} - term={term} + term={searchedTerm} testID='manage_members.user_list' tutorialWatched={tutorialWatched} includeUserMargin={true} diff --git a/app/utils/user/index.ts b/app/utils/user/index.ts index 496bfa0e6..f5fbbc479 100644 --- a/app/utils/user/index.ts +++ b/app/utils/user/index.ts @@ -312,7 +312,7 @@ export function filterProfilesMatchingTerm(users: UserProfile[], term: string): return profileSuggestions. filter((suggestion) => suggestion !== ''). - some((suggestion) => suggestion.startsWith(trimmedTerm)); + some((suggestion) => suggestion.includes(trimmedTerm)); }); }