Skip to content
Merged
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
115 changes: 66 additions & 49 deletions src/pages/NewChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@ import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus';
import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import {navigateToAndOpenReport, searchInServer, setGroupDraft} from '@libs/actions/Report';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import type {Option, Section} from '@libs/OptionsListUtils';
import {
filterAndOrderOptions,
formatSectionsFromSearchTerm,
getFirstKeyForList,
getHeaderMessage,
getPersonalDetailSearchTerms,
getUserToInviteOption,
getValidOptions,
} from '@libs/OptionsListUtils';
import type {OptionData} from '@libs/ReportUtils';
import variables from '@styles/variables';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -51,7 +60,7 @@ function useOptions() {
});

const defaultOptions = useMemo(() => {
const filteredOptions = OptionsListUtils.getValidOptions(
const filteredOptions = getValidOptions(
{
reports: listOptions.reports ?? [],
personalDetails: listOptions.personalDetails ?? [],
Expand All @@ -66,7 +75,7 @@ function useOptions() {
}, [betas, listOptions.personalDetails, listOptions.reports, selectedOptions]);

const options = useMemo(() => {
const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchTerm, {
const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchTerm, {
selectedOptions,
maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
});
Expand All @@ -75,11 +84,11 @@ function useOptions() {
}, [debouncedSearchTerm, defaultOptions, selectedOptions]);
const cleanSearchTerm = useMemo(() => debouncedSearchTerm.trim().toLowerCase(), [debouncedSearchTerm]);
const headerMessage = useMemo(() => {
return OptionsListUtils.getHeaderMessage(
return getHeaderMessage(
options.personalDetails.length + options.recentReports.length !== 0,
!!options.userToInvite,
debouncedSearchTerm.trim(),
selectedOptions.some((participant) => OptionsListUtils.getPersonalDetailSearchTerms(participant).join(' ').toLowerCase?.().includes(cleanSearchTerm)),
selectedOptions.some((participant) => getPersonalDetailSearchTerms(participant).join(' ').toLowerCase?.().includes(cleanSearchTerm)),
);
}, [cleanSearchTerm, debouncedSearchTerm, options.personalDetails.length, options.recentReports.length, options.userToInvite, selectedOptions]);

Expand All @@ -88,7 +97,7 @@ function useOptions() {
return;
}

Report.searchInServer(debouncedSearchTerm);
searchInServer(debouncedSearchTerm);
}, [debouncedSearchTerm]);

useEffect(() => {
Expand All @@ -102,7 +111,7 @@ function useOptions() {
}
let participantOption: OptionData | undefined | null = listOptions.personalDetails.find((option) => option.accountID === participant.accountID);
if (!participantOption) {
participantOption = OptionsListUtils.getUserToInviteOption({
participantOption = getUserToInviteOption({
searchValue: participant?.login,
});
}
Expand Down Expand Up @@ -146,23 +155,23 @@ function NewChatPage() {
useOptions();

const [sections, firstKeyForList] = useMemo(() => {
const sectionsList: OptionsListUtils.Section[] = [];
const sectionsList: Section[] = [];
let firstKey = '';

const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(debouncedSearchTerm, selectedOptions, recentReports, personalDetails);
const formatResults = formatSectionsFromSearchTerm(debouncedSearchTerm, selectedOptions, recentReports, personalDetails);
sectionsList.push(formatResults.section);

if (!firstKey) {
firstKey = OptionsListUtils.getFirstKeyForList(formatResults.section.data);
firstKey = getFirstKeyForList(formatResults.section.data);
}

sectionsList.push({
title: translate('common.recents'),
data: recentReports,
data: selectedOptions.length ? recentReports.filter((option) => !option.isSelfDM) : recentReports,
shouldShow: !isEmpty(recentReports),
});
if (!firstKey) {
firstKey = OptionsListUtils.getFirstKeyForList(recentReports);
firstKey = getFirstKeyForList(recentReports);
}

sectionsList.push({
Expand All @@ -171,7 +180,7 @@ function NewChatPage() {
shouldShow: !isEmpty(personalDetails),
});
if (!firstKey) {
firstKey = OptionsListUtils.getFirstKeyForList(personalDetails);
firstKey = getFirstKeyForList(personalDetails);
}

if (userToInvite) {
Expand All @@ -181,23 +190,52 @@ function NewChatPage() {
shouldShow: true,
});
if (!firstKey) {
firstKey = OptionsListUtils.getFirstKeyForList([userToInvite]);
firstKey = getFirstKeyForList([userToInvite]);
}
}

return [sectionsList, firstKey];
}, [debouncedSearchTerm, selectedOptions, recentReports, personalDetails, translate, userToInvite]);

/**
Comment thread
FitseTLT marked this conversation as resolved.
* Creates a new 1:1 chat with the option and the current user,
* Removes a selected option from list if already selected. If not already selected add this option to the list.
*/
const toggleOption = useCallback(
(option: ListItem & Partial<OptionData>) => {
const isOptionInList = !!option.isSelected;

let newSelectedOptions;

if (isOptionInList) {
newSelectedOptions = reject(selectedOptions, (selectedOption) => selectedOption.login === option.login);
} else {
newSelectedOptions = [...selectedOptions, {...option, isSelected: true, selected: true, reportID: option.reportID ?? `${CONST.DEFAULT_NUMBER_ID}`}];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FitseTLT Please note that we shouldn't use ${CONST.DEFAULT_NUMBER_ID} to by pass the lint, it still violates the the lint rule.

https://expensify.slack.com/archives/C01GTK53T8Q/p1737913879440099

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW I did not make that change I only moved the function.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's weird. I saw it came from this commit: ef09dba

Do you know which added that? We should remind them.

}

selectionListRef?.current?.clearInputAfterSelect?.();
selectionListRef.current?.focusTextInput();
selectionListRef?.current?.scrollToIndex(Math.max(newSelectedOptions.length - 1, 0), true);
setSelectedOptions(newSelectedOptions);
},
[selectedOptions, setSelectedOptions],
);

/**
* If there are selected options already then it will toggle the option otherwise
* creates a new 1:1 chat with the option and the current user,
* or navigates to the existing chat if one with those participants already exists.
*/
const createChat = useCallback(
(option?: OptionsListUtils.Option) => {
const selectOption = useCallback(
(option?: Option) => {
if (option?.isSelfDM) {
Navigation.dismissModal(option.reportID);
return;
}
if (selectedOptions.length && option) {
toggleOption(option);
return;
}

let login = '';

if (option?.login) {
Expand All @@ -209,37 +247,16 @@ function NewChatPage() {
Log.warn('Tried to create chat with empty login');
return;
}
Report.navigateToAndOpenReport([login]);
navigateToAndOpenReport([login]);
},
[selectedOptions],
[selectedOptions, toggleOption],
);

const itemRightSideComponent = useCallback(
(item: ListItem & OptionsListUtils.Option, isFocused?: boolean) => {
(item: ListItem & Option, isFocused?: boolean) => {
if (!!item.isSelfDM || (item.login && excludedGroupEmails.includes(item.login))) {
return null;
}
/**
* Removes a selected option from list if already selected. If not already selected add this option to the list.
* @param option
*/
function toggleOption(option: ListItem & Partial<OptionData>) {
const isOptionInList = !!option.isSelected;

let newSelectedOptions;

if (isOptionInList) {
newSelectedOptions = reject(selectedOptions, (selectedOption) => selectedOption.login === option.login);
} else {
newSelectedOptions = [...selectedOptions, {...option, isSelected: true, selected: true, reportID: option.reportID ?? `${CONST.DEFAULT_NUMBER_ID}`}];
}

selectionListRef?.current?.clearInputAfterSelect?.();

selectionListRef.current?.focusTextInput();
selectionListRef?.current?.scrollToIndex(Math.max(newSelectedOptions.length - 1, 0), true);
setSelectedOptions(newSelectedOptions);
}

if (item.isSelected) {
return (
Expand Down Expand Up @@ -268,7 +285,7 @@ function NewChatPage() {
/>
);
},
[selectedOptions, setSelectedOptions, styles.alignItemsCenter, styles.buttonDefaultHovered, styles.flexRow, styles.ml0, styles.ml5, styles.optionSelectCircle, styles.pl2, translate],
[toggleOption, styles.alignItemsCenter, styles.buttonDefaultHovered, styles.flexRow, styles.ml0, styles.ml5, styles.optionSelectCircle, styles.pl2, translate],
);

const createGroup = useCallback(() => {
Expand All @@ -280,7 +297,7 @@ function NewChatPage() {
accountID: option.accountID ?? CONST.DEFAULT_NUMBER_ID,
}));
const logins = [...selectedParticipants, {login: personalData.login, accountID: personalData.accountID}];
Report.setGroupDraft({participants: logins});
setGroupDraft({participants: logins});
Keyboard.dismiss();
Navigation.navigate(ROUTES.NEW_CHAT_CONFIRM);
}, [selectedOptions, personalData]);
Expand Down Expand Up @@ -327,7 +344,7 @@ function NewChatPage() {
// This is because when wrapping whole screen the screen was freezing when changing Tabs.
keyboardVerticalOffset={variables.contentHeaderHeight + top + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding}
>
<SelectionList<OptionsListUtils.Option & ListItem>
<SelectionList<Option & ListItem>
ref={selectionListRef}
ListItem={UserListItem}
sections={areOptionsInitialized ? sections : CONST.EMPTY_ARRAY}
Expand All @@ -336,13 +353,13 @@ function NewChatPage() {
onChangeText={setSearchTerm}
textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')}
headerMessage={headerMessage}
onSelectRow={createChat}
onSelectRow={selectOption}
shouldSingleExecuteRowSelect
onConfirm={(e, option) => (selectedOptions.length > 0 ? createGroup() : createChat(option))}
onConfirm={(e, option) => (selectedOptions.length > 0 ? createGroup() : selectOption(option))}
rightHandSideComponent={itemRightSideComponent}
footerContent={footerContent}
showLoadingPlaceholder={!areOptionsInitialized}
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
shouldPreventDefaultFocusOnSelectRow={!canUseTouchScreen()}
isLoadingNewOptions={!!isSearchingForReports}
initiallyFocusedOptionKey={firstKeyForList}
shouldTextInputInterceptSwipe
Expand Down