Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Handle Back press event for search header on multiple screens #56203

Merged
merged 6 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/hooks/useSearchBackPress/index.android.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useFocusEffect} from '@react-navigation/native';
import {useCallback} from 'react';
import {BackHandler} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import ONYXKEYS from '@src/ONYXKEYS';
import type UseSearchBackPress from './types';

const useSearchBackPress: UseSearchBackPress = ({onClearSelection, onNavigationCallBack}) => {
const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE);
useFocusEffect(
useCallback(() => {
const onBackPress = () => {
if (selectionMode?.isEnabled) {
onClearSelection();
turnOffMobileSelectionMode();
return true;
}
onNavigationCallBack();
return false;
};
const backHandler = BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () => backHandler.remove();
}, [selectionMode?.isEnabled, onClearSelection, onNavigationCallBack]),
);
};

export default useSearchBackPress;
6 changes: 6 additions & 0 deletions src/hooks/useSearchBackPress/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type UseSearchBackPress from './types';

// the back press event is only supported on Android native
const useSearchBackPress: UseSearchBackPress = () => {};

export default useSearchBackPress;
8 changes: 8 additions & 0 deletions src/hooks/useSearchBackPress/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type UseSearchBackPressParams = {
onClearSelection: () => void;
onNavigationCallBack: () => void;
};

type UseSearchBackPress = (params: UseSearchBackPressParams) => void;

export default UseSearchBackPress;
80 changes: 49 additions & 31 deletions src/pages/ReportParticipantsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/ButtonWithDropdownMenu/types';
import ConfirmModal from '@components/ConfirmModal';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import {FallbackAvatar, MakeAdmin, Plus, RemoveMembers, User} from '@components/Icon/Expensicons';
import ScreenWrapper from '@components/ScreenWrapper';
import TableListItem from '@components/SelectionList/TableListItem';
import type {ListItem, SelectionListHandle} from '@components/SelectionList/types';
Expand All @@ -21,17 +21,30 @@ import useLocalize from '@hooks/useLocalize';
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
import useNetwork from '@hooks/useNetwork';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSearchBackPress from '@hooks/useSearchBackPress';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import * as Report from '@libs/actions/Report';
import * as UserSearchPhraseActions from '@libs/actions/RoomMembersUserSearchPhrase';
import {removeFromGroupChat, updateGroupChatMemberRoles} from '@libs/actions/Report';
import {clearUserSearchPhrase} from '@libs/actions/RoomMembersUserSearchPhrase';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {ParticipantsNavigatorParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import {isSearchStringMatchUserDetails} from '@libs/OptionsListUtils';
import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils';
import {
getParticipantsList,
getReportName,
isArchivedNonExpenseReport,
isChatRoom,
isChatThread,
isGroupChatAdmin,
isGroupChat as isGroupChatUtils,
isMoneyRequestReport,
isPolicyExpenseChat,
isSelfDM,
isTaskReport,
} from '@libs/ReportUtils';
import StringUtils from '@libs/StringUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -63,8 +76,8 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
const [session] = useOnyx(ONYXKEYS.SESSION);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const currentUserAccountID = Number(session?.accountID);
const isCurrentUserAdmin = ReportUtils.isGroupChatAdmin(report, currentUserAccountID);
const isGroupChat = useMemo(() => ReportUtils.isGroupChat(report), [report]);
const isCurrentUserAdmin = isGroupChatAdmin(report, currentUserAccountID);
const isGroupChat = useMemo(() => isGroupChatUtils(report), [report]);
const isFocused = useIsFocused();
const {isOffline} = useNetwork();
const canSelectMultiple = isGroupChat && isCurrentUserAdmin && (isSmallScreenWidth ? selectionMode?.isEnabled : true);
Expand All @@ -77,7 +90,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
setSelectedMembers([]);
}, [isFocused]);

const chatParticipants = ReportUtils.getParticipantsList(report, personalDetails);
const chatParticipants = getParticipantsList(report, personalDetails);

const pendingChatMembers = reportMetadata?.pendingChatMembers;
const reportParticipants = report?.participants;
Expand All @@ -102,11 +115,23 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
if (shouldShowTextInput) {
setSearchValue(userSearchPhrase ?? '');
} else {
UserSearchPhraseActions.clearUserSearchPhrase();
clearUserSearchPhrase();
setSearchValue('');
}
}, [isFocused, setSearchValue, shouldShowTextInput, userSearchPhrase]);

useSearchBackPress({
onClearSelection: () => setSelectedMembers([]),
onNavigationCallBack: () => {
if (!report) {
return;
}

setSearchValue('');
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID, backTo));
},
});

const getParticipants = () => {
let result: MemberOption[] = [];

Expand All @@ -115,7 +140,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
const details = personalDetails?.[accountID];

// If search value is provided, filter out members that don't match the search value
if (!details || (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue))) {
if (!details || (searchValue.trim() && !isSearchStringMatchUserDetails(details, searchValue))) {
return;
}

Expand All @@ -135,13 +160,13 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
isSelected,
isDisabledCheckbox: accountID === currentUserAccountID,
isDisabled: pendingChatMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || details?.isOptimisticPersonalDetail,
text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)),
text: formatPhoneNumber(getDisplayNameOrDefault(details)),
alternateText: formatPhoneNumber(details?.login ?? ''),
rightElement: roleBadge,
pendingAction,
icons: [
{
source: details?.avatar ?? Expensicons.FallbackAvatar,
source: details?.avatar ?? FallbackAvatar,
name: formatPhoneNumber(details?.login ?? ''),
type: CONST.ICON_TYPE_AVATAR,
id: accountID,
Expand Down Expand Up @@ -200,19 +225,19 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
const removeUsers = () => {
// Remove the admin from the list
const accountIDsToRemove = selectedMembers.filter((id) => id !== currentUserAccountID);
Report.removeFromGroupChat(report.reportID, accountIDsToRemove);
removeFromGroupChat(report.reportID, accountIDsToRemove);
setSearchValue('');
setSelectedMembers([]);
setRemoveMembersConfirmModalVisible(false);
InteractionManager.runAfterInteractions(() => {
UserSearchPhraseActions.clearUserSearchPhrase();
clearUserSearchPhrase();
});
};

const changeUserRole = useCallback(
(role: ValueOf<typeof CONST.REPORT.ROLE>) => {
const accountIDsToUpdate = selectedMembers.filter((id) => report.participants?.[id].role !== role);
Report.updateGroupChatMemberRoles(report.reportID, accountIDsToUpdate, role);
updateGroupChatMemberRoles(report.reportID, accountIDsToUpdate, role);
setSelectedMembers([]);
},
[report, selectedMembers],
Expand Down Expand Up @@ -263,7 +288,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
{
text: translate('workspace.people.removeMembersTitle', {count: selectedMembers.length}),
value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.REMOVE,
icon: Expensicons.RemoveMembers,
icon: RemoveMembers,
onSelected: () => setRemoveMembersConfirmModalVisible(true),
},
];
Expand All @@ -274,7 +299,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
options.push({
text: translate('workspace.people.makeMember'),
value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_MEMBER,
icon: Expensicons.User,
icon: User,
onSelected: () => changeUserRole(CONST.REPORT.ROLE.MEMBER),
});
}
Expand All @@ -285,7 +310,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
options.push({
text: translate('workspace.people.makeAdmin'),
value: CONST.POLICY.MEMBERS_BULK_ACTION_TYPES.MAKE_ADMIN,
icon: Expensicons.MakeAdmin,
icon: MakeAdmin,
onSelected: () => changeUserRole(CONST.REPORT.ROLE.ADMIN),
});
}
Expand Down Expand Up @@ -317,7 +342,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
success
onPress={inviteUser}
text={translate('workspace.invite.member')}
icon={Expensicons.Plus}
icon={Plus}
innerStyles={[shouldUseNarrowLayout && styles.alignItemsCenter]}
style={[shouldUseNarrowLayout && styles.flexGrow1]}
/>
Expand All @@ -338,14 +363,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
[report, isCurrentUserAdmin, isGroupChat, backTo],
);
const headerTitle = useMemo(() => {
if (
ReportUtils.isChatRoom(report) ||
ReportUtils.isPolicyExpenseChat(report) ||
ReportUtils.isChatThread(report) ||
ReportUtils.isTaskReport(report) ||
ReportUtils.isMoneyRequestReport(report) ||
isGroupChat
) {
if (isChatRoom(report) || isPolicyExpenseChat(report) || isChatThread(report) || isTaskReport(report) || isMoneyRequestReport(report) || isGroupChat) {
return translate('common.members');
}
return translate('common.details');
Expand All @@ -365,7 +383,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
style={[styles.defaultModalContainer]}
testID={ReportParticipantsPage.displayName}
>
<FullPageNotFoundView shouldShow={!report || ReportUtils.isArchivedNonExpenseReport(report, reportNameValuePairs) || ReportUtils.isSelfDM(report)}>
<FullPageNotFoundView shouldShow={!report || isArchivedNonExpenseReport(report, reportNameValuePairs) || isSelfDM(report)}>
<HeaderWithBackButton
title={selectionModeHeader ? translate('common.selectMultiple') : headerTitle}
onBackButtonPress={() => {
Expand All @@ -381,7 +399,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
}
}}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
subtitle={StringUtils.lineBreaksToSpaces(ReportUtils.getReportName(report))}
subtitle={StringUtils.lineBreaksToSpaces(getReportName(report))}
/>
<View style={[styles.pl5, styles.pr5]}>{headerButtons}</View>
<ConfirmModal
Expand All @@ -392,7 +410,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) {
onCancel={() => setRemoveMembersConfirmModalVisible(false)}
prompt={translate('workspace.people.removeMembersPrompt', {
count: selectedMembers.length,
memberName: formatPhoneNumber(PersonalDetailsUtils.getPersonalDetailsByIDs({accountIDs: selectedMembers, currentUserAccountID}).at(0)?.displayName ?? ''),
memberName: formatPhoneNumber(getPersonalDetailsByIDs({accountIDs: selectedMembers, currentUserAccountID}).at(0)?.displayName ?? ''),
})}
confirmText={translate('common.remove')}
cancelText={translate('common.cancel')}
Expand Down
Loading