Skip to content

Commit

Permalink
Merge pull request #34638 from ruben-rebelo/ts-migration/newchatpage
Browse files Browse the repository at this point in the history
[TS Migration] Migrate NewChatPage
  • Loading branch information
jasperhuangg authored Feb 13, 2024
2 parents c4856c9 + 82eba33 commit ffe1420
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 80 deletions.
1 change: 1 addition & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ type OnyxValues = {
[ONYXKEYS.MAX_CANVAS_AREA]: number;
[ONYXKEYS.MAX_CANVAS_HEIGHT]: number;
[ONYXKEYS.MAX_CANVAS_WIDTH]: number;
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: boolean;
[ONYXKEYS.LAST_VISITED_PATH]: string | undefined;
[ONYXKEYS.RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields;
[ONYXKEYS.UPDATE_REQUIRED]: boolean;
Expand Down
6 changes: 3 additions & 3 deletions src/libs/DoInteractionTask/index.desktop.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {InteractionManager} from 'react-native';
import type DoInteractionTask from './types';

// For desktop, we should call the callback after all interactions to prevent freezing. See more detail in https://github.com/Expensify/App/issues/28916
function doInteractionTask(callback: () => void) {
return InteractionManager.runAfterInteractions(() => {
const doInteractionTask: DoInteractionTask = (callback) =>
InteractionManager.runAfterInteractions(() => {
callback();
});
}

export default doInteractionTask;
6 changes: 4 additions & 2 deletions src/libs/DoInteractionTask/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
function doInteractionTask(callback: () => void) {
import type DoInteractionTask from './types';

const doInteractionTask: DoInteractionTask = (callback) => {
callback();
return null;
}
};

export default doInteractionTask;
5 changes: 5 additions & 0 deletions src/libs/DoInteractionTask/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type {InteractionManager} from 'react-native';

type DoInteractionTask = (callback: () => void) => ReturnType<typeof InteractionManager.runAfterInteractions> | null;

export default DoInteractionTask;
6 changes: 3 additions & 3 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1756,7 +1756,7 @@ function getIOUConfirmationOptionsFromParticipants(participants: Participant[],
* Build the options for the New Group view
*/
function getFilteredOptions(
reports: Record<string, Report>,
reports: OnyxCollection<Report>,
personalDetails: OnyxEntry<PersonalDetailsList>,
betas: Beta[] = [],
searchValue = '',
Expand Down Expand Up @@ -1942,7 +1942,7 @@ function formatSectionsFromSearchTerm(
searchTerm: string,
selectedOptions: ReportUtils.OptionData[],
filteredRecentReports: ReportUtils.OptionData[],
filteredPersonalDetails: PersonalDetails[],
filteredPersonalDetails: ReportUtils.OptionData[],
maxOptionsSelected: boolean,
indexOffset = 0,
personalDetails: OnyxEntry<PersonalDetailsList> = {},
Expand Down Expand Up @@ -2025,4 +2025,4 @@ export {
transformedTaxRates,
};

export type {MemberForList};
export type {MemberForList, CategorySection};
134 changes: 62 additions & 72 deletions src/pages/NewChatPage.js → src/pages/NewChatPage.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,59 @@
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import KeyboardAvoidingView from '@components/KeyboardAvoidingView';
import OfflineIndicator from '@components/OfflineIndicator';
import OptionsSelector from '@components/OptionsSelector';
import ScreenWrapper from '@components/ScreenWrapper';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import compose from '@libs/compose';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import doInteractionTask from '@libs/DoInteractionTask';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
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 personalDetailsPropType from './personalDetailsPropType';
import reportPropTypes from './reportPropTypes';
import type * as OnyxTypes from '@src/types/onyx';
import type {DismissedReferralBanners} from '@src/types/onyx/Account';

const propTypes = {
/** Beta features list */
betas: PropTypes.arrayOf(PropTypes.string),
type NewChatPageWithOnyxProps = {
/** All reports shared with the user */
reports: OnyxCollection<OnyxTypes.Report>;

/** All of the personal details for everyone */
personalDetails: PropTypes.objectOf(personalDetailsPropType),
personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;

/** All reports shared with the user */
reports: PropTypes.objectOf(reportPropTypes),
betas: OnyxEntry<OnyxTypes.Beta[]>;

/** An object that holds data about which referral banners have been dismissed */
dismissedReferralBanners: PropTypes.objectOf(PropTypes.bool),

...windowDimensionsPropTypes,

...withLocalizePropTypes,
dismissedReferralBanners: DismissedReferralBanners;

/** Whether we are searching for reports in the server */
isSearchingForReports: PropTypes.bool,
isSearchingForReports: OnyxEntry<boolean>;
};

const defaultProps = {
betas: [],
dismissedReferralBanners: {},
personalDetails: {},
reports: {},
isSearchingForReports: false,
type NewChatPageProps = NewChatPageWithOnyxProps & {
isGroupChat: boolean;
};

const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE);
const excludedGroupEmails = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE);

function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, isSearchingForReports, dismissedReferralBanners}) {
function NewChatPage({betas, isGroupChat, personalDetails, reports, isSearchingForReports, dismissedReferralBanners}: NewChatPageProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const [searchTerm, setSearchTerm] = useState('');
const [filteredRecentReports, setFilteredRecentReports] = useState([]);
const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]);
const [filteredUserToInvite, setFilteredUserToInvite] = useState();
const [selectedOptions, setSelectedOptions] = useState([]);
const [filteredRecentReports, setFilteredRecentReports] = useState<ReportUtils.OptionData[]>([]);
const [filteredPersonalDetails, setFilteredPersonalDetails] = useState<ReportUtils.OptionData[]>([]);
const [filteredUserToInvite, setFilteredUserToInvite] = useState<ReportUtils.OptionData | null>();
const [selectedOptions, setSelectedOptions] = useState<OptionData[]>([]);
const {isOffline} = useNetwork();
const {isSmallScreenWidth} = useWindowDimensions();
const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false);
Expand All @@ -76,16 +66,18 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
Boolean(filteredUserToInvite),
searchTerm.trim(),
maxParticipantsReached,
_.some(selectedOptions, (participant) => participant.searchText.toLowerCase().includes(searchTerm.trim().toLowerCase())),
selectedOptions.some((participant) => participant?.searchText?.toLowerCase().includes(searchTerm.trim().toLowerCase())),
);

const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails);

const sections = useMemo(() => {
const sectionsList = [];
const sections = useMemo((): OptionsListUtils.CategorySection[] => {
const sectionsList: OptionsListUtils.CategorySection[] = [];
let indexOffset = 0;

const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, maxParticipantsReached, indexOffset);
sectionsList.push(formatResults.section);

indexOffset = formatResults.newIndexOffset;

if (maxParticipantsReached) {
Expand All @@ -95,15 +87,15 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
sectionsList.push({
title: translate('common.recents'),
data: filteredRecentReports,
shouldShow: !_.isEmpty(filteredRecentReports),
shouldShow: filteredRecentReports.length > 0,
indexOffset,
});
indexOffset += filteredRecentReports.length;

sectionsList.push({
title: translate('common.contacts'),
data: filteredPersonalDetails,
shouldShow: !_.isEmpty(filteredPersonalDetails),
shouldShow: filteredPersonalDetails.length > 0,
indexOffset,
});
indexOffset += filteredPersonalDetails.length;
Expand All @@ -122,15 +114,14 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i

/**
* Removes a selected option from list if already selected. If not already selected add this option to the list.
* @param {Object} option
*/
const toggleOption = (option) => {
const isOptionInList = _.some(selectedOptions, (selectedOption) => selectedOption.login === option.login);
const toggleOption = (option: OptionData) => {
const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login);

let newSelectedOptions;

if (isOptionInList) {
newSelectedOptions = _.reject(selectedOptions, (selectedOption) => selectedOption.login === option.login);
newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login);
} else {
newSelectedOptions = [...selectedOptions, option];
}
Expand All @@ -142,7 +133,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
} = OptionsListUtils.getFilteredOptions(
reports,
personalDetails,
betas,
betas ?? [],
searchTerm,
newSelectedOptions,
isGroupChat ? excludedGroupEmails : [],
Expand All @@ -166,10 +157,11 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
/**
* 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.
*
* @param {Object} option
*/
const createChat = (option) => {
const createChat = (option: OptionData) => {
if (!option.login) {
return;
}
Report.navigateToAndOpenReport([option.login]);
};

Expand All @@ -178,10 +170,12 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
* or navigates to the existing chat if one with those participants already exists.
*/
const createGroup = () => {
const logins = _.pluck(selectedOptions, 'login');
const logins = selectedOptions.map((option) => option.login).filter((login): login is string => typeof login === 'string');

if (logins.length < 1) {
return;
}

Report.navigateToAndOpenReport(logins);
};

Expand All @@ -193,7 +187,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
} = OptionsListUtils.getFilteredOptions(
reports,
personalDetails,
betas,
betas ?? [],
searchTerm,
selectedOptions,
isGroupChat ? excludedGroupEmails : [],
Expand Down Expand Up @@ -223,6 +217,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
if (!interactionTask) {
return;
}

interactionTask.cancel();
};
}, []);
Expand Down Expand Up @@ -251,11 +246,12 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
behavior="padding"
// Offset is needed as KeyboardAvoidingView in nested inside of TabNavigator instead of wrapping whole screen.
// This is because when wrapping whole screen the screen was freezing when changing Tabs.
keyboardVerticalOffset={variables.contentHeaderHeight + insets.top + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding}
keyboardVerticalOffset={variables.contentHeaderHeight + (insets?.top ?? 0) + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding}
>
<View style={[styles.flex1, styles.w100, styles.pRelative, selectedOptions.length > 0 ? safeAreaPaddingBottomStyle : {}]}>
<OptionsSelector
ref={inputCallbackRef}
// @ts-expect-error TODO: Remove this once OptionsSelector (https://github.com/Expensify/App/issues/25125) is migrated to TypeScript.
canSelectMultipleOptions
shouldShowMultipleOptionSelectorAsButton
multipleOptionSelectorButtonText={translate('newChatPage.addToGroup')}
Expand Down Expand Up @@ -287,30 +283,24 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
);
}

NewChatPage.propTypes = propTypes;
NewChatPage.defaultProps = defaultProps;
NewChatPage.displayName = 'NewChatPage';

export default compose(
withLocalize,
withWindowDimensions,
withOnyx({
dismissedReferralBanners: {
key: ONYXKEYS.ACCOUNT,
selector: (data) => data.dismissedReferralBanners || {},
},
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
betas: {
key: ONYXKEYS.BETAS,
},
isSearchingForReports: {
key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS,
initWithStoredValues: false,
},
}),
)(NewChatPage);
export default withOnyx<NewChatPageProps, NewChatPageWithOnyxProps>({
dismissedReferralBanners: {
key: ONYXKEYS.ACCOUNT,
selector: (data) => data?.dismissedReferralBanners ?? {},
},
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
betas: {
key: ONYXKEYS.BETAS,
},
isSearchingForReports: {
key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS,
initWithStoredValues: false,
},
})(NewChatPage);

0 comments on commit ffe1420

Please sign in to comment.