From 7beb521641b51f81a43fdf069cda2355c0d64aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3rawski?= Date: Tue, 30 Jan 2024 15:24:27 +0100 Subject: [PATCH 1/5] added isLoadingNewOptions prop to BaseSelectionList --- src/components/SelectionList/BaseSelectionList.tsx | 2 ++ src/components/SelectionList/types.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 815b80aaa50e..086ead66f820 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -58,6 +58,7 @@ function BaseSelectionList( shouldShowTooltips = true, shouldUseDynamicMaxToRenderPerBatch = false, rightHandSideComponent, + isLoadingNewOptions = false, }: BaseSelectionListProps, inputRef: ForwardedRef, ) { @@ -417,6 +418,7 @@ function BaseSelectionList( spellCheck={false} onSubmitEditing={selectFocusedOption} blurOnSubmit={!!flattenedSections.allOptions.length} + isLoading={isLoadingNewOptions} /> )} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index a82ddef6febb..222c818dd66d 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -230,6 +230,9 @@ type BaseSelectionListProps = Partial ReactElement) | ReactElement | null; + + /** Whether to show the loading indicator for new options */ + isLoadingNewOptions?: boolean; }; type ItemLayout = { From 4c45c2ab3b4e0e5fda321427fac9d302eec41015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3rawski?= Date: Tue, 30 Jan 2024 15:24:35 +0100 Subject: [PATCH 2/5] component refactor --- .../TaskShareDestinationSelectorModal.js | 181 ++++++------------ 1 file changed, 60 insertions(+), 121 deletions(-) diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 64fd5f50b61f..8d3051b0669f 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -1,22 +1,20 @@ -/* 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'; @@ -24,131 +22,82 @@ 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(); + 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 ( {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( <> Navigation.goBack(ROUTES.NEW_TASK)} /> - @@ -158,25 +107,15 @@ function TaskShareDestinationSelectorModal(props) { ); } -TaskShareDestinationSelectorModal.displayName = 'TaskShareDestinationSelectorModal'; TaskShareDestinationSelectorModal.propTypes = propTypes; 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); From a01ada256ab16612293d94027c7d01519af183ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3rawski?= Date: Wed, 31 Jan 2024 13:46:32 +0100 Subject: [PATCH 3/5] brought back displayName --- src/pages/tasks/TaskShareDestinationSelectorModal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 8d3051b0669f..b609ce02af86 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -107,6 +107,7 @@ function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) { ); } +TaskShareDestinationSelectorModal.displayName = 'TaskShareDestinationSelectorModal'; TaskShareDestinationSelectorModal.propTypes = propTypes; TaskShareDestinationSelectorModal.defaultProps = defaultProps; From 2a269b55c5ad520153a931c8da6ad1e9de849bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3rawski?= Date: Tue, 20 Feb 2024 16:29:03 +0100 Subject: [PATCH 4/5] offline text input hint --- src/pages/tasks/TaskShareDestinationSelectorModal.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index b609ce02af86..7a5b4b764e49 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -10,6 +10,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Report from '@libs/actions/Report'; import Navigation from '@libs/Navigation/Navigation'; @@ -60,6 +61,9 @@ function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) { const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); const {translate} = useLocalize(); const personalDetails = usePersonalDetails(); + const {isOffline} = useNetwork(); + + const textInputHint = useMemo(() => (isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''), [isOffline, translate]); const options = useMemo(() => { const filteredReports = reportFilter(reports); @@ -99,6 +103,7 @@ function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) { safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} showLoadingPlaceholder={!didScreenTransitionEnd} isLoadingNewOptions={isSearchingForReports} + textInputHint={textInputHint} /> From b028c51344fa7816a9f29466e6d21aa444ae3733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3rawski?= Date: Wed, 28 Feb 2024 16:49:18 +0100 Subject: [PATCH 5/5] crash fix --- src/pages/tasks/TaskShareDestinationSelectorModal.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 7a5b4b764e49..b62440b22967 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -8,6 +8,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {usePersonalDetails} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; +import UserListItem from '@components/SelectionList/UserListItem'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -94,6 +95,7 @@ function TaskShareDestinationSelectorModal({reports, isSearchingForReports}) { />