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

20354 task share destination selector refactor #35413

Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function BaseSelectionList<TItem extends User | RadioItem>(
shouldShowTooltips = true,
shouldUseDynamicMaxToRenderPerBatch = false,
rightHandSideComponent,
isLoadingNewOptions = false,
}: BaseSelectionListProps<TItem>,
inputRef: ForwardedRef<RNTextInput>,
) {
Expand Down Expand Up @@ -417,6 +418,7 @@ function BaseSelectionList<TItem extends User | RadioItem>(
spellCheck={false}
onSubmitEditing={selectFocusedOption}
blurOnSubmit={!!flattenedSections.allOptions.length}
isLoading={isLoadingNewOptions}
/>
</View>
)}
Expand Down
3 changes: 3 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ type BaseSelectionListProps<TItem extends User | RadioItem> = Partial<ChildrenPr

/** Component to display on the right side of each child */
rightHandSideComponent?: ((item: TItem) => ReactElement<TItem>) | ReactElement | null;

/** Whether to show the loading indicator for new options */
isLoadingNewOptions?: boolean;
};

type ItemLayout = {
Expand Down
181 changes: 60 additions & 121 deletions src/pages/tasks/TaskShareDestinationSelectorModal.js
Original file line number Diff line number Diff line change
@@ -1,154 +1,103 @@
/* eslint-disable es/no-optional-chaining */
import keys from 'lodash/keys';
import reduce from 'lodash/reduce';
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import React, {useEffect, useMemo} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import OptionsSelector from '@components/OptionsSelector';
import {usePersonalDetails} from '@components/OnyxProvider';
import ScreenWrapper from '@components/ScreenWrapper';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useNetwork from '@hooks/useNetwork';
import SelectionList from '@components/SelectionList';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Report from '@libs/actions/Report';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportPropTypes from '@pages/reportPropTypes';
import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

const propTypes = {
/* Onyx Props */

/** Beta features list */
betas: PropTypes.arrayOf(PropTypes.string),

/** All of the personal details for everyone */
personalDetails: PropTypes.objectOf(personalDetailsPropType),

/** All reports shared with the user */
reports: PropTypes.objectOf(reportPropTypes),

/** Whether we are searching for reports in the server */
/** Whether or not we are searching for reports on the server */
isSearchingForReports: PropTypes.bool,

...withLocalizePropTypes,
};

const defaultProps = {
betas: [],
personalDetails: {},
reports: {},
isSearchingForReports: false,
};

function TaskShareDestinationSelectorModal(props) {
const styles = useThemeStyles();
const [searchValue, setSearchValue] = useState('');
const [headerMessage, setHeaderMessage] = useState('');
const [filteredRecentReports, setFilteredRecentReports] = useState([]);
const selectReportHandler = (option) => {
if (!option || !option.reportID) {
return;
}

const {inputCallbackRef} = useAutoFocusInput();
const {isSearchingForReports} = props;
const {isOffline} = useNetwork();
Task.setShareDestinationValue(option.reportID);
Navigation.goBack(ROUTES.NEW_TASK);
};

const filteredReports = useMemo(() => {
const reports = {};
_.keys(props.reports).forEach((reportKey) => {
if (
!ReportUtils.canUserPerformWriteAction(props.reports[reportKey]) ||
!ReportUtils.canCreateTaskInReport(props.reports[reportKey]) ||
ReportUtils.isCanceledTaskReport(props.reports[reportKey])
) {
return;
const reportFilter = (reports) =>
reduce(
keys(reports),
(filtered, reportKey) => {
const report = reports[reportKey];
if (ReportUtils.canUserPerformWriteAction(report) && ReportUtils.canCreateTaskInReport(report) && !ReportUtils.isCanceledTaskReport(report)) {
return {...filtered, [reportKey]: report};
}
reports[reportKey] = props.reports[reportKey];
});
return reports;
}, [props.reports]);
const updateOptions = useCallback(() => {
const {recentReports} = OptionsListUtils.getShareDestinationOptions(filteredReports, props.personalDetails, props.betas, searchValue.trim(), [], CONST.EXPENSIFY_EMAILS, true);

setHeaderMessage(OptionsListUtils.getHeaderMessage(recentReports?.length !== 0, false, searchValue));

setFilteredRecentReports(recentReports);
}, [props, searchValue, filteredReports]);

useEffect(() => {
const debouncedSearch = _.debounce(updateOptions, 150);
debouncedSearch();
return () => {
debouncedSearch.cancel();
};
}, [updateOptions]);
return filtered;
},
{},
);

const getSections = () => {
const sections = [];
let indexOffset = 0;
function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) {
const styles = useThemeStyles();
Copy link
Member

@rushatgabhane rushatgabhane Feb 14, 2024

Choose a reason for hiding this comment

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

The task share page has a stuttering animation.

Screen.Recording.2024-02-14.at.08.32.26.mov

const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
const {translate} = useLocalize();
const personalDetails = usePersonalDetails();

if (filteredRecentReports?.length > 0) {
sections.push({
data: filteredRecentReports,
shouldShow: true,
indexOffset,
});
indexOffset += filteredRecentReports?.length;
}
const options = useMemo(() => {
const filteredReports = reportFilter(reports);

return sections;
};
const {recentReports} = OptionsListUtils.getShareDestinationOptions(filteredReports, personalDetails, [], debouncedSearchValue.trim(), [], CONST.EXPENSIFY_EMAILS, true);

const selectReport = (option) => {
if (!option) {
return;
}
const headerMessage = OptionsListUtils.getHeaderMessage(recentReports && recentReports.length !== 0, false, debouncedSearchValue);

if (option.reportID) {
Task.setShareDestinationValue(option.reportID);
Navigation.goBack(ROUTES.NEW_TASK);
}
};
const sections = recentReports && recentReports.length > 0 ? [{data: recentReports, shouldShow: true}] : [];

// When search term updates we will fetch any reports
const setSearchTermAndSearchInServer = useCallback((text = '') => {
Report.searchInServer(text);
setSearchValue(text);
}, []);
return {sections, headerMessage};
}, [personalDetails, reports, debouncedSearchValue]);

const sections = getSections();
useEffect(() => {
Report.searchInServer(debouncedSearchValue);
}, [debouncedSearchValue]);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={TaskShareDestinationSelectorModal.displayName}
testID="TaskShareDestinationSelectorModal"
>
{({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
<>
<HeaderWithBackButton
title={props.translate('newTaskPage.shareSomewhere')}
title={translate('newTaskPage.shareSomewhere')}
onBackButtonPress={() => Navigation.goBack(ROUTES.NEW_TASK)}
/>
<View style={[styles.flex1, styles.w100, styles.pRelative]}>
<OptionsSelector
sections={sections}
onSelectRow={selectReport}
onChangeText={setSearchTermAndSearchInServer}
headerMessage={headerMessage}
hideSection
Headers
showTitleTooltip
shouldShowOptions={didScreenTransitionEnd}
textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')}
textInputAlert={isOffline ? `${props.translate('common.youAppearToBeOffline')} ${props.translate('search.resultsAreLimited')}` : ''}
<SelectionList
Copy link
Member

Choose a reason for hiding this comment

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

@lukemorawski on this branch, no offline message appears. was it intentionally removed?

current branch
image

staging
image

sections={didScreenTransitionEnd ? options.sections : CONST.EMPTY_ARRAY}
onSelectRow={selectReportHandler}
onChangeText={setSearchValue}
textInputValue={searchValue}
headerMessage={options.headerMessage}
textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle}
autoFocus={false}
ref={inputCallbackRef}
showLoadingPlaceholder={!didScreenTransitionEnd}
isLoadingNewOptions={isSearchingForReports}
/>
</View>
Expand All @@ -158,25 +107,15 @@ function TaskShareDestinationSelectorModal(props) {
);
}

TaskShareDestinationSelectorModal.displayName = 'TaskShareDestinationSelectorModal';
TaskShareDestinationSelectorModal.propTypes = propTypes;
lukemorawski marked this conversation as resolved.
Show resolved Hide resolved
TaskShareDestinationSelectorModal.defaultProps = defaultProps;

export default compose(
withLocalize,
withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
betas: {
key: ONYXKEYS.BETAS,
},
isSearchingForReports: {
key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS,
initWithStoredValues: false,
},
}),
)(TaskShareDestinationSelectorModal);
export default withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
isSearchingForReports: {
key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS,
initWithStoredValues: false,
},
})(TaskShareDestinationSelectorModal);
Loading