From 639136ad63796d54e7c18af31b6cd769fcd4f9e4 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Tue, 7 Nov 2023 10:38:53 +0530 Subject: [PATCH 01/73] fix: do not append whitespace to emoji if whitespace is already present --- .../home/report/ReportActionCompose/ComposerWithSuggestions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index b306676d476a..d99e93e8f628 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -259,7 +259,7 @@ function ComposerWithSuggestions({ (commentValue, shouldDebounceSaveComment) => { raiseIsScrollLikelyLayoutTriggered(); const {startIndex, endIndex, diff} = findNewlyAddedChars(lastTextRef.current, commentValue); - const isEmojiInserted = diff.length && endIndex > startIndex && EmojiUtils.containsOnlyEmojis(diff); + const isEmojiInserted = diff.length && endIndex > startIndex && diff.trim() === diff && EmojiUtils.containsOnlyEmojis(diff); const {text: newComment, emojis} = EmojiUtils.replaceAndExtractEmojis( isEmojiInserted ? insertWhiteSpace(commentValue, endIndex) : commentValue, preferredSkinTone, From 6062753ff9e20bae3f6f1ca1623c55c4f63ff1c3 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Wed, 8 Nov 2023 11:06:19 +0530 Subject: [PATCH 02/73] fix: whitespace after emoji with skin tone --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index cf1fe0e2ec3c..32ea949c4593 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -224,11 +224,13 @@ function ComposerWithSuggestions({ startIndex = currentIndex; // if text is getting pasted over find length of common suffix and subtract it from new text length + const commonSuffixLength = ComposerUtils.getCommonSuffixLength(prevText, newText); if (selection.end - selection.start > 0) { - const commonSuffixLength = ComposerUtils.getCommonSuffixLength(prevText, newText); endIndex = newText.length - commonSuffixLength; + } else if (commonSuffixLength > 0) { + endIndex = currentIndex + newText.length - prevText.length; } else { - endIndex = currentIndex + (newText.length - prevText.length); + endIndex = currentIndex + newText.length; } } From 4d6412eb31349659cf973616c4e0e266e2914f0d Mon Sep 17 00:00:00 2001 From: Aswin S Date: Wed, 8 Nov 2023 11:29:28 +0530 Subject: [PATCH 03/73] fix: whitespace after short codes right before a word --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 32ea949c4593..4e906bd81f9f 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -225,10 +225,8 @@ function ComposerWithSuggestions({ // if text is getting pasted over find length of common suffix and subtract it from new text length const commonSuffixLength = ComposerUtils.getCommonSuffixLength(prevText, newText); - if (selection.end - selection.start > 0) { + if (commonSuffixLength > 0 || selection.end - selection.start > 0) { endIndex = newText.length - commonSuffixLength; - } else if (commonSuffixLength > 0) { - endIndex = currentIndex + newText.length - prevText.length; } else { endIndex = currentIndex + newText.length; } From 276cae20b7f0c44d11fc3118216f0c03b71064ac Mon Sep 17 00:00:00 2001 From: Aswin S Date: Wed, 8 Nov 2023 11:48:00 +0530 Subject: [PATCH 04/73] fix: insert whitespace after emoji --- src/libs/ComposerUtils/index.ts | 6 +- .../ComposerWithSuggestions.js | 83 +++++++++++++++---- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/libs/ComposerUtils/index.ts b/src/libs/ComposerUtils/index.ts index 5a7da7ca08cf..58e1efa7aa65 100644 --- a/src/libs/ComposerUtils/index.ts +++ b/src/libs/ComposerUtils/index.ts @@ -32,7 +32,11 @@ function canSkipTriggerHotkeys(isSmallScreenWidth: boolean, isKeyboardShown: boo */ function getCommonSuffixLength(str1: string, str2: string): number { let i = 0; - while (str1[str1.length - 1 - i] === str2[str2.length - 1 - i]) { + if (str1.length === 0 || str2.length === 0) { + return 0; + } + const minLen = Math.min(str1.length, str2.length); + while (i < minLen && str1[str1.length - 1 - i] === str2[str2.length - 1 - i]) { i++; } return i; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index b69e65e854d7..8791c1e7cef9 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -119,6 +119,7 @@ function ComposerWithSuggestions({ return draft; }); const commentRef = useRef(value); + const lastTextRef = useRef(value); const {isSmallScreenWidth} = useWindowDimensions(); const maxComposerLines = isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; @@ -206,6 +207,50 @@ function ComposerWithSuggestions({ [], ); + /** + * Find the newly added characters between the previous text and the new text based on the selection. + * + * @param {string} prevText - The previous text. + * @param {string} newText - The new text. + * @returns {object} An object containing information about the newly added characters. + * @property {number} startIndex - The start index of the newly added characters in the new text. + * @property {number} endIndex - The end index of the newly added characters in the new text. + * @property {string} diff - The newly added characters. + */ + const findNewlyAddedChars = useCallback( + (prevText, newText) => { + let startIndex = -1; + let endIndex = -1; + let currentIndex = 0; + + // Find the first character mismatch with newText + while (currentIndex < newText.length && prevText.charAt(currentIndex) === newText.charAt(currentIndex) && selection.start > currentIndex) { + currentIndex++; + } + + if (currentIndex < newText.length) { + startIndex = currentIndex; + + const commonSuffixLength = ComposerUtils.getCommonSuffixLength(prevText, newText); + // if text is getting pasted over find length of common suffix and subtract it from new text length + if (commonSuffixLength > 0 || selection.end - selection.start > 0) { + endIndex = newText.length - commonSuffixLength; + } else { + endIndex = currentIndex + newText.length + } + } + + return { + startIndex, + endIndex, + diff: newText.substring(startIndex, endIndex), + }; + }, + [selection.end, selection.start], + ); + + const insertWhiteSpace = (text, index) => `${text.slice(0, index)} ${text.slice(index)}`; + /** * Update the value of the comment in Onyx * @@ -215,7 +260,13 @@ function ComposerWithSuggestions({ const updateComment = useCallback( (commentValue, shouldDebounceSaveComment) => { raiseIsScrollLikelyLayoutTriggered(); - const {text: newComment, emojis} = EmojiUtils.replaceAndExtractEmojis(commentValue, preferredSkinTone, preferredLocale); + const {startIndex, endIndex, diff} = findNewlyAddedChars(lastTextRef.current, commentValue); + const isEmojiInserted = diff.length && endIndex > startIndex && diff.trim() === diff && EmojiUtils.containsOnlyEmojis(diff); + const {text: newComment, emojis} = EmojiUtils.replaceAndExtractEmojis( + isEmojiInserted ? insertWhiteSpace(commentValue, endIndex) : commentValue, + preferredSkinTone, + preferredLocale, + ); if (!_.isEmpty(emojis)) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (!_.isEmpty(newEmojis)) { @@ -260,13 +311,14 @@ function ComposerWithSuggestions({ } }, [ - debouncedUpdateFrequentlyUsedEmojis, - preferredLocale, + raiseIsScrollLikelyLayoutTriggered, + findNewlyAddedChars, preferredSkinTone, - reportID, + preferredLocale, setIsCommentEmpty, suggestionsRef, - raiseIsScrollLikelyLayoutTriggered, + debouncedUpdateFrequentlyUsedEmojis, + reportID, debouncedSaveReportComment, ], ); @@ -317,14 +369,8 @@ function ComposerWithSuggestions({ * @param {Boolean} shouldAddTrailSpace */ const replaceSelectionWithText = useCallback( - (text, shouldAddTrailSpace = true) => { - const updatedText = shouldAddTrailSpace ? `${text} ` : text; - const selectionSpaceLength = shouldAddTrailSpace ? CONST.SPACE_LENGTH : 0; - updateComment(ComposerUtils.insertText(commentRef.current, selection, updatedText)); - setSelection((prevSelection) => ({ - start: prevSelection.start + text.length + selectionSpaceLength, - end: prevSelection.start + text.length + selectionSpaceLength, - })); + (text) => { + updateComment(ComposerUtils.insertText(commentRef.current, selection, text)); }, [selection, updateComment], ); @@ -448,7 +494,12 @@ function ComposerWithSuggestions({ } focus(); - replaceSelectionWithText(e.key, false); + // Reset cursor to last known location + setSelection((prevSelection) => ({ + start: prevSelection.start + 1, + end: prevSelection.end + 1, + })); + replaceSelectionWithText(e.key); }, [checkComposerVisibility, focus, replaceSelectionWithText], ); @@ -524,6 +575,10 @@ function ComposerWithSuggestions({ [blur, focus, prepareCommentAndResetComposer, replaceSelectionWithText], ); + useEffect(() => { + lastTextRef.current = value; + }, [value]); + return ( <> From d7269601b2d90d10ae230144787d376ef5a33f07 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Wed, 8 Nov 2023 11:55:53 +0530 Subject: [PATCH 05/73] fix: prettier issue --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 8791c1e7cef9..518339828e2a 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -236,7 +236,7 @@ function ComposerWithSuggestions({ if (commonSuffixLength > 0 || selection.end - selection.start > 0) { endIndex = newText.length - commonSuffixLength; } else { - endIndex = currentIndex + newText.length + endIndex = currentIndex + newText.length; } } From 80535723c3f0301bcf508100116ea59b837bbfb4 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Wed, 8 Nov 2023 12:04:01 +0530 Subject: [PATCH 06/73] fix: prevent duplicate character insertion on refocus --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 518339828e2a..6c906246121b 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -494,14 +494,14 @@ function ComposerWithSuggestions({ } focus(); + // Reset cursor to last known location setSelection((prevSelection) => ({ start: prevSelection.start + 1, end: prevSelection.end + 1, })); - replaceSelectionWithText(e.key); }, - [checkComposerVisibility, focus, replaceSelectionWithText], + [checkComposerVisibility, focus], ); const blur = useCallback(() => { From 658038c9865fee0d2a630c13242b118935168433 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Fri, 24 Nov 2023 23:10:18 +0530 Subject: [PATCH 07/73] fix: refactor utility methods --- src/libs/ComposerUtils/index.ts | 33 ++++++++++++++++++- .../ComposerWithSuggestions.js | 20 ++++------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/libs/ComposerUtils/index.ts b/src/libs/ComposerUtils/index.ts index 32ebca9afee8..54af287a67b7 100644 --- a/src/libs/ComposerUtils/index.ts +++ b/src/libs/ComposerUtils/index.ts @@ -14,6 +14,17 @@ function insertText(text: string, selection: Selection, textToInsert: string): s return text.slice(0, selection.start) + textToInsert + text.slice(selection.end, text.length); } +/** + * Insert a white space at given index of text + * @param text - text that needs whitespace to be appended to + * @param index - index at which whitespace should be inserted + * @returns + */ + +function insertWhiteSpaceAtIndex(text: string, index: number) { + return `${text.slice(0, index)} ${text.slice(index)}`; +} + /** * Check whether we can skip trigger hotkeys on some specific devices. */ @@ -23,4 +34,24 @@ function canSkipTriggerHotkeys(isSmallScreenWidth: boolean, isKeyboardShown: boo return (isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen()) || isKeyboardShown; } -export {getNumberOfLines, updateNumberOfLines, insertText, canSkipTriggerHotkeys}; +/** + * Finds the length of common suffix between two texts + * @param str1 - first string to compare + * @param str2 - next string to compare + * @returns number - Length of the common suffix + */ +function findCommonSuffixLength(str1: string, str2: string) { + let commonSuffixLength = 0; + const minLength = Math.min(str1.length, str2.length); + for (let i = 1; i <= minLength; i++) { + if (str1.charAt(str1.length - i) === str2.charAt(str2.length - i)) { + commonSuffixLength++; + } else { + break; + } + } + + return commonSuffixLength; +} + +export {getNumberOfLines, updateNumberOfLines, insertText, canSkipTriggerHotkeys, insertWhiteSpaceAtIndex, findCommonSuffixLength}; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 1425b872f7f1..8793f617b306 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -209,7 +209,6 @@ function ComposerWithSuggestions({ [], ); - /** * Find the newly added characters between the previous text and the new text based on the selection. * @@ -226,14 +225,6 @@ function ComposerWithSuggestions({ let endIndex = -1; let currentIndex = 0; - const getCommonSuffixLength=(str1, str2) =>{ - let i = 0; - while (str1[str1.length - 1 - i] === str2[str2.length - 1 - i]) { - i++; - } - return i; - } - // Find the first character mismatch with newText while (currentIndex < newText.length && prevText.charAt(currentIndex) === newText.charAt(currentIndex) && selection.start > currentIndex) { currentIndex++; @@ -241,8 +232,7 @@ function ComposerWithSuggestions({ if (currentIndex < newText.length) { startIndex = currentIndex; - - const commonSuffixLength = getCommonSuffixLength(prevText, newText); + const commonSuffixLength = ComposerUtils.findCommonSuffixLength(prevText, newText); // if text is getting pasted over find length of common suffix and subtract it from new text length if (commonSuffixLength > 0 || selection.end - selection.start > 0) { endIndex = newText.length - commonSuffixLength; @@ -260,8 +250,6 @@ function ComposerWithSuggestions({ [selection.end, selection.start], ); - const insertWhiteSpace = (text, index) => `${text.slice(0, index)} ${text.slice(index)}`; - /** * Update the value of the comment in Onyx * @@ -273,7 +261,11 @@ function ComposerWithSuggestions({ raiseIsScrollLikelyLayoutTriggered(); const {startIndex, endIndex, diff} = findNewlyAddedChars(lastTextRef.current, commentValue); const isEmojiInserted = diff.length && endIndex > startIndex && diff.trim() === diff && EmojiUtils.containsOnlyEmojis(diff); - const {text: newComment, emojis, cursorPosition} = EmojiUtils.replaceAndExtractEmojis(isEmojiInserted ? insertWhiteSpace(commentValue, endIndex) : commentValue, preferredSkinTone, preferredLocale); + const { + text: newComment, + emojis, + cursorPosition, + } = EmojiUtils.replaceAndExtractEmojis(isEmojiInserted ? ComposerUtils.insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue, preferredSkinTone, preferredLocale); if (!_.isEmpty(emojis)) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (!_.isEmpty(newEmojis)) { From 9eafd1fce280df3a53617063348a525f3f2ca6b5 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 7 Dec 2023 09:39:04 -0300 Subject: [PATCH 08/73] Add 'didScreenTransitionEnd' prop to MoneyRequestParticipantsSelector --- .../MoneyRequestParticipantsSelector.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index c08c8c0a21b8..8d6ed55724bf 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -66,6 +66,9 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, + /** Whether the screen transition has ended */ + didScreenTransitionEnd: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -78,6 +81,7 @@ const defaultProps = { betas: [], isDistanceRequest: false, isSearchingForReports: false, + didScreenTransitionEnd: false, }; function MoneyRequestParticipantsSelector({ @@ -94,6 +98,7 @@ function MoneyRequestParticipantsSelector({ iouType, isDistanceRequest, isSearchingForReports, + didScreenTransitionEnd, }) { const styles = useThemeStyles(); const [searchTerm, setSearchTerm] = useState(''); From a5ed7d8ddc0cdcb9d1caa0da9e391bcb3b751c04 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 7 Dec 2023 09:41:25 -0300 Subject: [PATCH 09/73] Integrate 'didScreenTransitionEnd' prop in MoneyRequestParticipantsPage --- .../MoneyRequestParticipantsPage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index d0982e6296db..edf452c78848 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -132,7 +132,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) { onEntryTransitionEnd={() => optionsSelectorRef.current && optionsSelectorRef.current.focus()} testID={MoneyRequestParticipantsPage.displayName} > - {({safeAreaPaddingBottomStyle}) => ( + {({safeAreaPaddingBottomStyle, didScreenTransitionEnd}) => ( )} From 19d6d6856cd2b713a8da56b47a8b4c35d444a7e8 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 7 Dec 2023 09:44:33 -0300 Subject: [PATCH 10/73] Conditionally update chatOptions on screen transition end --- .../MoneyRequestParticipantsSelector.js | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 8d6ed55724bf..292ace6c9c50 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -230,37 +230,39 @@ function MoneyRequestParticipantsSelector({ const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); useEffect(() => { - const chatOptions = OptionsListUtils.getFilteredOptions( - reports, - personalDetails, - betas, - searchTerm, - participants, - CONST.EXPENSIFY_EMAILS, - - // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user - // sees the option to request money from their admin on their own Workspace Chat. - iouType === CONST.IOU.TYPE.REQUEST, - - // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - !isDistanceRequest, - false, - {}, - [], - false, - {}, - [], - // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. - // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - !isDistanceRequest, - true, - ); - setNewChatOptions({ - recentReports: chatOptions.recentReports, - personalDetails: chatOptions.personalDetails, - userToInvite: chatOptions.userToInvite, - }); - }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, isDistanceRequest]); + if (didScreenTransitionEnd) { + const chatOptions = OptionsListUtils.getFilteredOptions( + reports, + personalDetails, + betas, + searchTerm, + participants, + CONST.EXPENSIFY_EMAILS, + + // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user + // sees the option to request money from their admin on their own Workspace Chat. + iouType === CONST.IOU.TYPE.REQUEST, + + // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. + !isDistanceRequest, + false, + {}, + [], + false, + {}, + [], + // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. + // This functionality is being built here: https://github.com/Expensify/App/issues/23291 + !isDistanceRequest, + true, + ); + setNewChatOptions({ + recentReports: chatOptions.recentReports, + personalDetails: chatOptions.personalDetails, + userToInvite: chatOptions.userToInvite, + }); + } + }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, isDistanceRequest, didScreenTransitionEnd]); // When search term updates we will fetch any reports const setSearchTermAndSearchInServer = useCallback((text = '') => { From ef06c1f4d00c4685b5cd8c11c7e95c91676a1461 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 14 Dec 2023 15:36:28 -0300 Subject: [PATCH 11/73] Revert "Conditionally update chatOptions on screen transition end" This reverts commit 19d6d6856cd2b713a8da56b47a8b4c35d444a7e8. --- .../MoneyRequestParticipantsSelector.js | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 14e94db38202..8c0fc71b0112 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -230,39 +230,37 @@ function MoneyRequestParticipantsSelector({ const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); useEffect(() => { - if (didScreenTransitionEnd) { - const chatOptions = OptionsListUtils.getFilteredOptions( - reports, - personalDetails, - betas, - searchTerm, - participants, - CONST.EXPENSIFY_EMAILS, - - // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user - // sees the option to request money from their admin on their own Workspace Chat. - iouType === CONST.IOU.TYPE.REQUEST, - - // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - !isDistanceRequest, - false, - {}, - [], - false, - {}, - [], - // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. - // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - !isDistanceRequest, - true, - ); - setNewChatOptions({ - recentReports: chatOptions.recentReports, - personalDetails: chatOptions.personalDetails, - userToInvite: chatOptions.userToInvite, - }); - } - }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, isDistanceRequest, didScreenTransitionEnd]); + const chatOptions = OptionsListUtils.getFilteredOptions( + reports, + personalDetails, + betas, + searchTerm, + participants, + CONST.EXPENSIFY_EMAILS, + + // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user + // sees the option to request money from their admin on their own Workspace Chat. + iouType === CONST.IOU.TYPE.REQUEST, + + // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. + !isDistanceRequest, + false, + {}, + [], + false, + {}, + [], + // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. + // This functionality is being built here: https://github.com/Expensify/App/issues/23291 + !isDistanceRequest, + true, + ); + setNewChatOptions({ + recentReports: chatOptions.recentReports, + personalDetails: chatOptions.personalDetails, + userToInvite: chatOptions.userToInvite, + }); + }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, isDistanceRequest]); // When search term updates we will fetch any reports const setSearchTermAndSearchInServer = useCallback((text = '') => { From 1108784a7451d387dc9ab2d54482c1b792903925 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 14 Dec 2023 15:37:56 -0300 Subject: [PATCH 12/73] Revert "Integrate 'didScreenTransitionEnd' prop in MoneyRequestParticipantsPage" This reverts commit a5ed7d8ddc0cdcb9d1caa0da9e391bcb3b751c04. --- .../MoneyRequestParticipantsPage.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index a2073f9f5d44..7826643d4283 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -133,7 +133,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) { onEntryTransitionEnd={() => optionsSelectorRef.current && optionsSelectorRef.current.focus()} testID={MoneyRequestParticipantsPage.displayName} > - {({safeAreaPaddingBottomStyle, didScreenTransitionEnd}) => ( + {({safeAreaPaddingBottomStyle}) => ( )} From c69ecfd1f3a1296c2a9be1a575abb5c5c39a829c Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 14 Dec 2023 15:38:24 -0300 Subject: [PATCH 13/73] Revert "Add 'didScreenTransitionEnd' prop to MoneyRequestParticipantsSelector" This reverts commit 9eafd1fce280df3a53617063348a525f3f2ca6b5. --- .../MoneyRequestParticipantsSelector.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 8c0fc71b0112..d8d644479270 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -66,9 +66,6 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, - /** Whether the screen transition has ended */ - didScreenTransitionEnd: PropTypes.bool, - ...withLocalizePropTypes, }; @@ -81,7 +78,6 @@ const defaultProps = { betas: [], isDistanceRequest: false, isSearchingForReports: false, - didScreenTransitionEnd: false, }; function MoneyRequestParticipantsSelector({ @@ -98,7 +94,6 @@ function MoneyRequestParticipantsSelector({ iouType, isDistanceRequest, isSearchingForReports, - didScreenTransitionEnd, }) { const styles = useThemeStyles(); const [searchTerm, setSearchTerm] = useState(''); From dc1f87fdc77b65716dc625b65c61be8fccb718e5 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 14 Dec 2023 16:01:52 -0300 Subject: [PATCH 14/73] Integrate 'didScreenTransitionEnd' prop in IOURequestStepParticipants --- .../step/IOURequestStepParticipants.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 85d67ea34bae..fe5633c2f05c 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -87,14 +87,17 @@ function IOURequestStepParticipants({ onEntryTransitionEnd={() => optionsSelectorRef.current && optionsSelectorRef.current.focus()} includeSafeAreaPaddingBottom > - (optionsSelectorRef.current = el)} - participants={participants} - onParticipantsAdded={addParticipant} - onFinish={goToNextStep} - iouType={iouType} - iouRequestType={iouRequestType} - /> + {({didScreenTransitionEnd}) => ( + (optionsSelectorRef.current = el)} + participants={participants} + onParticipantsAdded={addParticipant} + onFinish={goToNextStep} + iouType={iouType} + iouRequestType={iouRequestType} + didScreenTransitionEnd={didScreenTransitionEnd} + /> + )} ); } From 9cd0ab537b3c3f36438baf221352b2aabc5e7881 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 14 Dec 2023 16:03:38 -0300 Subject: [PATCH 15/73] Add 'didScreenTransitionEnd' prop to MoneyRequestParticipantsSelector --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 8d7d5cfceb77..6194398e2bb9 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -63,6 +63,9 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, + /** Whether the screen transition has ended */ + didScreenTransitionEnd: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -74,6 +77,7 @@ const defaultProps = { reports: {}, betas: [], isSearchingForReports: false, + didScreenTransitionEnd: false, }; function MoneyTemporaryForRefactorRequestParticipantsSelector({ @@ -89,6 +93,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType, iouRequestType, isSearchingForReports, + didScreenTransitionEnd, }) { const styles = useThemeStyles(); const [searchTerm, setSearchTerm] = useState(''); From a6a36f82518fdf220d41b754ee5723db64a61277 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 14 Dec 2023 16:05:15 -0300 Subject: [PATCH 16/73] Conditionally update 'chatOptions' on screen transition end --- ...yForRefactorRequestParticipantsSelector.js | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 6194398e2bb9..48fac9c1cef5 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -228,38 +228,40 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); useEffect(() => { - const chatOptions = OptionsListUtils.getFilteredOptions( - reports, - personalDetails, - betas, - searchTerm, - participants, - CONST.EXPENSIFY_EMAILS, - - // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user - // sees the option to request money from their admin on their own Workspace Chat. - iouType === CONST.IOU.TYPE.REQUEST, - - // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, - false, - {}, - [], - false, - {}, - [], - - // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. - // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, - true, - ); - setNewChatOptions({ - recentReports: chatOptions.recentReports, - personalDetails: chatOptions.personalDetails, - userToInvite: chatOptions.userToInvite, - }); - }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, iouRequestType]); + if (didScreenTransitionEnd) { + const chatOptions = OptionsListUtils.getFilteredOptions( + reports, + personalDetails, + betas, + searchTerm, + participants, + CONST.EXPENSIFY_EMAILS, + + // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user + // sees the option to request money from their admin on their own Workspace Chat. + iouType === CONST.IOU.TYPE.REQUEST, + + // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + false, + {}, + [], + false, + {}, + [], + + // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. + // This functionality is being built here: https://github.com/Expensify/App/issues/23291 + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + true, + ); + setNewChatOptions({ + recentReports: chatOptions.recentReports, + personalDetails: chatOptions.personalDetails, + userToInvite: chatOptions.userToInvite, + }); + } + }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, iouRequestType, didScreenTransitionEnd]); // When search term updates we will fetch any reports const setSearchTermAndSearchInServer = useCallback((text = '') => { @@ -324,7 +326,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} - shouldShowOptions={isOptionsDataReady} + shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} shouldShowReferralCTA referralContentType={iouType === 'send' ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST} shouldPreventDefaultFocusOnSelectRow={!Browser.isMobile()} From cb616829c5d7ecc94b915e0adc46f19cdfb673e5 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 14 Dec 2023 23:57:01 -0300 Subject: [PATCH 17/73] Use early return --- ...yForRefactorRequestParticipantsSelector.js | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 48fac9c1cef5..6341326b6eec 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -228,39 +228,40 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); useEffect(() => { - if (didScreenTransitionEnd) { - const chatOptions = OptionsListUtils.getFilteredOptions( - reports, - personalDetails, - betas, - searchTerm, - participants, - CONST.EXPENSIFY_EMAILS, - - // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user - // sees the option to request money from their admin on their own Workspace Chat. - iouType === CONST.IOU.TYPE.REQUEST, - - // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, - false, - {}, - [], - false, - {}, - [], - - // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. - // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, - true, - ); - setNewChatOptions({ - recentReports: chatOptions.recentReports, - personalDetails: chatOptions.personalDetails, - userToInvite: chatOptions.userToInvite, - }); + if (!didScreenTransitionEnd) { + return; } + const chatOptions = OptionsListUtils.getFilteredOptions( + reports, + personalDetails, + betas, + searchTerm, + participants, + CONST.EXPENSIFY_EMAILS, + + // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user + // sees the option to request money from their admin on their own Workspace Chat. + iouType === CONST.IOU.TYPE.REQUEST, + + // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + false, + {}, + [], + false, + {}, + [], + + // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. + // This functionality is being built here: https://github.com/Expensify/App/issues/23291 + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + true, + ); + setNewChatOptions({ + recentReports: chatOptions.recentReports, + personalDetails: chatOptions.personalDetails, + userToInvite: chatOptions.userToInvite, + }); }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, iouRequestType, didScreenTransitionEnd]); // When search term updates we will fetch any reports From ddd452f23225c7f3409c39a9f7c2fd14145f7f89 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 21 Dec 2023 15:38:48 +0700 Subject: [PATCH 18/73] fix: Green dot does not appear in LHN when the request is created in workspace chat --- src/libs/actions/IOU.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 802f0f00fffd..4653ca586039 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -302,7 +302,10 @@ function buildOnyxDataForMoneyRequest( optimisticPolicyRecentlyUsedTags, isNewChatReport, isNewIOUReport, + policyID, ) { + const isPolicyAdmin = ReportUtils.getPolicy(policyID).role === CONST.POLICY.ROLE.ADMIN; + const optimisticData = [ { // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page @@ -313,6 +316,7 @@ function buildOnyxDataForMoneyRequest( lastReadTime: DateUtils.getDBTime(), lastMessageTranslationKey: '', iouReportID: iouReport.reportID, + ...(isPolicyAdmin ? {hasOutstandingChildRequest: true} : {}), ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), }, }, @@ -459,6 +463,7 @@ function buildOnyxDataForMoneyRequest( iouReportID: chatReport.iouReportID, lastReadTime: chatReport.lastReadTime, pendingFields: null, + ...(isPolicyAdmin ? {hasOutstandingChildRequest: chatReport.hasOutstandingChildRequest} : {}), ...(isNewChatReport ? { errorFields: { @@ -752,6 +757,7 @@ function getMoneyRequestInformation( optimisticPolicyRecentlyUsedTags, isNewChatReport, isNewIOUReport, + participant.policyID, ); return { @@ -1430,6 +1436,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco optimisticPolicyRecentlyUsedTags, isNewOneOnOneChatReport, shouldCreateNewOneOnOneIOUReport, + participant.policyID, ); const individualSplit = { @@ -1955,6 +1962,7 @@ function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessi {}, isNewOneOnOneChatReport, shouldCreateNewOneOnOneIOUReport, + participant.policyID, ); splits.push({ From 4f25fa70385ad275a55d86f726a1cfca433a4b40 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Fri, 22 Dec 2023 00:57:12 -0300 Subject: [PATCH 19/73] Enhance 'OptionsListSkeletonView' for quicker display using Dimensions. --- src/components/OptionsListSkeletonView.js | 130 ++++++++++------------ 1 file changed, 58 insertions(+), 72 deletions(-) diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 2c46ac5d4d7a..f2e77f80f96e 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; -import {View} from 'react-native'; +import {View, Dimensions} from 'react-native'; import {Circle, Rect} from 'react-native-svg'; import compose from '@libs/compose'; import CONST from '@src/CONST'; @@ -9,64 +9,55 @@ import withTheme, {withThemePropTypes} from './withTheme'; import withThemeStyles, {withThemeStylesPropTypes} from './withThemeStyles'; const propTypes = { - /** Whether to animate the skeleton view */ - shouldAnimate: PropTypes.bool, - ...withThemeStylesPropTypes, - ...withThemePropTypes, + /** Whether to animate the skeleton view */ + shouldAnimate: PropTypes.bool, + ...withThemeStylesPropTypes, + ...withThemePropTypes, }; const defaultTypes = { - shouldAnimate: true, + shouldAnimate: true, }; class OptionsListSkeletonView extends React.Component { - constructor(props) { - super(props); - this.state = { - skeletonViewItems: [], - }; - } - - /** - * Generate the skeleton view items. - * - * @param {Number} numItems - */ - generateSkeletonViewItems(numItems) { - if (this.state.skeletonViewItems.length === numItems) { - return; - } + constructor(props) { + super(props); + const screenHeight = Dimensions.get('window').height; + const numItems = Math.ceil(screenHeight / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT); + this.state = { + skeletonViewItems: this.generateSkeletonViewItems(numItems), + }; + } - if (this.state.skeletonViewItems.length > numItems) { - this.setState((prevState) => ({ - skeletonViewItems: prevState.skeletonViewItems.slice(0, numItems), - })); - return; - } - - const skeletonViewItems = []; - for (let i = this.state.skeletonViewItems.length; i < numItems; i++) { - const step = i % 3; - let lineWidth; - switch (step) { - case 0: - lineWidth = '100%'; - break; - case 1: - lineWidth = '50%'; - break; - default: - lineWidth = '25%'; - } - skeletonViewItems.push( - + /** + * Generate the skeleton view items. + * + * @param {Number} numItems + */ + generateSkeletonViewItems(numItems) { + const skeletonViewItems = []; + for (let i = 0; i < numItems; i++) { + const step = i % 3; + let lineWidth; + switch (step) { + case 0: + lineWidth = '100%'; + break; + case 1: + lineWidth = '50%'; + break; + default: + lineWidth = '25%'; + } + skeletonViewItems.push( + - , - ); - } - - this.setState((prevState) => ({ - skeletonViewItems: [...prevState.skeletonViewItems, ...skeletonViewItems], - })); + , + ); } - render() { - return ( - { - const numItems = Math.ceil(event.nativeEvent.layout.height / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT); - this.generateSkeletonViewItems(numItems); - }} - > - {this.state.skeletonViewItems} - - ); - } + return skeletonViewItems; + } + + render() { + return ( + + {this.state.skeletonViewItems} + + ); + } } OptionsListSkeletonView.propTypes = propTypes; OptionsListSkeletonView.defaultProps = defaultTypes; export default compose(withThemeStyles, withTheme)(OptionsListSkeletonView); + From fefd084e6af6f8d9d6b40cf85a3f7557f3ac447c Mon Sep 17 00:00:00 2001 From: brunovjk Date: Sat, 23 Dec 2023 10:37:12 -0300 Subject: [PATCH 20/73] Revert "Enhance 'OptionsListSkeletonView' for quicker display using Dimensions." This reverts commit 4f25fa70385ad275a55d86f726a1cfca433a4b40. --- src/components/OptionsListSkeletonView.js | 130 ++++++++++++---------- 1 file changed, 72 insertions(+), 58 deletions(-) diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index f2e77f80f96e..2c46ac5d4d7a 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; -import {View, Dimensions} from 'react-native'; +import {View} from 'react-native'; import {Circle, Rect} from 'react-native-svg'; import compose from '@libs/compose'; import CONST from '@src/CONST'; @@ -9,55 +9,64 @@ import withTheme, {withThemePropTypes} from './withTheme'; import withThemeStyles, {withThemeStylesPropTypes} from './withThemeStyles'; const propTypes = { - /** Whether to animate the skeleton view */ - shouldAnimate: PropTypes.bool, - ...withThemeStylesPropTypes, - ...withThemePropTypes, + /** Whether to animate the skeleton view */ + shouldAnimate: PropTypes.bool, + ...withThemeStylesPropTypes, + ...withThemePropTypes, }; const defaultTypes = { - shouldAnimate: true, + shouldAnimate: true, }; class OptionsListSkeletonView extends React.Component { - constructor(props) { - super(props); - const screenHeight = Dimensions.get('window').height; - const numItems = Math.ceil(screenHeight / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT); - this.state = { - skeletonViewItems: this.generateSkeletonViewItems(numItems), - }; - } + constructor(props) { + super(props); + this.state = { + skeletonViewItems: [], + }; + } + + /** + * Generate the skeleton view items. + * + * @param {Number} numItems + */ + generateSkeletonViewItems(numItems) { + if (this.state.skeletonViewItems.length === numItems) { + return; + } - /** - * Generate the skeleton view items. - * - * @param {Number} numItems - */ - generateSkeletonViewItems(numItems) { - const skeletonViewItems = []; - for (let i = 0; i < numItems; i++) { - const step = i % 3; - let lineWidth; - switch (step) { - case 0: - lineWidth = '100%'; - break; - case 1: - lineWidth = '50%'; - break; - default: - lineWidth = '25%'; - } - skeletonViewItems.push( - + if (this.state.skeletonViewItems.length > numItems) { + this.setState((prevState) => ({ + skeletonViewItems: prevState.skeletonViewItems.slice(0, numItems), + })); + return; + } + + const skeletonViewItems = []; + for (let i = this.state.skeletonViewItems.length; i < numItems; i++) { + const step = i % 3; + let lineWidth; + switch (step) { + case 0: + lineWidth = '100%'; + break; + case 1: + lineWidth = '50%'; + break; + default: + lineWidth = '25%'; + } + skeletonViewItems.push( + - , - ); - } + , + ); + } - return skeletonViewItems; - } + this.setState((prevState) => ({ + skeletonViewItems: [...prevState.skeletonViewItems, ...skeletonViewItems], + })); + } - render() { - return ( - - {this.state.skeletonViewItems} - - ); - } + render() { + return ( + { + const numItems = Math.ceil(event.nativeEvent.layout.height / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT); + this.generateSkeletonViewItems(numItems); + }} + > + {this.state.skeletonViewItems} + + ); + } } OptionsListSkeletonView.propTypes = propTypes; OptionsListSkeletonView.defaultProps = defaultTypes; export default compose(withThemeStyles, withTheme)(OptionsListSkeletonView); - From 271ace6bab57ab3390c093c44e1a1a096d66e9c3 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Sat, 23 Dec 2023 11:23:26 -0300 Subject: [PATCH 21/73] Optimize OptionsListSkeletonView component --- src/CONST.ts | 1 + src/components/OptionsListSkeletonView.js | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index bc0a0c3216f0..544b4ce7e044 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -980,6 +980,7 @@ const CONST = { }, }, LHN_SKELETON_VIEW_ITEM_HEIGHT: 64, + SKELETON_VIEW_HEIGHT_THRESHOLD: 0.3, EXPENSIFY_PARTNER_NAME: 'expensify.com', EMAIL: { ACCOUNTING: 'accounting@expensify.com', diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 2c46ac5d4d7a..ab617df28943 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; -import {View} from 'react-native'; +import {View, Dimensions} from 'react-native'; import {Circle, Rect} from 'react-native-svg'; import compose from '@libs/compose'; import CONST from '@src/CONST'; @@ -22,8 +22,11 @@ const defaultTypes = { class OptionsListSkeletonView extends React.Component { constructor(props) { super(props); + const numItems = Math.ceil( + Dimensions.get('window').height * CONST.SKELETON_VIEW_HEIGHT_THRESHOLD / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT + ); this.state = { - skeletonViewItems: [], + skeletonViewItems: this.generateSkeletonViewItems(numItems), }; } From 99dd07a464741a96f06dd9f88373d5d361b0f715 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Wed, 27 Dec 2023 15:55:48 -0300 Subject: [PATCH 22/73] Revert "Optimize OptionsListSkeletonView component" This reverts commit 271ace6bab57ab3390c093c44e1a1a096d66e9c3. --- src/CONST.ts | 1 - src/components/OptionsListSkeletonView.js | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 1c0c1ef6112e..0fc684347243 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -986,7 +986,6 @@ const CONST = { }, }, LHN_SKELETON_VIEW_ITEM_HEIGHT: 64, - SKELETON_VIEW_HEIGHT_THRESHOLD: 0.3, EXPENSIFY_PARTNER_NAME: 'expensify.com', EMAIL: { ACCOUNTING: 'accounting@expensify.com', diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index ab617df28943..2c46ac5d4d7a 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; -import {View, Dimensions} from 'react-native'; +import {View} from 'react-native'; import {Circle, Rect} from 'react-native-svg'; import compose from '@libs/compose'; import CONST from '@src/CONST'; @@ -22,11 +22,8 @@ const defaultTypes = { class OptionsListSkeletonView extends React.Component { constructor(props) { super(props); - const numItems = Math.ceil( - Dimensions.get('window').height * CONST.SKELETON_VIEW_HEIGHT_THRESHOLD / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT - ); this.state = { - skeletonViewItems: this.generateSkeletonViewItems(numItems), + skeletonViewItems: [], }; } From d5278ef1a208f07ace0f121b34bd9848ba5ae878 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Thu, 4 Jan 2024 15:37:10 +0700 Subject: [PATCH 23/73] fix duplicate phone number can be invited --- src/libs/OptionsListUtils.js | 2 +- src/pages/RoomInvitePage.js | 10 ++++++++-- src/pages/workspace/WorkspaceInvitePage.js | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index fa3538b58ca6..bd869f3d8e0d 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1479,7 +1479,7 @@ function getOptions( if (includePersonalDetails) { // Next loop over all personal details removing any that are selectedUsers or recentChats _.each(allPersonalDetailsOptions, (personalDetailOption) => { - if (_.some(optionsToExclude, (optionToExclude) => optionToExclude.login === personalDetailOption.login)) { + if (_.some(optionsToExclude, (optionToExclude) => optionToExclude.login === addSMSDomainIfPhoneNumber(personalDetailOption.login))) { return; } const {searchText, participantsList, isChatRoom} = personalDetailOption; diff --git a/src/pages/RoomInvitePage.js b/src/pages/RoomInvitePage.js index aebdec047895..b440be16823d 100644 --- a/src/pages/RoomInvitePage.js +++ b/src/pages/RoomInvitePage.js @@ -70,7 +70,13 @@ function RoomInvitePage(props) { const [userToInvite, setUserToInvite] = useState(null); // Any existing participants and Expensify emails should not be eligible for invitation - const excludedUsers = useMemo(() => [...PersonalDetailsUtils.getLoginsByAccountIDs(lodashGet(props.report, 'participantAccountIDs', [])), ...CONST.EXPENSIFY_EMAILS], [props.report]); + const excludedUsers = useMemo( + () => + _.map([...PersonalDetailsUtils.getLoginsByAccountIDs(lodashGet(props.report, 'participantAccountIDs', [])), ...CONST.EXPENSIFY_EMAILS], (participant) => + OptionsListUtils.addSMSDomainIfPhoneNumber(participant), + ), + [props.report], + ); useEffect(() => { const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers); @@ -191,7 +197,7 @@ function RoomInvitePage(props) { if (!userToInvite && CONST.EXPENSIFY_EMAILS.includes(searchValue)) { return translate('messages.errorMessageInvalidEmail'); } - if (!userToInvite && excludedUsers.includes(searchValue)) { + if (!userToInvite && excludedUsers.includes(OptionsListUtils.addSMSDomainIfPhoneNumber(searchValue).toLowerCase())) { return translate('messages.userIsAlreadyMember', {login: searchValue, name: reportName}); } return OptionsListUtils.getHeaderMessage(personalDetails.length !== 0, Boolean(userToInvite), searchValue); diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 589c4971506b..b3c6e6da839c 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -237,7 +237,7 @@ function WorkspaceInvitePage(props) { if (usersToInvite.length === 0 && CONST.EXPENSIFY_EMAILS.includes(searchValue)) { return translate('messages.errorMessageInvalidEmail'); } - if (usersToInvite.length === 0 && excludedUsers.includes(searchValue)) { + if (usersToInvite.length === 0 && excludedUsers.includes(OptionsListUtils.addSMSDomainIfPhoneNumber(searchValue))) { return translate('messages.userIsAlreadyMember', {login: searchValue, name: policyName}); } return OptionsListUtils.getHeaderMessage(personalDetails.length !== 0, usersToInvite.length > 0, searchValue); From 1aaf1689389b7ea515a59ab405ff41545ea8ff5f Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 5 Jan 2024 17:12:22 +0700 Subject: [PATCH 24/73] add js docs --- src/libs/actions/IOU.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index a5e6ab492bdc..efd7ec594cc2 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -322,6 +322,7 @@ function getReceiptError(receipt, filename, isScanRequest = true) { * @param {Array} optimisticPolicyRecentlyUsedTags * @param {boolean} isNewChatReport * @param {boolean} isNewIOUReport + * @param {String} policyID * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) * @param {Array} policyTags * @param {Array} policyCategories From ed2ffb4a04097f4de857ffca6ffe9c4e863d2104 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Wed, 10 Jan 2024 15:40:14 -0300 Subject: [PATCH 25/73] Move 'isOptionsDataReady' to an 'useState' --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 250f68b2b504..025250a4b70a 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -96,6 +96,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ didScreenTransitionEnd, }) { const styles = useThemeStyles(); + const [isOptionsDataReady, setIsOptionsDataReady] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [newChatOptions, setNewChatOptions] = useState({ recentReports: [], @@ -225,7 +226,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ maxParticipantsReached, _.some(participants, (participant) => lodashGet(participant, 'searchText', '').toLowerCase().includes(searchTerm.trim().toLowerCase())), ); - const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); useEffect(() => { if (!didScreenTransitionEnd) { @@ -262,6 +262,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ personalDetails: chatOptions.personalDetails, userToInvite: chatOptions.userToInvite, }); + setIsOptionsDataReady(ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails)) }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, iouRequestType, didScreenTransitionEnd]); // When search term updates we will fetch any reports @@ -326,7 +327,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} - shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} + shouldShowOptions={isOptionsDataReady} shouldShowReferralCTA referralContentType={iouType === 'send' ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST} shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} From 37dc816e62294fadd10223f04b0124a61c0b87eb Mon Sep 17 00:00:00 2001 From: brunovjk Date: Sun, 14 Jan 2024 19:25:14 -0300 Subject: [PATCH 26/73] Pass 'didScreenTransitionEnd' from 'StepParticipants.StepScreenWrapper' to 'ParticipantsSelector' --- ...aryForRefactorRequestParticipantsSelector.js | 5 +++++ .../request/step/IOURequestStepParticipants.js | 17 ++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index d9ae8b9fab1c..9246fb202534 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -56,6 +56,9 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, + + /** Whether the parent screen transition has ended */ + didScreenTransitionEnd: PropTypes.bool, }; const defaultProps = { @@ -64,6 +67,7 @@ const defaultProps = { reports: {}, betas: [], isSearchingForReports: false, + didScreenTransitionEnd: false, }; function MoneyTemporaryForRefactorRequestParticipantsSelector({ @@ -76,6 +80,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType, iouRequestType, isSearchingForReports, + didScreenTransitionEnd, }) { const {translate} = useLocalize(); const styles = useThemeStyles(); diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 9f06360ef3b1..aad85307b3e4 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -87,13 +87,16 @@ function IOURequestStepParticipants({ testID={IOURequestStepParticipants.displayName} includeSafeAreaPaddingBottom > - + {({didScreenTransitionEnd}) => ( + + )} ); } From 8af2b8d78b27d08305879c39e097a776060f2d6e Mon Sep 17 00:00:00 2001 From: brunovjk Date: Sun, 14 Jan 2024 19:48:24 -0300 Subject: [PATCH 27/73] Handle OptionsListUtils logic after screen transition --- ...emporaryForRefactorRequestParticipantsSelector.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 9246fb202534..f6731f775b9b 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -99,6 +99,16 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ */ const [sections, newChatOptions] = useMemo(() => { const newSections = []; + if (!didScreenTransitionEnd) { + return [ + newSections, + { + recentReports: {}, + personalDetails: {}, + userToInvite: {}, + } + ]; + } let indexOffset = 0; const chatOptions = OptionsListUtils.getFilteredOptions( @@ -173,7 +183,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ } return [newSections, chatOptions]; - }, [reports, personalDetails, betas, searchTerm, participants, iouType, iouRequestType, maxParticipantsReached, translate]); + }, [didScreenTransitionEnd, reports, personalDetails, betas, searchTerm, participants, iouType, iouRequestType, maxParticipantsReached, translate]); /** * Adds a single participant to the request From a2611cc2e153b10d571bb4362821fc0b92467456 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Sun, 14 Jan 2024 21:01:27 -0300 Subject: [PATCH 28/73] Implement 'isLoadingNewOptions' to 'BaseSelectionList' --- src/components/SelectionList/BaseSelectionList.js | 2 ++ src/components/SelectionList/selectionListPropTypes.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 960618808fd9..221436e1020e 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -45,6 +45,7 @@ function BaseSelectionList({ inputMode = CONST.INPUT_MODE.TEXT, onChangeText, initiallyFocusedOptionKey = '', + isLoadingNewOptions = false, onScroll, onScrollBeginDrag, headerMessage = '', @@ -428,6 +429,7 @@ function BaseSelectionList({ spellCheck={false} onSubmitEditing={selectFocusedOption} blurOnSubmit={Boolean(flattenedSections.allOptions.length)} + isLoading={isLoadingNewOptions} /> )} diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index f5178112a4c3..b0c5dd37867e 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -151,6 +151,9 @@ const propTypes = { /** Item `keyForList` to focus initially */ initiallyFocusedOptionKey: PropTypes.string, + /** Whether we are loading new options */ + isLoadingNewOptions: PropTypes.bool, + /** Callback to fire when the list is scrolled */ onScroll: PropTypes.func, From 301df39b60fc9f49b608b6932727f1eebd743c28 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Sun, 14 Jan 2024 21:07:20 -0300 Subject: [PATCH 29/73] Adjust 'SelectionList' props --- ...oneyTemporaryForRefactorRequestParticipantsSelector.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index f6731f775b9b..9fb91e34fb33 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -16,6 +16,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as Report from '@libs/actions/Report'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import MoneyRequestReferralProgramCTA from '@pages/iou/MoneyRequestReferralProgramCTA'; import reportPropTypes from '@pages/reportPropTypes'; import CONST from '@src/CONST'; @@ -337,11 +338,13 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ [addParticipantToSelection, isAllowedToSplit, styles, translate], ); + const isOptionsDataReady = useMemo(() => ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails), [personalDetails]); + return ( 0 ? safeAreaPaddingBottomStyle : {}]}> ); From 6d15a87cc10a4f59e1b474272e563aa5968ba8c8 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Sun, 14 Jan 2024 21:09:36 -0300 Subject: [PATCH 30/73] Fill MoneyRequestReferralProgramCTA icon --- src/pages/iou/MoneyRequestReferralProgramCTA.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/iou/MoneyRequestReferralProgramCTA.tsx b/src/pages/iou/MoneyRequestReferralProgramCTA.tsx index 31394e1bd0e1..30db04dffdac 100644 --- a/src/pages/iou/MoneyRequestReferralProgramCTA.tsx +++ b/src/pages/iou/MoneyRequestReferralProgramCTA.tsx @@ -41,6 +41,7 @@ function MoneyRequestReferralProgramCTA({referralContentType}: MoneyRequestRefer src={Info} height={20} width={20} + fill={theme.icon} /> ); From 26630b221955e5d48e3daf0fea50f9911b82e2c8 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Mon, 15 Jan 2024 05:40:42 +0530 Subject: [PATCH 31/73] Update useEffect to not scroll if multiple options cannot be selected --- src/components/SelectionList/BaseSelectionList.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 960618808fd9..48ee89a80192 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -372,6 +372,11 @@ function BaseSelectionList({ return; } + // scroll is unnecessary if multiple options cannot be selected + if(!canSelectMultiple) { + return; + } + // set the focus on the first item when the sections list is changed if (sections.length > 0) { updateAndScrollToFocusedIndex(0); From b0742dab36182273f1a28603f23881e53bd0bf80 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Mon, 15 Jan 2024 05:51:56 +0530 Subject: [PATCH 32/73] Define shouldScrollToTopOnSelect prop --- src/components/SelectionList/selectionListPropTypes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index f5178112a4c3..1790cf3aad6f 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -198,6 +198,9 @@ const propTypes = { /** Right hand side component to display in the list item. Function has list item passed as the param */ rightHandSideComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + + /** Whether to scroll to top when an option is selected */ + shouldScrollToTopOnSelect: PropTypes.bool, }; export {propTypes, baseListItemPropTypes, radioListItemPropTypes, userListItemPropTypes}; From 102862bbcbfa26fb9e2af2c4162955bc54f0e071 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Mon, 15 Jan 2024 05:54:17 +0530 Subject: [PATCH 33/73] Add prop shouldScrollToTopOnSelect to BaseSelectionList --- src/components/SelectionList/BaseSelectionList.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 48ee89a80192..d073110a5e26 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -66,6 +66,7 @@ function BaseSelectionList({ shouldShowTooltips = true, shouldUseDynamicMaxToRenderPerBatch = false, rightHandSideComponent, + shouldScrollToTopOnSelect = true, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -378,7 +379,7 @@ function BaseSelectionList({ } // set the focus on the first item when the sections list is changed - if (sections.length > 0) { + if (sections.length > 0 && shouldScrollToTopOnSelect) { updateAndScrollToFocusedIndex(0); } // eslint-disable-next-line react-hooks/exhaustive-deps From 2543e1c57a2600e7b5a802e73c4951ad5df97bf9 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Mon, 15 Jan 2024 07:01:45 +0530 Subject: [PATCH 34/73] Prettier --- src/components/SelectionList/BaseSelectionList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index d073110a5e26..dd8cba3602dc 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -374,7 +374,7 @@ function BaseSelectionList({ } // scroll is unnecessary if multiple options cannot be selected - if(!canSelectMultiple) { + if (!canSelectMultiple) { return; } From 078b4ce44115245ac26aa2157219dd520714a877 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Mon, 15 Jan 2024 14:41:47 -0300 Subject: [PATCH 35/73] Revert "Fill MoneyRequestReferralProgramCTA icon" This reverts commit 6d15a87cc10a4f59e1b474272e563aa5968ba8c8. --- src/pages/iou/MoneyRequestReferralProgramCTA.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/MoneyRequestReferralProgramCTA.tsx b/src/pages/iou/MoneyRequestReferralProgramCTA.tsx index 30db04dffdac..31394e1bd0e1 100644 --- a/src/pages/iou/MoneyRequestReferralProgramCTA.tsx +++ b/src/pages/iou/MoneyRequestReferralProgramCTA.tsx @@ -41,7 +41,6 @@ function MoneyRequestReferralProgramCTA({referralContentType}: MoneyRequestRefer src={Info} height={20} width={20} - fill={theme.icon} /> ); From 87f5cf5db7ac4ccc8ce6cd70a35563512bb2fb28 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Mon, 15 Jan 2024 15:03:35 -0300 Subject: [PATCH 36/73] Use debounce text input value --- ...ryForRefactorRequestParticipantsSelector.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 9fb91e34fb33..02e0eb730c43 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useEffect,useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -10,6 +10,7 @@ import {usePersonalDetails} from '@components/OnyxProvider'; import {PressableWithFeedback} from '@components/Pressable'; import SelectCircle from '@components/SelectCircle'; 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'; @@ -85,7 +86,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ }) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [searchTerm, setSearchTerm] = useState(''); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); @@ -254,13 +255,12 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm], ); - // When search term updates we will fetch any reports - const setSearchTermAndSearchInServer = useCallback((text = '') => { - if (text.length) { - Report.searchInServer(text); + useEffect(() => { + if (!debouncedSearchTerm.length) { + return; } - setSearchTerm(text); - }, []); + Report.searchInServer(debouncedSearchTerm); + }, [debouncedSearchTerm]); // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent @@ -348,7 +348,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ textInputValue={searchTerm} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} textInputHint={offlineMessage} - onChangeText={setSearchTermAndSearchInServer} + onChangeText={setSearchTerm} shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} onSelectRow={addSingleParticipant} footerContent={footerContent} From 97f1c9aac058e9f74c10777bf61da77c1d7c3eb2 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Mon, 15 Jan 2024 16:03:55 -0300 Subject: [PATCH 37/73] applying all the same changes in 'pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js' --- .../MoneyRequestParticipantsPage.js | 3 +- .../MoneyRequestParticipantsSelector.js | 43 +++++++++++++------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 216154be9cd4..76b7b80c6306 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -130,7 +130,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) { shouldEnableMaxHeight={DeviceCapabilities.canUseTouchScreen()} testID={MoneyRequestParticipantsPage.displayName} > - {({safeAreaPaddingBottomStyle}) => ( + {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( )} diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 9edede770233..708398d7ea00 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useEffect,useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -10,16 +10,19 @@ import {usePersonalDetails} from '@components/OnyxProvider'; import {PressableWithFeedback} from '@components/Pressable'; import SelectCircle from '@components/SelectCircle'; 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 * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import MoneyRequestReferralProgramCTA from '@pages/iou/MoneyRequestReferralProgramCTA'; import reportPropTypes from '@pages/reportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; const propTypes = { /** Beta features list */ @@ -59,6 +62,9 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, + + /** Whether the parent screen transition has ended */ + didScreenTransitionEnd: PropTypes.bool, }; const defaultProps = { @@ -68,6 +74,7 @@ const defaultProps = { betas: [], isDistanceRequest: false, isSearchingForReports: false, + didScreenTransitionEnd: false, }; function MoneyRequestParticipantsSelector({ @@ -81,16 +88,24 @@ function MoneyRequestParticipantsSelector({ iouType, isDistanceRequest, isSearchingForReports, + didScreenTransitionEnd, }) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [searchTerm, setSearchTerm] = useState(''); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const newChatOptions = useMemo(() => { + if (!didScreenTransitionEnd) { + return { + recentReports: {}, + personalDetails: {}, + userToInvite: {}, + }; + } const chatOptions = OptionsListUtils.getFilteredOptions( reports, personalDetails, @@ -121,7 +136,7 @@ function MoneyRequestParticipantsSelector({ personalDetails: chatOptions.personalDetails, userToInvite: chatOptions.userToInvite, }; - }, [betas, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest]); + }, [betas, didScreenTransitionEnd, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest]); const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; @@ -166,7 +181,7 @@ function MoneyRequestParticipantsSelector({ }); indexOffset += newChatOptions.personalDetails.length; - if (newChatOptions.userToInvite && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { + if (isNotEmptyObject(newChatOptions.userToInvite) && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { newSections.push({ title: undefined, data: _.map([newChatOptions.userToInvite], (participant) => { @@ -258,11 +273,12 @@ function MoneyRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm], ); - // When search term updates we will fetch any reports - const setSearchTermAndSearchInServer = useCallback((text = '') => { - Report.searchInServer(text); - setSearchTerm(text); - }, []); + useEffect(() => { + if (!debouncedSearchTerm.length) { + return; + } + Report.searchInServer(debouncedSearchTerm); + }, [debouncedSearchTerm]); // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent @@ -341,21 +357,24 @@ function MoneyRequestParticipantsSelector({ [addParticipantToSelection, isAllowedToSplit, styles, translate], ); + const isOptionsDataReady = useMemo(() => ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails), [personalDetails]); + return ( 0 ? safeAreaPaddingBottomStyle : {}]}> ); From 8d08f71beb23eb08f6d65770fe0a83c6a28d2aa6 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Mon, 15 Jan 2024 18:20:32 -0300 Subject: [PATCH 38/73] Resolve conflict over 'ReferralProgramCTA' --- ...yForRefactorRequestParticipantsSelector.js | 43 +++++++++++++----- .../MoneyRequestParticipantsSelector.js | 45 +++++++++++++------ 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index ea9788ccddb5..72f9831c2c12 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useEffect,useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -11,12 +11,14 @@ import {PressableWithFeedback} from '@components/Pressable'; import ReferralProgramCTA from '@components/ReferralProgramCTA'; import SelectCircle from '@components/SelectCircle'; 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 * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import reportPropTypes from '@pages/reportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -56,6 +58,9 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, + + /** Whether the parent screen transition has ended */ + didScreenTransitionEnd: PropTypes.bool, }; const defaultProps = { @@ -64,6 +69,7 @@ const defaultProps = { reports: {}, betas: [], isSearchingForReports: false, + didScreenTransitionEnd: false, }; function MoneyTemporaryForRefactorRequestParticipantsSelector({ @@ -76,10 +82,11 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType, iouRequestType, isSearchingForReports, + didScreenTransitionEnd, }) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [searchTerm, setSearchTerm] = useState(''); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); @@ -94,6 +101,16 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ */ const [sections, newChatOptions] = useMemo(() => { const newSections = []; + if (!didScreenTransitionEnd) { + return [ + newSections, + { + recentReports: {}, + personalDetails: {}, + userToInvite: {}, + } + ]; + } let indexOffset = 0; const chatOptions = OptionsListUtils.getFilteredOptions( @@ -168,7 +185,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ } return [newSections, chatOptions]; - }, [reports, personalDetails, betas, searchTerm, participants, iouType, iouRequestType, maxParticipantsReached, translate]); + }, [didScreenTransitionEnd, reports, personalDetails, betas, searchTerm, participants, iouType, iouRequestType, maxParticipantsReached, translate]); /** * Adds a single participant to the request @@ -238,13 +255,12 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm], ); - // When search term updates we will fetch any reports - const setSearchTermAndSearchInServer = useCallback((text = '') => { - if (text.length) { - Report.searchInServer(text); + useEffect(() => { + if (!debouncedSearchTerm.length) { + return; } - setSearchTerm(text); - }, []); + Report.searchInServer(debouncedSearchTerm); + }, [debouncedSearchTerm]); // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent @@ -322,21 +338,24 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ [addParticipantToSelection, isAllowedToSplit, styles, translate], ); + const isOptionsDataReady = useMemo(() => ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails), [personalDetails]); + return ( 0 ? safeAreaPaddingBottomStyle : {}]}> ); diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 2162e81d7bb8..76e97b140506 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useEffect,useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -11,15 +11,18 @@ import {PressableWithFeedback} from '@components/Pressable'; import ReferralProgramCTA from '@components/ReferralProgramCTA'; import SelectCircle from '@components/SelectCircle'; 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 * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import reportPropTypes from '@pages/reportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; const propTypes = { /** Beta features list */ @@ -59,6 +62,9 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, + + /** Whether the parent screen transition has ended */ + didScreenTransitionEnd: PropTypes.bool, }; const defaultProps = { @@ -68,6 +74,7 @@ const defaultProps = { betas: [], isDistanceRequest: false, isSearchingForReports: false, + didScreenTransitionEnd: false, }; function MoneyRequestParticipantsSelector({ @@ -81,16 +88,24 @@ function MoneyRequestParticipantsSelector({ iouType, isDistanceRequest, isSearchingForReports, + didScreenTransitionEnd, }) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [searchTerm, setSearchTerm] = useState(''); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const newChatOptions = useMemo(() => { + if (!didScreenTransitionEnd) { + return { + recentReports: {}, + personalDetails: {}, + userToInvite: {}, + }; + } const chatOptions = OptionsListUtils.getFilteredOptions( reports, personalDetails, @@ -121,7 +136,7 @@ function MoneyRequestParticipantsSelector({ personalDetails: chatOptions.personalDetails, userToInvite: chatOptions.userToInvite, }; - }, [betas, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest]); + }, [betas, didScreenTransitionEnd, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest]); const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; @@ -166,7 +181,7 @@ function MoneyRequestParticipantsSelector({ }); indexOffset += newChatOptions.personalDetails.length; - if (newChatOptions.userToInvite && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { + if (isNotEmptyObject(newChatOptions.userToInvite) && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { newSections.push({ title: undefined, data: _.map([newChatOptions.userToInvite], (participant) => { @@ -258,11 +273,12 @@ function MoneyRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm], ); - // When search term updates we will fetch any reports - const setSearchTermAndSearchInServer = useCallback((text = '') => { - Report.searchInServer(text); - setSearchTerm(text); - }, []); + useEffect(() => { + if (!debouncedSearchTerm.length) { + return; + } + Report.searchInServer(debouncedSearchTerm); + }, [debouncedSearchTerm]); // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent @@ -341,21 +357,24 @@ function MoneyRequestParticipantsSelector({ [addParticipantToSelection, isAllowedToSplit, styles, translate], ); + const isOptionsDataReady = useMemo(() => ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails), [personalDetails]); + return ( 0 ? safeAreaPaddingBottomStyle : {}]}> ); @@ -376,4 +395,4 @@ export default withOnyx({ key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, initWithStoredValues: false, }, -})(MoneyRequestParticipantsSelector); \ No newline at end of file +})(MoneyRequestParticipantsSelector); From a1f468ca1176b27202d461a9c83f7ae97c92948a Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 17 Jan 2024 12:26:33 +0700 Subject: [PATCH 39/73] fix hasOutstandingChildRequest --- src/libs/actions/IOU.js | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 06adaab3c28f..93fd4d5827ad 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -311,6 +311,27 @@ function getReceiptError(receipt, filename, isScanRequest = true) { : ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source, filename}); } +/** + * @param {Object} [policy] + * @param {Boolean} needsToBeManuallySubmitted + * @returns {Object} + */ +function getOutstandingChildRequest(policy, needsToBeManuallySubmitted) { + if (!needsToBeManuallySubmitted) { + return { + hasOutstandingChildRequest: false, + }; + } + + if (PolicyUtils.isPolicyAdmin(policy)) { + return { + hasOutstandingChildRequest: true, + }; + } + + return {}; +} + /** * Builds the Onyx data for a money request. * @@ -329,7 +350,7 @@ function getReceiptError(receipt, filename, isScanRequest = true) { * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts) * @param {Array} policyTags * @param {Array} policyCategories - * @param {Boolean} hasOutstandingChildRequest + * @param {Boolean} needsToBeManuallySubmitted * @returns {Array} - An array containing the optimistic data, success data, and failure data. */ function buildOnyxDataForMoneyRequest( @@ -348,11 +369,10 @@ function buildOnyxDataForMoneyRequest( policy, policyTags, policyCategories, - hasOutstandingChildRequest = false, + needsToBeManuallySubmitted = true, ) { - const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); - const isScanRequest = TransactionUtils.isScanRequest(transaction); + const hasOutstandingChildRequest = getOutstandingChildRequest(needsToBeManuallySubmitted, policy); const optimisticData = [ { // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page @@ -363,7 +383,6 @@ function buildOnyxDataForMoneyRequest( lastReadTime: DateUtils.getDBTime(), lastMessageTranslationKey: '', iouReportID: iouReport.reportID, - ...(isPolicyAdmin ? {hasOutstandingChildRequest: true} : {}), hasOutstandingChildRequest, ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), }, @@ -509,7 +528,7 @@ function buildOnyxDataForMoneyRequest( iouReportID: chatReport.iouReportID, lastReadTime: chatReport.lastReadTime, pendingFields: null, - ...(isPolicyAdmin ? {hasOutstandingChildRequest: chatReport.hasOutstandingChildRequest} : {}), + hasOutstandingChildRequest: chatReport.hasOutstandingChildRequest, ...(isNewChatReport ? { errorFields: { @@ -691,7 +710,7 @@ function getMoneyRequestInformation( let iouReport = isNewIOUReport ? null : allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]; // Check if the Scheduled Submit is enabled in case of expense report - let needsToBeManuallySubmitted = false; + let needsToBeManuallySubmitted = true; let isFromPaidPolicy = false; if (isPolicyExpenseChat) { isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy); @@ -810,10 +829,6 @@ function getMoneyRequestInformation( } : undefined; - // The policy expense chat should have the GBR only when its a paid policy and the scheduled submit is turned off - // so the employee has to submit to their manager manually. - const hasOutstandingChildRequest = isPolicyExpenseChat && needsToBeManuallySubmitted; - // STEP 5: Build Onyx Data const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest( chatReport, @@ -831,7 +846,7 @@ function getMoneyRequestInformation( policy, policyTags, policyCategories, - hasOutstandingChildRequest, + needsToBeManuallySubmitted, ); return { From 8859d213427b1dae8f28d09ad15dd89cd29db80d Mon Sep 17 00:00:00 2001 From: brunovjk Date: Wed, 17 Jan 2024 10:10:18 -0300 Subject: [PATCH 40/73] Changes requested by reviewer --- src/components/SelectionList/BaseSelectionList.js | 2 +- ...neyTemporaryForRefactorRequestParticipantsSelector.js | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 221436e1020e..2d209ef573c3 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -433,7 +433,7 @@ function BaseSelectionList({ /> )} - {Boolean(headerMessage) && ( + {!isLoadingNewOptions && Boolean(headerMessage) && ( {headerMessage} diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 01ef3d9bb697..2554b5933c6a 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -102,14 +102,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const [sections, newChatOptions] = useMemo(() => { const newSections = []; if (!didScreenTransitionEnd) { - return [ - newSections, - { - recentReports: {}, - personalDetails: {}, - userToInvite: {}, - } - ]; + return [newSections, {}]; } let indexOffset = 0; From 05fa761f58159140793e4ccc70e0cc2ef5848ae8 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Thu, 18 Jan 2024 06:10:15 -0800 Subject: [PATCH 41/73] Update Budgets.md --- .../expensify-classic/workspace-and-domain-settings/Budgets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md index 30adac589dc0..2b95bfab31d6 100644 --- a/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md +++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md @@ -44,7 +44,7 @@ Expensify’s Budgets feature allows you to: {% include faq-begin.md %} ## Can I import budgets as a CSV? -At this time, you cannot import budgets via CSV since we don’t import categories or tags from direct accounting integrations. +At this time, you can't import budgets via CSV. ## When will I be notified as a budget is hit? Notifications are sent twice: From 008807d7c5baf480ccecbdbe899a0f8691e816fd Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Thu, 18 Jan 2024 06:30:04 -0800 Subject: [PATCH 42/73] Update Budgets.md --- .../expensify-classic/workspace-and-domain-settings/Budgets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md index 2b95bfab31d6..b3f0ad3c6f6f 100644 --- a/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md +++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md @@ -44,7 +44,7 @@ Expensify’s Budgets feature allows you to: {% include faq-begin.md %} ## Can I import budgets as a CSV? -At this time, you can't import budgets via CSV. +At this time, you cannot import budgets via CSV. ## When will I be notified as a budget is hit? Notifications are sent twice: From 1a94efad3cdfecf94266dea234835cabc6e4b7ae Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 18 Jan 2024 12:07:53 -0300 Subject: [PATCH 43/73] Resolve conflict over Migrate 'SelectionList' --- src/components/SelectionList/BaseSelectionList.js | 4 +++- src/components/SelectionList/selectionListPropTypes.js | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index ea9ee3a0012c..0ad7d3621660 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -45,6 +45,7 @@ function BaseSelectionList({ inputMode = CONST.INPUT_MODE.TEXT, onChangeText, initiallyFocusedOptionKey = '', + isLoadingNewOptions = false, onScroll, onScrollBeginDrag, headerMessage = '', @@ -428,10 +429,11 @@ function BaseSelectionList({ spellCheck={false} onSubmitEditing={selectFocusedOption} blurOnSubmit={Boolean(flattenedSections.allOptions.length)} + isLoading={isLoadingNewOptions} /> )} - {Boolean(headerMessage) && ( + {!isLoadingNewOptions && !!headerMessage && ( {headerMessage} diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index 7914ecc8572c..254f3e22b684 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -151,6 +151,9 @@ const propTypes = { /** Item `keyForList` to focus initially */ initiallyFocusedOptionKey: PropTypes.string, + /** Whether we are loading new options */ + isLoadingNewOptions: PropTypes.bool, + /** Callback to fire when the list is scrolled */ onScroll: PropTypes.func, From 0d6b7a5aca021eea4be0e8aa1385db6f67d1ec74 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 18 Jan 2024 12:12:48 -0300 Subject: [PATCH 44/73] Resolve conflict over Migrate 'SelectionList' --- src/components/SelectionList/BaseSelectionList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 0ad7d3621660..36d56813e917 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -433,7 +433,7 @@ function BaseSelectionList({ /> )} - {!isLoadingNewOptions && !!headerMessage && ( + {!isLoadingNewOptions && Boolean(headerMessage) && ( {headerMessage} From 8c0eee24ebe61eaffc271b680d944b2d35b401ac Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 18 Jan 2024 13:28:39 -0300 Subject: [PATCH 45/73] Run prettier --- src/components/SelectionList/BaseListItem.js | 2 +- .../SelectionList/BaseSelectionList.js | 2 +- src/components/SelectionList/RadioListItem.js | 2 +- src/components/SelectionList/UserListItem.js | 2 +- src/components/SelectionList/index.android.js | 2 +- src/components/SelectionList/index.ios.js | 2 +- src/components/SelectionList/index.js | 2 +- .../SelectionList/selectionListPropTypes.js | 2 +- ...yForRefactorRequestParticipantsSelector.js | 4 +- .../step/IOURequestStepParticipants.js | 2 +- .../MoneyRequestParticipantsPage.js | 3 +- .../MoneyRequestParticipantsSelector.js | 43 ++++++------------- 12 files changed, 24 insertions(+), 44 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.js b/src/components/SelectionList/BaseListItem.js index ad19e7dcad76..6a067ea0fe3d 100644 --- a/src/components/SelectionList/BaseListItem.js +++ b/src/components/SelectionList/BaseListItem.js @@ -142,4 +142,4 @@ function BaseListItem({ BaseListItem.displayName = 'BaseListItem'; BaseListItem.propTypes = baseListItemPropTypes; -export default BaseListItem; \ No newline at end of file +export default BaseListItem; diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 36d56813e917..2d209ef573c3 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -513,4 +513,4 @@ function BaseSelectionList({ BaseSelectionList.displayName = 'BaseSelectionList'; BaseSelectionList.propTypes = propTypes; -export default withKeyboardState(BaseSelectionList); \ No newline at end of file +export default withKeyboardState(BaseSelectionList); diff --git a/src/components/SelectionList/RadioListItem.js b/src/components/SelectionList/RadioListItem.js index 5b465f9efe58..2de0c96932ea 100644 --- a/src/components/SelectionList/RadioListItem.js +++ b/src/components/SelectionList/RadioListItem.js @@ -41,4 +41,4 @@ function RadioListItem({item, showTooltip, textStyles, alternateTextStyles}) { RadioListItem.displayName = 'RadioListItem'; RadioListItem.propTypes = radioListItemPropTypes; -export default RadioListItem; \ No newline at end of file +export default RadioListItem; diff --git a/src/components/SelectionList/UserListItem.js b/src/components/SelectionList/UserListItem.js index 1513f2601b9f..a842f19858f2 100644 --- a/src/components/SelectionList/UserListItem.js +++ b/src/components/SelectionList/UserListItem.js @@ -54,4 +54,4 @@ function UserListItem({item, textStyles, alternateTextStyles, showTooltip, style UserListItem.displayName = 'UserListItem'; UserListItem.propTypes = userListItemPropTypes; -export default UserListItem; \ No newline at end of file +export default UserListItem; diff --git a/src/components/SelectionList/index.android.js b/src/components/SelectionList/index.android.js index 5c98df733c02..53d5b6bbce06 100644 --- a/src/components/SelectionList/index.android.js +++ b/src/components/SelectionList/index.android.js @@ -14,4 +14,4 @@ const SelectionList = forwardRef((props, ref) => ( SelectionList.displayName = 'SelectionList'; -export default SelectionList; \ No newline at end of file +export default SelectionList; diff --git a/src/components/SelectionList/index.ios.js b/src/components/SelectionList/index.ios.js index b03aae215f9d..7f2a282aeb89 100644 --- a/src/components/SelectionList/index.ios.js +++ b/src/components/SelectionList/index.ios.js @@ -13,4 +13,4 @@ const SelectionList = forwardRef((props, ref) => ( SelectionList.displayName = 'SelectionList'; -export default SelectionList; \ No newline at end of file +export default SelectionList; diff --git a/src/components/SelectionList/index.js b/src/components/SelectionList/index.js index f0fe27acbe2d..24ea60d29be5 100644 --- a/src/components/SelectionList/index.js +++ b/src/components/SelectionList/index.js @@ -43,4 +43,4 @@ const SelectionList = forwardRef((props, ref) => { SelectionList.displayName = 'SelectionList'; -export default SelectionList; \ No newline at end of file +export default SelectionList; diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index 254f3e22b684..b0c5dd37867e 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -203,4 +203,4 @@ const propTypes = { rightHandSideComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), }; -export {propTypes, baseListItemPropTypes, radioListItemPropTypes, userListItemPropTypes}; \ No newline at end of file +export {propTypes, baseListItemPropTypes, radioListItemPropTypes, userListItemPropTypes}; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 2554b5933c6a..6da8524934d3 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect,useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -369,4 +369,4 @@ export default withOnyx({ key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, initWithStoredValues: false, }, -})(MoneyTemporaryForRefactorRequestParticipantsSelector); \ No newline at end of file +})(MoneyTemporaryForRefactorRequestParticipantsSelector); diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index aad85307b3e4..4846c3c4c8a4 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -105,4 +105,4 @@ IOURequestStepParticipants.displayName = 'IOURequestStepParticipants'; IOURequestStepParticipants.propTypes = propTypes; IOURequestStepParticipants.defaultProps = defaultProps; -export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepParticipants); \ No newline at end of file +export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepParticipants); diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 76b7b80c6306..216154be9cd4 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -130,7 +130,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) { shouldEnableMaxHeight={DeviceCapabilities.canUseTouchScreen()} testID={MoneyRequestParticipantsPage.displayName} > - {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( + {({safeAreaPaddingBottomStyle}) => ( )} diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 76e97b140506..9567b17ecdf5 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect,useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -11,18 +11,15 @@ import {PressableWithFeedback} from '@components/Pressable'; import ReferralProgramCTA from '@components/ReferralProgramCTA'; import SelectCircle from '@components/SelectCircle'; 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 * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import reportPropTypes from '@pages/reportPropTypes'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; const propTypes = { /** Beta features list */ @@ -62,9 +59,6 @@ const propTypes = { /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, - - /** Whether the parent screen transition has ended */ - didScreenTransitionEnd: PropTypes.bool, }; const defaultProps = { @@ -74,7 +68,6 @@ const defaultProps = { betas: [], isDistanceRequest: false, isSearchingForReports: false, - didScreenTransitionEnd: false, }; function MoneyRequestParticipantsSelector({ @@ -88,24 +81,16 @@ function MoneyRequestParticipantsSelector({ iouType, isDistanceRequest, isSearchingForReports, - didScreenTransitionEnd, }) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); + const [searchTerm, setSearchTerm] = useState(''); const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const newChatOptions = useMemo(() => { - if (!didScreenTransitionEnd) { - return { - recentReports: {}, - personalDetails: {}, - userToInvite: {}, - }; - } const chatOptions = OptionsListUtils.getFilteredOptions( reports, personalDetails, @@ -136,7 +121,7 @@ function MoneyRequestParticipantsSelector({ personalDetails: chatOptions.personalDetails, userToInvite: chatOptions.userToInvite, }; - }, [betas, didScreenTransitionEnd, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest]); + }, [betas, reports, participants, personalDetails, searchTerm, iouType, isDistanceRequest]); const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; @@ -181,7 +166,7 @@ function MoneyRequestParticipantsSelector({ }); indexOffset += newChatOptions.personalDetails.length; - if (isNotEmptyObject(newChatOptions.userToInvite) && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { + if (newChatOptions.userToInvite && !OptionsListUtils.isCurrentUser(newChatOptions.userToInvite)) { newSections.push({ title: undefined, data: _.map([newChatOptions.userToInvite], (participant) => { @@ -273,12 +258,11 @@ function MoneyRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm], ); - useEffect(() => { - if (!debouncedSearchTerm.length) { - return; - } - Report.searchInServer(debouncedSearchTerm); - }, [debouncedSearchTerm]); + // When search term updates we will fetch any reports + const setSearchTermAndSearchInServer = useCallback((text = '') => { + Report.searchInServer(text); + setSearchTerm(text); + }, []); // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent @@ -357,24 +341,21 @@ function MoneyRequestParticipantsSelector({ [addParticipantToSelection, isAllowedToSplit, styles, translate], ); - const isOptionsDataReady = useMemo(() => ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails), [personalDetails]); - return ( 0 ? safeAreaPaddingBottomStyle : {}]}> ); From fbff2d3ed5774a6a2ddccd303051bab31c638e72 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:31:51 +0100 Subject: [PATCH 46/73] use a better approach for useResponsiveLayout hook --- src/hooks/useResponsiveLayout.ts | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/hooks/useResponsiveLayout.ts b/src/hooks/useResponsiveLayout.ts index dd782a9dbba5..10f1bccf15bd 100644 --- a/src/hooks/useResponsiveLayout.ts +++ b/src/hooks/useResponsiveLayout.ts @@ -1,25 +1,19 @@ -import type {ParamListBase, RouteProp} from '@react-navigation/native'; -import {useRoute} from '@react-navigation/native'; +import {navigationRef} from '@libs/Navigation/Navigation'; +import NAVIGATORS from '@src/NAVIGATORS'; import useWindowDimensions from './useWindowDimensions'; -type RouteParams = ParamListBase & { - params: {isInRHP?: boolean}; -}; type ResponsiveLayoutResult = { shouldUseNarrowLayout: boolean; }; /** - * Hook to determine if we are on mobile devices or in the RHP + * Hook to determine if we are on mobile devices or in the Modal Navigator */ export default function useResponsiveLayout(): ResponsiveLayoutResult { const {isSmallScreenWidth} = useWindowDimensions(); - try { - // eslint-disable-next-line react-hooks/rules-of-hooks - const {params} = useRoute>(); - return {shouldUseNarrowLayout: isSmallScreenWidth || (params?.isInRHP ?? false)}; - } catch (error) { - return { - shouldUseNarrowLayout: isSmallScreenWidth, - }; - } + const state = navigationRef.getRootState(); + const lastRoute = state.routes.at(-1); + const lastRouteName = lastRoute?.name; + const isInModal = lastRouteName === NAVIGATORS.LEFT_MODAL_NAVIGATOR || lastRouteName === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; + const shouldUseNarrowLayout = isSmallScreenWidth || isInModal; + return {shouldUseNarrowLayout}; } From 10dd583f74e84527bb59d7e844f8568fd9617a11 Mon Sep 17 00:00:00 2001 From: someone-here Date: Fri, 19 Jan 2024 00:24:00 +0530 Subject: [PATCH 47/73] Fix Context menu from keyboard --- src/libs/calculateAnchorPosition.ts | 6 +-- .../PopoverReportActionContextMenu.tsx | 41 +++++++++++-------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/libs/calculateAnchorPosition.ts b/src/libs/calculateAnchorPosition.ts index 66966b7b504c..2ff0c178e6e0 100644 --- a/src/libs/calculateAnchorPosition.ts +++ b/src/libs/calculateAnchorPosition.ts @@ -1,5 +1,5 @@ -/* eslint-disable no-console */ -import type {View} from 'react-native'; +/* eslint-disable no-restricted-imports */ +import type {Text as RNText, View} from 'react-native'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {AnchorPosition} from '@src/styles'; @@ -13,7 +13,7 @@ type AnchorOrigin = { /** * Gets the x,y position of the passed in component for the purpose of anchoring another component to it. */ -export default function calculateAnchorPosition(anchorComponent: View, anchorOrigin?: AnchorOrigin): Promise { +export default function calculateAnchorPosition(anchorComponent: View | RNText, anchorOrigin?: AnchorOrigin): Promise { return new Promise((resolve) => { if (!anchorComponent) { return resolve({horizontal: 0, vertical: 0}); diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 46b783bca3f9..476efa591177 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -1,11 +1,14 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; -import type {EmitterSubscription, GestureResponderEvent, NativeTouchEvent, View} from 'react-native'; + +/* eslint-disable no-restricted-imports */ +import type {EmitterSubscription, GestureResponderEvent, NativeTouchEvent, Text as RNText, View} from 'react-native'; import {Dimensions} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import useLocalize from '@hooks/useLocalize'; +import calculateAnchorPosition from '@libs/calculateAnchorPosition'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; @@ -14,10 +17,6 @@ import type {ReportAction} from '@src/types/onyx'; import BaseReportActionContextMenu from './BaseReportActionContextMenu'; import type {ContextMenuType, ReportActionContextMenu} from './ReportActionContextMenu'; -type ContextMenuAnchorCallback = (x: number, y: number) => void; - -type ContextMenuAnchor = {measureInWindow: (callback: ContextMenuAnchorCallback) => void}; - type Location = { x: number; y: number; @@ -66,7 +65,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef(null); const anchorRef = useRef(null); const dimensionsEventListener = useRef(null); - const contextMenuAnchorRef = useRef(null); + const contextMenuAnchorRef = useRef(null); const contextMenuTargetNode = useRef(null); const onPopoverShow = useRef(() => {}); @@ -171,16 +170,26 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef { - popoverAnchorPosition.current = { - horizontal: pageX - x, - vertical: pageY - y, - }; - - popoverAnchorPosition.current = { - horizontal: pageX, - vertical: pageY, - }; + new Promise((resolve) => { + if (!pageX && !pageY && contextMenuAnchorRef.current) { + calculateAnchorPosition(contextMenuAnchorRef.current).then((position) => { + popoverAnchorPosition.current = position; + resolve(); + }); + } else { + getContextMenuMeasuredLocation().then(({x, y}) => { + cursorRelativePosition.current = { + horizontal: pageX - x, + vertical: pageY - y, + }; + popoverAnchorPosition.current = { + horizontal: pageX, + vertical: pageY, + }; + resolve(); + }); + } + }).then(() => { typeRef.current = type; reportIDRef.current = reportID ?? '0'; reportActionIDRef.current = reportActionID ?? '0'; From 55408387417e016eb45bb908df9ade8b373c3a68 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Fri, 19 Jan 2024 09:51:36 -0300 Subject: [PATCH 48/73] Isolate reuse 'didScreenTransitionEnd' to address the screen transition skeleton issue --- .../SelectionList/BaseSelectionList.js | 4 +--- .../SelectionList/selectionListPropTypes.js | 3 --- ...ryForRefactorRequestParticipantsSelector.js | 18 +++++++++--------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 2d209ef573c3..960618808fd9 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -45,7 +45,6 @@ function BaseSelectionList({ inputMode = CONST.INPUT_MODE.TEXT, onChangeText, initiallyFocusedOptionKey = '', - isLoadingNewOptions = false, onScroll, onScrollBeginDrag, headerMessage = '', @@ -429,11 +428,10 @@ function BaseSelectionList({ spellCheck={false} onSubmitEditing={selectFocusedOption} blurOnSubmit={Boolean(flattenedSections.allOptions.length)} - isLoading={isLoadingNewOptions} /> )} - {!isLoadingNewOptions && Boolean(headerMessage) && ( + {Boolean(headerMessage) && ( {headerMessage} diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index b0c5dd37867e..f5178112a4c3 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -151,9 +151,6 @@ const propTypes = { /** Item `keyForList` to focus initially */ initiallyFocusedOptionKey: PropTypes.string, - /** Whether we are loading new options */ - isLoadingNewOptions: PropTypes.bool, - /** Callback to fire when the list is scrolled */ onScroll: PropTypes.func, diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 6da8524934d3..65b51da1d72d 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -11,7 +11,6 @@ import {PressableWithFeedback} from '@components/Pressable'; import ReferralProgramCTA from '@components/ReferralProgramCTA'; import SelectCircle from '@components/SelectCircle'; 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'; @@ -86,7 +85,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ }) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); + const [searchTerm, setSearchTerm] = useState(''); const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); @@ -248,12 +247,13 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ [maxParticipantsReached, newChatOptions, participants, searchTerm], ); - useEffect(() => { - if (!debouncedSearchTerm.length) { - return; + // When search term updates we will fetch any reports + const setSearchTermAndSearchInServer = useCallback((text = '') => { + if (text.length) { + Report.searchInServer(text); } - Report.searchInServer(debouncedSearchTerm); - }, [debouncedSearchTerm]); + setSearchTerm(text); + }, []); // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent @@ -341,7 +341,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ textInputValue={searchTerm} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} textInputHint={offlineMessage} - onChangeText={setSearchTerm} + onChangeText={setSearchTermAndSearchInServer} shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} onSelectRow={addSingleParticipant} footerContent={footerContent} From c235a069b0630394813b8bb18cf4b1fc94d61e53 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Fri, 19 Jan 2024 13:53:25 -0300 Subject: [PATCH 49/73] remove redundance --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 65b51da1d72d..699284645162 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -348,7 +348,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ headerMessage={headerMessage} showLoadingPlaceholder={!(didScreenTransitionEnd && isOptionsDataReady)} rightHandSideComponent={itemRightSideComponent} - isLoadingNewOptions={isSearchingForReports} /> ); From 5dfacd83687526f6000f5b0f095d48e83595791f Mon Sep 17 00:00:00 2001 From: someone-here Date: Sat, 20 Jan 2024 21:08:22 +0530 Subject: [PATCH 50/73] Show only left-out options in overflow menu --- .../BaseReportActionContextMenu.tsx | 58 +++++++++++++++---- .../report/ContextMenu/ContextMenuActions.tsx | 24 ++------ .../PopoverReportActionContextMenu.tsx | 5 ++ .../ContextMenu/ReportActionContextMenu.ts | 4 ++ 4 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index 3eecb74a048a..213d94f51f81 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -2,6 +2,8 @@ import lodashIsEqual from 'lodash/isEqual'; import type {MutableRefObject, RefObject} from 'react'; import React, {memo, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {GestureResponderEvent, Text as RNText, View as ViewType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ContextMenuItemHandle} from '@components/ContextMenuItem'; @@ -12,15 +14,16 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as ReportUtils from '@libs/ReportUtils'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, ReportAction, ReportActions} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type {ContextMenuActionPayload} from './ContextMenuActions'; +import type {ContextMenuAction, ContextMenuActionPayload} from './ContextMenuActions'; import ContextMenuActions from './ContextMenuActions'; import type {ContextMenuType} from './ReportActionContextMenu'; -import {hideContextMenu} from './ReportActionContextMenu'; +import {hideContextMenu, showContextMenu} from './ReportActionContextMenu'; type BaseReportActionContextMenuOnyxProps = { /** Beta features list */ @@ -78,7 +81,11 @@ type BaseReportActionContextMenuProps = BaseReportActionContextMenuOnyxProps & { /** Content Ref */ contentRef?: RefObject; + /** Function to check if context menu is active */ checkIfContextMenuActive?: () => void; + + /** List of disabled actions */ + disabledActions?: ContextMenuAction[]; }; type MenuItemRefs = Record; @@ -100,6 +107,7 @@ function BaseReportActionContextMenu({ betas, reportActions, checkIfContextMenuActive, + disabledActions = [], }: BaseReportActionContextMenuProps) { const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); @@ -117,13 +125,22 @@ function BaseReportActionContextMenu({ }, [reportActions, reportActionID]); const shouldEnableArrowNavigation = !isMini && (isVisible || shouldKeepOpen); - let filteredContextMenuActions = ContextMenuActions.filter((contextAction) => - contextAction.shouldShow(type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat, isUnreadChat, !!isOffline, isMini), + let filteredContextMenuActions = ContextMenuActions.filter( + (contextAction) => + !disabledActions.includes(contextAction) && + contextAction.shouldShow(type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat, isUnreadChat, !!isOffline, isMini), ); - filteredContextMenuActions = - isMini && filteredContextMenuActions.length > CONST.MINI_CONTEXT_MENU_MAX_ITEMS - ? ([...filteredContextMenuActions.slice(0, CONST.MINI_CONTEXT_MENU_MAX_ITEMS - 1), filteredContextMenuActions.at(-1)] as typeof filteredContextMenuActions) - : filteredContextMenuActions; + + if (isMini) { + const menuAction = filteredContextMenuActions.at(-1); + const otherActions = filteredContextMenuActions.slice(0, -1); + if (otherActions.length > CONST.MINI_CONTEXT_MENU_MAX_ITEMS && menuAction) { + filteredContextMenuActions = otherActions.slice(0, CONST.MINI_CONTEXT_MENU_MAX_ITEMS - 1); + filteredContextMenuActions.push(menuAction); + } else { + filteredContextMenuActions = otherActions; + } + } // Context menu actions that are not rendered as menu items are excluded from arrow navigation const nonMenuItemActionIndexes = filteredContextMenuActions.map((contextAction, index) => @@ -172,6 +189,28 @@ function BaseReportActionContextMenu({ {isActive: shouldEnableArrowNavigation}, ); + const openOverflowMenu = (event: GestureResponderEvent | MouseEvent) => { + const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); + const originalReport = ReportUtils.getReport(originalReportID); + showContextMenu( + CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, + event, + selection, + anchor?.current as ViewType | RNText | null, + reportID, + reportAction?.reportActionID, + originalReportID, + draftMessage, + checkIfContextMenuActive, + checkIfContextMenuActive, + ReportUtils.isArchivedRoom(originalReport), + ReportUtils.chatIncludesChronos(originalReport), + undefined, + undefined, + filteredContextMenuActions, + ); + }; + return ( (isVisible || shouldKeepOpen) && ( setShouldKeepOpen(false), openContextMenu: () => setShouldKeepOpen(true), interceptAnonymousUser, - anchor, - checkIfContextMenuActive, + openOverflowMenu, }; if ('renderContent' in contextAction) { diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index ea25a00ee1d3..1e8f9dde64d6 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -70,8 +70,7 @@ type ContextMenuActionPayload = { close: () => void; openContextMenu: () => void; interceptAnonymousUser: (callback: () => void, isAnonymousAction?: boolean) => void; - anchor?: MutableRefObject; - checkIfContextMenuActive?: () => void; + openOverflowMenu: (event: GestureResponderEvent | MouseEvent) => void; event?: GestureResponderEvent | MouseEvent | KeyboardEvent; }; @@ -502,27 +501,12 @@ const ContextMenuActions: ContextMenuAction[] = [ textTranslateKey: 'reportActionContextMenu.menu', icon: Expensicons.ThreeDots, shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat, isUnreadChat, isOffline, isMini) => isMini, - onPress: (closePopover, {reportAction, reportID, event, anchor, selection, draftMessage, checkIfContextMenuActive}) => { - const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); - const originalReport = ReportUtils.getReport(originalReportID); - showContextMenu( - CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, - event as GestureResponderEvent | MouseEvent, - selection, - anchor?.current as View | RNText | null, - reportID, - reportAction.reportActionID, - originalReportID, - draftMessage, - checkIfContextMenuActive, - checkIfContextMenuActive, - ReportUtils.isArchivedRoom(originalReport), - ReportUtils.chatIncludesChronos(originalReport), - ); + onPress: (closePopover, {openOverflowMenu, event}) => { + openOverflowMenu(event as GestureResponderEvent | MouseEvent); }, getDescription: () => {}, }, ]; export default ContextMenuActions; -export type {ContextMenuActionPayload}; +export type {ContextMenuActionPayload, ContextMenuAction}; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 476efa591177..42f27ecdfd59 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -15,6 +15,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; import BaseReportActionContextMenu from './BaseReportActionContextMenu'; +import type {ContextMenuAction} from './ContextMenuActions'; import type {ContextMenuType, ReportActionContextMenu} from './ReportActionContextMenu'; type Location = { @@ -61,6 +62,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef([]); const contentRef = useRef(null); const anchorRef = useRef(null); @@ -160,6 +162,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef { const {pageX = 0, pageY = 0} = extractPointerEvent(event); contextMenuAnchorRef.current = contextMenuAnchor; @@ -190,6 +193,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef { + setDisabledActions(disabledActions); typeRef.current = type; reportIDRef.current = reportID ?? '0'; reportActionIDRef.current = reportActionID ?? '0'; @@ -319,6 +323,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef void; @@ -30,6 +31,7 @@ type ShowContextMenu = ( isChronosReport?: boolean, isPinnedChat?: boolean, isUnreadChat?: boolean, + disabledOptions?: ContextMenuAction[], ) => void; type ReportActionContextMenu = { @@ -108,6 +110,7 @@ function showContextMenu( isChronosReport = false, isPinnedChat = false, isUnreadChat = false, + disabledActions: ContextMenuAction[] = [], ) { if (!contextMenuRef.current) { return; @@ -134,6 +137,7 @@ function showContextMenu( isChronosReport, isPinnedChat, isUnreadChat, + disabledActions, ); } From cbc19a1ab83129ce5dc351f8d5dfb3dddcd9050e Mon Sep 17 00:00:00 2001 From: someone-here Date: Sat, 20 Jan 2024 22:01:56 +0530 Subject: [PATCH 51/73] Fix lint --- src/pages/home/report/ContextMenu/ContextMenuActions.tsx | 5 ++--- .../report/ContextMenu/PopoverReportActionContextMenu.tsx | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 1e8f9dde64d6..dde99f746d9f 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -1,8 +1,7 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import type {MutableRefObject} from 'react'; import React from 'react'; -// eslint-disable-next-line no-restricted-imports -import type {GestureResponderEvent, Text as RNText, View} from 'react-native'; +import type {GestureResponderEvent} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -29,7 +28,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; import type {Beta, ReportAction, ReportActionReactions} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; -import {hideContextMenu, showContextMenu, showDeleteModal} from './ReportActionContextMenu'; +import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; /** Gets the HTML version of the message in an action */ function getActionText(reportAction: OnyxEntry): string { diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 42f27ecdfd59..b28374fae04a 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -162,7 +162,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef { const {pageX = 0, pageY = 0} = extractPointerEvent(event); contextMenuAnchorRef.current = contextMenuAnchor; @@ -193,7 +193,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef { - setDisabledActions(disabledActions); + setDisabledActions(disabledOptions); typeRef.current = type; reportIDRef.current = reportID ?? '0'; reportActionIDRef.current = reportActionID ?? '0'; From 51a4c6479a1ba2c6d20eaf9e652e4d2159b092ba Mon Sep 17 00:00:00 2001 From: someone-here Date: Mon, 22 Jan 2024 23:15:21 +0530 Subject: [PATCH 52/73] Change icon for unread --- src/pages/home/report/ContextMenu/ContextMenuActions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index dde99f746d9f..95300d4ed3f4 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -238,7 +238,7 @@ const ContextMenuActions: ContextMenuAction[] = [ { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.markAsUnread', - icon: Expensicons.Mail, + icon: Expensicons.ChatBubbleUnread, successIcon: Expensicons.Checkmark, shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, reportID, isPinnedChat, isUnreadChat) => type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION || (type === CONST.CONTEXT_MENU_TYPES.REPORT && !isUnreadChat), From f7a19cab866735c8485a25bad4d7abc817d066dd Mon Sep 17 00:00:00 2001 From: Aswin S Date: Tue, 23 Jan 2024 09:50:32 +0530 Subject: [PATCH 53/73] fix: common suffix length calculation --- src/libs/ComposerUtils/index.ts | 6 ++++-- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libs/ComposerUtils/index.ts b/src/libs/ComposerUtils/index.ts index 54af287a67b7..c0e01e3e751b 100644 --- a/src/libs/ComposerUtils/index.ts +++ b/src/libs/ComposerUtils/index.ts @@ -38,11 +38,13 @@ function canSkipTriggerHotkeys(isSmallScreenWidth: boolean, isKeyboardShown: boo * Finds the length of common suffix between two texts * @param str1 - first string to compare * @param str2 - next string to compare + * @param cursorPosition - position of cursor * @returns number - Length of the common suffix */ -function findCommonSuffixLength(str1: string, str2: string) { +function findCommonSuffixLength(str1: string, str2: string, cursorPosition: number) { let commonSuffixLength = 0; - const minLength = Math.min(str1.length, str2.length); + const minLength = Math.min(str1.length - cursorPosition, str2.length); + for (let i = 1; i <= minLength; i++) { if (str1.charAt(str1.length - i) === str2.charAt(str2.length - i)) { commonSuffixLength++; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 0706fa4fc8e2..90738c66579c 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -213,7 +213,7 @@ function ComposerWithSuggestions({ if (currentIndex < newText.length) { startIndex = currentIndex; - const commonSuffixLength = ComposerUtils.findCommonSuffixLength(prevText, newText); + const commonSuffixLength = ComposerUtils.findCommonSuffixLength(prevText, newText, selection.end); // if text is getting pasted over find length of common suffix and subtract it from new text length if (commonSuffixLength > 0 || selection.end - selection.start > 0) { endIndex = newText.length - commonSuffixLength; @@ -228,7 +228,7 @@ function ComposerWithSuggestions({ diff: newText.substring(startIndex, endIndex), }; }, - [selection.end, selection.start], + [selection.start, selection.end], ); /** From 83553150afbd3236a82010865715904315ef7526 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Tue, 23 Jan 2024 09:54:12 +0530 Subject: [PATCH 54/73] refactor: remove redundant changes --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 90738c66579c..ee7f0168940d 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -302,14 +302,14 @@ function ComposerWithSuggestions({ } }, [ - raiseIsScrollLikelyLayoutTriggered, + debouncedUpdateFrequentlyUsedEmojis, findNewlyAddedChars, - preferredSkinTone, preferredLocale, + preferredSkinTone, + reportID, setIsCommentEmpty, suggestionsRef, - debouncedUpdateFrequentlyUsedEmojis, - reportID, + raiseIsScrollLikelyLayoutTriggered, debouncedSaveReportComment, selection.end, ], From f8051ceef05967c50179aecc6c9a1b2e06298d8e Mon Sep 17 00:00:00 2001 From: Matt Allen Date: Tue, 23 Jan 2024 15:19:56 -0800 Subject: [PATCH 55/73] Update CONTRIBUTING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated Line 126 to read > 13. Daily updates on weekdays are highly recommended. If you know you won’t be able to provide updates within 48 hours, please comment on the PR or issue stating how long you plan to be out so that we may plan accordingly. We understand everyone needs a little vacation here and there. Any issue that doesn't receive an update for 5 days (including weekend days) may be considered abandoned and the original contract terminated. Coming from https://expensify.slack.com/archives/C01SKUP7QR0/p1705964771887319 --- contributingGuides/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index 9eb16099f535..25f54c668b24 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -123,7 +123,7 @@ Additionally if you want to discuss an idea with the open source community witho ``` 11. [Open a pull request](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork), and make sure to fill in the required fields. 12. An Expensify engineer and a member from the Contributor-Plus team will be assigned to your pull request automatically to review. -13. Daily updates on weekdays are highly recommended. If you know you won’t be able to provide updates for > 1 week, please comment on the PR or issue how long you plan to be out so that we may plan accordingly. We understand everyone needs a little vacation here and there. Any issue that doesn't receive an update for 1 full week may be considered abandoned and the original contract terminated. +13. Daily updates on weekdays are highly recommended. If you know you won’t be able to provide updates within 48 hours, please comment on the PR or issue stating how long you plan to be out so that we may plan accordingly. We understand everyone needs a little vacation here and there. Any issue that doesn't receive an update for 5 days (including weekend days) may be considered abandoned and the original contract terminated. #### Submit your pull request for final review 14. When you are ready to submit your pull request for final review, make sure the following checks pass: From fb49bc9496fe342e4e674d47512a95c684955e1d Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Wed, 24 Jan 2024 06:42:14 +0530 Subject: [PATCH 56/73] Remove shouldScrollToTopOnSelect prop We remove this prop from BaseSelectionList as we don't have a usecase for now --- src/components/SelectionList/BaseSelectionList.js | 3 +-- src/components/SelectionList/selectionListPropTypes.js | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index dd8cba3602dc..c123633297c1 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -66,7 +66,6 @@ function BaseSelectionList({ shouldShowTooltips = true, shouldUseDynamicMaxToRenderPerBatch = false, rightHandSideComponent, - shouldScrollToTopOnSelect = true, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -379,7 +378,7 @@ function BaseSelectionList({ } // set the focus on the first item when the sections list is changed - if (sections.length > 0 && shouldScrollToTopOnSelect) { + if (sections.length > 0) { updateAndScrollToFocusedIndex(0); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/components/SelectionList/selectionListPropTypes.js b/src/components/SelectionList/selectionListPropTypes.js index 1790cf3aad6f..f5178112a4c3 100644 --- a/src/components/SelectionList/selectionListPropTypes.js +++ b/src/components/SelectionList/selectionListPropTypes.js @@ -198,9 +198,6 @@ const propTypes = { /** Right hand side component to display in the list item. Function has list item passed as the param */ rightHandSideComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), - - /** Whether to scroll to top when an option is selected */ - shouldScrollToTopOnSelect: PropTypes.bool, }; export {propTypes, baseListItemPropTypes, radioListItemPropTypes, userListItemPropTypes}; From 84f085162ff251bfa3df3dfcc515a9f76a7cd6f5 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Wed, 24 Jan 2024 08:51:54 +0530 Subject: [PATCH 57/73] fix: remove params & returns from jsdoc --- src/libs/ComposerUtils/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libs/ComposerUtils/index.ts b/src/libs/ComposerUtils/index.ts index c0e01e3e751b..4113f7447d58 100644 --- a/src/libs/ComposerUtils/index.ts +++ b/src/libs/ComposerUtils/index.ts @@ -17,8 +17,6 @@ function insertText(text: string, selection: Selection, textToInsert: string): s /** * Insert a white space at given index of text * @param text - text that needs whitespace to be appended to - * @param index - index at which whitespace should be inserted - * @returns */ function insertWhiteSpaceAtIndex(text: string, index: number) { @@ -36,10 +34,6 @@ function canSkipTriggerHotkeys(isSmallScreenWidth: boolean, isKeyboardShown: boo /** * Finds the length of common suffix between two texts - * @param str1 - first string to compare - * @param str2 - next string to compare - * @param cursorPosition - position of cursor - * @returns number - Length of the common suffix */ function findCommonSuffixLength(str1: string, str2: string, cursorPosition: number) { let commonSuffixLength = 0; From f37fef1928d4f59ecaca3fc4891afc371b0cb6b3 Mon Sep 17 00:00:00 2001 From: Github Date: Wed, 17 Jan 2024 10:19:14 +0100 Subject: [PATCH 58/73] Add performance test speed improvements --- .../ModifiedExpenseMessage.perf-test.ts | 10 ++-- tests/perf-test/OptionsSelector.perf-test.js | 13 ++--- .../ReportActionCompose.perf-test.js | 13 ++--- .../perf-test/ReportActionsList.perf-test.js | 7 +-- .../perf-test/ReportActionsUtils.perf-test.ts | 18 +++--- tests/perf-test/ReportScreen.perf-test.js | 6 +- tests/perf-test/ReportUtils.perf-test.ts | 56 +++++++++---------- tests/perf-test/SelectionList.perf-test.js | 9 +-- tests/perf-test/SidebarLinks.perf-test.js | 6 +- tests/perf-test/SidebarUtils.perf-test.ts | 33 +++++------ 10 files changed, 71 insertions(+), 100 deletions(-) diff --git a/tests/perf-test/ModifiedExpenseMessage.perf-test.ts b/tests/perf-test/ModifiedExpenseMessage.perf-test.ts index 5aa842155cb5..6e9ab5394cf9 100644 --- a/tests/perf-test/ModifiedExpenseMessage.perf-test.ts +++ b/tests/perf-test/ModifiedExpenseMessage.perf-test.ts @@ -11,8 +11,6 @@ import createRandomReportAction from '../utils/collections/reportActions'; import createRandomReport from '../utils/collections/reports'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -const runs = CONST.PERFORMANCE_TESTS.RUNS; - beforeAll(() => Onyx.init({ keys: ONYXKEYS, @@ -39,10 +37,10 @@ const getMockedPolicies = (length = 500) => length, ); -const mockedReportsMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; -const mockedPoliciesMap = getMockedPolicies(5000) as Record<`${typeof ONYXKEYS.COLLECTION.POLICY}`, Policy>; +const mockedReportsMap = getMockedReports(1000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; +const mockedPoliciesMap = getMockedPolicies(1000) as Record<`${typeof ONYXKEYS.COLLECTION.POLICY}`, Policy>; -test('[ModifiedExpenseMessage] getForReportAction on 5k reports and policies', async () => { +test('[ModifiedExpenseMessage] getForReportAction on 1k reports and policies', async () => { const reportAction = { ...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE, @@ -60,5 +58,5 @@ test('[ModifiedExpenseMessage] getForReportAction on 5k reports and policies', a }); await waitForBatchedUpdates(); - await measureFunction(() => ModifiedExpenseMessage.getForReportAction(reportAction), {runs}); + await measureFunction(() => ModifiedExpenseMessage.getForReportAction(reportAction)); }); diff --git a/tests/perf-test/OptionsSelector.perf-test.js b/tests/perf-test/OptionsSelector.perf-test.js index b30169b8b53f..6104ded05c6a 100644 --- a/tests/perf-test/OptionsSelector.perf-test.js +++ b/tests/perf-test/OptionsSelector.perf-test.js @@ -3,7 +3,6 @@ import React from 'react'; import {measurePerformance} from 'reassure'; import _ from 'underscore'; import OptionsSelector from '@src/components/OptionsSelector'; -import CONST from '@src/CONST'; import variables from '@src/styles/variables'; jest.mock('../../src/components/withLocalize', () => (Component) => { @@ -65,8 +64,6 @@ function OptionsSelectorWrapper(args) { ); } -const runs = CONST.PERFORMANCE_TESTS.RUNS; - test('[OptionsSelector] should render text input with interactions', () => { const scenario = (screen) => { const textInput = screen.getByTestId('options-selector-input'); @@ -75,16 +72,16 @@ test('[OptionsSelector] should render text input with interactions', () => { fireEvent.changeText(textInput, 'test3'); }; - measurePerformance(, {scenario, runs}); + measurePerformance(, {scenario}); }); test('[OptionsSelector] should render 1 section', () => { - measurePerformance(, {runs}); + measurePerformance(); }); test('[OptionsSelector] should render multiple sections', () => { const sections = generateSections(mutlipleSectionsConfig); - measurePerformance(, {runs}); + measurePerformance(); }); test('[OptionsSelector] should press a list items', () => { @@ -94,7 +91,7 @@ test('[OptionsSelector] should press a list items', () => { fireEvent.press(screen.getByText('Item 10')); }; - measurePerformance(, {scenario, runs}); + measurePerformance(, {scenario}); }); test('[OptionsSelector] should scroll and press few items', () => { @@ -126,5 +123,5 @@ test('[OptionsSelector] should scroll and press few items', () => { fireEvent.press(screen.getByText('Item 200')); }; - measurePerformance(, {scenario, runs}); + measurePerformance(, {scenario}); }); diff --git a/tests/perf-test/ReportActionCompose.perf-test.js b/tests/perf-test/ReportActionCompose.perf-test.js index de34c139a361..9384bba27c7e 100644 --- a/tests/perf-test/ReportActionCompose.perf-test.js +++ b/tests/perf-test/ReportActionCompose.perf-test.js @@ -7,7 +7,6 @@ import {LocaleContextProvider} from '../../src/components/LocaleContextProvider' import OnyxProvider from '../../src/components/OnyxProvider'; import {KeyboardStateProvider} from '../../src/components/withKeyboardState'; import {WindowDimensionsProvider} from '../../src/components/withWindowDimensions'; -import CONST from '../../src/CONST'; import * as Localize from '../../src/libs/Localize'; import ONYXKEYS from '../../src/ONYXKEYS'; import ReportActionCompose from '../../src/pages/home/report/ReportActionCompose/ReportActionCompose'; @@ -64,8 +63,6 @@ beforeEach(() => { Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); }); -const runs = CONST.PERFORMANCE_TESTS.RUNS; - function ReportActionComposeWrapper() { return ( @@ -96,7 +93,7 @@ test('[ReportActionCompose] should render Composer with text input interactions' fireEvent.press(composer); }; - return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); + return waitForBatchedUpdates().then(() => measurePerformance(, {scenario})); }); test('[ReportActionCompose] should press add attachemnt button', async () => { @@ -108,7 +105,7 @@ test('[ReportActionCompose] should press add attachemnt button', async () => { fireEvent.press(attachmentButton, mockEvent); }; - return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); + return waitForBatchedUpdates().then(() => measurePerformance(, {scenario})); }); test('[ReportActionCompose] should press add emoji button', async () => { @@ -120,7 +117,7 @@ test('[ReportActionCompose] should press add emoji button', async () => { fireEvent.press(emojiButton); }; - return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); + return waitForBatchedUpdates().then(() => measurePerformance(, {scenario})); }); test('[ReportActionCompose] should press send message button', async () => { @@ -132,7 +129,7 @@ test('[ReportActionCompose] should press send message button', async () => { fireEvent.press(sendButton); }; - return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); + return waitForBatchedUpdates().then(() => measurePerformance(, {scenario})); }); test('[ReportActionCompose] render composer with attachement modal interactions', async () => { @@ -152,5 +149,5 @@ test('[ReportActionCompose] render composer with attachement modal interactions' fireEvent.press(assignTaskButton, mockEvent); }; - return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); + return waitForBatchedUpdates().then(() => measurePerformance(, {scenario})); }); diff --git a/tests/perf-test/ReportActionsList.perf-test.js b/tests/perf-test/ReportActionsList.perf-test.js index 8e3312cfa4c7..34b127c217e4 100644 --- a/tests/perf-test/ReportActionsList.perf-test.js +++ b/tests/perf-test/ReportActionsList.perf-test.js @@ -5,7 +5,6 @@ import ComposeProviders from '../../src/components/ComposeProviders'; import {LocaleContextProvider} from '../../src/components/LocaleContextProvider'; import OnyxProvider from '../../src/components/OnyxProvider'; import {WindowDimensionsProvider} from '../../src/components/withWindowDimensions'; -import CONST from '../../src/CONST'; import * as Localize from '../../src/libs/Localize'; import ONYXKEYS from '../../src/ONYXKEYS'; import ReportActionsList from '../../src/pages/home/report/ReportActionsList'; @@ -97,8 +96,6 @@ function ReportActionsListWrapper() { ); } -const runs = CONST.PERFORMANCE_TESTS.RUNS; - test('[ReportActionsList] should render ReportActionsList with 500 reportActions stored', () => { const scenario = async () => { await screen.findByTestId('report-actions-list'); @@ -113,7 +110,7 @@ test('[ReportActionsList] should render ReportActionsList with 500 reportActions [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, }), ) - .then(() => measurePerformance(, {scenario, runs})); + .then(() => measurePerformance(, {scenario})); }); test('[ReportActionsList] should scroll and click some of the reports', () => { @@ -151,5 +148,5 @@ test('[ReportActionsList] should scroll and click some of the reports', () => { [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, }), ) - .then(() => measurePerformance(, {scenario, runs})); + .then(() => measurePerformance(, {scenario})); }); diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts index 2a96a5959942..f194cd32bbf4 100644 --- a/tests/perf-test/ReportActionsUtils.perf-test.ts +++ b/tests/perf-test/ReportActionsUtils.perf-test.ts @@ -35,8 +35,6 @@ const reportActions = createCollection( const reportId = '1'; -const runs = CONST.PERFORMANCE_TESTS.RUNS; - describe('ReportActionsUtils', () => { beforeAll(() => { Onyx.init({ @@ -65,7 +63,7 @@ describe('ReportActionsUtils', () => { */ test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId), {runs}); + await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId)); }); test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions with actionsToMerge', async () => { @@ -91,19 +89,19 @@ describe('ReportActionsUtils', () => { } as unknown as ReportActions; await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge), {runs}); + await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge)); }); test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray), {runs}); + await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray)); }); test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId), {runs}); + await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId)); }); test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () => { @@ -129,21 +127,21 @@ describe('ReportActionsUtils', () => { } as unknown as ReportActions; await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge), {runs}); + await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge)); }); test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions), {runs}); + await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions)); }); test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastClosedReportAction(reportActions), {runs}); + await measureFunction(() => ReportActionsUtils.getLastClosedReportAction(reportActions)); }); test('[ReportActionsUtils] getMostRecentReportActionLastModified', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getMostRecentReportActionLastModified(), {runs}); + await measureFunction(() => ReportActionsUtils.getMostRecentReportActionLastModified()); }); }); diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index d58f71fa7ab4..5a144e715f5b 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -152,8 +152,6 @@ function ReportScreenWrapper(args) { ); } -const runs = CONST.PERFORMANCE_TESTS.RUNS; - test.skip('[ReportScreen] should render ReportScreen with composer interactions', () => { const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { @@ -222,7 +220,7 @@ test.skip('[ReportScreen] should render ReportScreen with composer interactions' navigation={navigation} route={mockRoute} />, - {scenario, runs}, + {scenario}, ), ); }); @@ -287,7 +285,7 @@ test.skip('[ReportScreen] should press of the report item', () => { navigation={navigation} route={mockRoute} />, - {scenario, runs}, + {scenario}, ), ); }); diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts index 953fc88a99cf..76a85ab36ae4 100644 --- a/tests/perf-test/ReportUtils.perf-test.ts +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -12,7 +12,6 @@ import createRandomReport from '../utils/collections/reports'; import createRandomTransaction from '../utils/collections/transaction'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -const runs = CONST.PERFORMANCE_TESTS.RUNS; const getMockedReports = (length = 500) => createCollection( (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, @@ -33,8 +32,8 @@ const personalDetails = createCollection( 1000, ); -const mockedReportsMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; -const mockedPoliciesMap = getMockedPolicies(5000) as Record<`${typeof ONYXKEYS.COLLECTION.POLICY}`, Policy>; +const mockedReportsMap = getMockedReports(1000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; +const mockedPoliciesMap = getMockedPolicies(1000) as Record<`${typeof ONYXKEYS.COLLECTION.POLICY}`, Policy>; const participantAccountIDs = Array.from({length: 1000}, (v, i) => i + 1); describe('ReportUtils', () => { @@ -62,15 +61,15 @@ describe('ReportUtils', () => { const openOnAdminRoom = true; await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom), {runs}); + await measureFunction(() => ReportUtils.findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom)); }); - test('[ReportUtils] canDeleteReportAction on 5k reports and policies', async () => { + test('[ReportUtils] canDeleteReportAction on 1k reports and policies', async () => { const reportID = '1'; const reportAction = {...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT} as unknown as ReportAction; await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.canDeleteReportAction(reportAction, reportID), {runs}); + await measureFunction(() => ReportUtils.canDeleteReportAction(reportAction, reportID)); }); test('[ReportUtils] getReportRecipientAccountID on 1k participants', async () => { @@ -78,14 +77,14 @@ describe('ReportUtils', () => { const currentLoginAccountID = 1; await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getReportRecipientAccountIDs(report, currentLoginAccountID), {runs}); + await measureFunction(() => ReportUtils.getReportRecipientAccountIDs(report, currentLoginAccountID)); }); test('[ReportUtils] getIconsForParticipants on 1k participants', async () => { const participants = Array.from({length: 1000}, (v, i) => i + 1); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getIconsForParticipants(participants, personalDetails), {runs}); + await measureFunction(() => ReportUtils.getIconsForParticipants(participants, personalDetails)); }); test('[ReportUtils] getIcons on 1k participants', async () => { @@ -96,7 +95,7 @@ describe('ReportUtils', () => { const defaultIconId = -1; await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getIcons(report, personalDetails, defaultIcon, defaultName, defaultIconId, policy), {runs}); + await measureFunction(() => ReportUtils.getIcons(report, personalDetails, defaultIcon, defaultName, defaultIconId, policy)); }); test('[ReportUtils] getDisplayNamesWithTooltips 1k participants', async () => { @@ -104,10 +103,10 @@ describe('ReportUtils', () => { const shouldFallbackToHidden = true; await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getDisplayNamesWithTooltips(personalDetails, isMultipleParticipantReport, shouldFallbackToHidden), {runs}); + await measureFunction(() => ReportUtils.getDisplayNamesWithTooltips(personalDetails, isMultipleParticipantReport, shouldFallbackToHidden)); }); - test('[ReportUtils] getReportPreviewMessage on 5k policies', async () => { + test('[ReportUtils] getReportPreviewMessage on 51k policies', async () => { const reportAction = createRandomReportAction(1); const report = createRandomReport(1); const policy = createRandomPolicy(1); @@ -115,7 +114,7 @@ describe('ReportUtils', () => { const isPreviewMessageForParentChatReport = true; await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getReportPreviewMessage(report, reportAction, shouldConsiderReceiptBeingScanned, isPreviewMessageForParentChatReport, policy), {runs}); + await measureFunction(() => ReportUtils.getReportPreviewMessage(report, reportAction, shouldConsiderReceiptBeingScanned, isPreviewMessageForParentChatReport, policy)); }); test('[ReportUtils] getReportName on 1k participants', async () => { @@ -123,7 +122,7 @@ describe('ReportUtils', () => { const policy = createRandomPolicy(1); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getReportName(report, policy), {runs}); + await measureFunction(() => ReportUtils.getReportName(report, policy)); }); test('[ReportUtils] canShowReportRecipientLocalTime on 1k participants', async () => { @@ -131,7 +130,7 @@ describe('ReportUtils', () => { const accountID = 1; await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, accountID), {runs}); + await measureFunction(() => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, accountID)); }); test('[ReportUtils] shouldReportBeInOptionList on 1k participant', async () => { @@ -142,20 +141,17 @@ describe('ReportUtils', () => { const policies = getMockedPolicies(); await waitForBatchedUpdates(); - await measureFunction( - () => ReportUtils.shouldReportBeInOptionList({report, currentReportId, isInGSDMode, betas, policies, doesReportHaveViolations: false, excludeEmptyChats: false}), - { - runs, - }, + await measureFunction(() => + ReportUtils.shouldReportBeInOptionList({report, currentReportId, isInGSDMode, betas, policies, doesReportHaveViolations: false, excludeEmptyChats: false}), ); }); - test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => { + test('[ReportUtils] getWorkspaceIcon on 1k policies', async () => { const report = createRandomReport(1); const policy = createRandomPolicy(1); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getWorkspaceIcon(report, policy), {runs}); + await measureFunction(() => ReportUtils.getWorkspaceIcon(report, policy)); }); test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => { @@ -164,32 +160,32 @@ describe('ReportUtils', () => { const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, policy, reportParticipants), {runs}); + await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, policy, reportParticipants)); }); - test('[ReportUtils] getWorkspaceAvatar on 5k policies', async () => { + test('[ReportUtils] getWorkspaceAvatar on 1k policies', async () => { const report = createRandomReport(1); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getWorkspaceAvatar(report), {runs}); + await measureFunction(() => ReportUtils.getWorkspaceAvatar(report)); }); - test('[ReportUtils] getWorkspaceChat on 5k policies', async () => { + test('[ReportUtils] getWorkspaceChat on 1k policies', async () => { const policyID = '1'; const accountsID = Array.from({length: 20}, (v, i) => i + 1); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getWorkspaceChats(policyID, accountsID), {runs}); + await measureFunction(() => ReportUtils.getWorkspaceChats(policyID, accountsID)); }); - test('[ReportUtils] getTransactionDetails on 5k reports', async () => { + test('[ReportUtils] getTransactionDetails on 1k reports', async () => { const transaction = createRandomTransaction(1); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getTransactionDetails(transaction, 'yyyy-MM-dd'), {runs}); + await measureFunction(() => ReportUtils.getTransactionDetails(transaction, 'yyyy-MM-dd')); }); - test('[ReportUtils] getIOUReportActionDisplayMessage on 5k policies', async () => { + test('[ReportUtils] getIOUReportActionDisplayMessage on 1k policies', async () => { const reportAction = { ...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.IOU, @@ -205,6 +201,6 @@ describe('ReportUtils', () => { }; await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getIOUReportActionDisplayMessage(reportAction), {runs}); + await measureFunction(() => ReportUtils.getIOUReportActionDisplayMessage(reportAction)); }); }); diff --git a/tests/perf-test/SelectionList.perf-test.js b/tests/perf-test/SelectionList.perf-test.js index b9f05c33e925..9decc4361612 100644 --- a/tests/perf-test/SelectionList.perf-test.js +++ b/tests/perf-test/SelectionList.perf-test.js @@ -3,7 +3,6 @@ import React, {useState} from 'react'; import {measurePerformance} from 'reassure'; import _ from 'underscore'; import SelectionList from '../../src/components/SelectionList'; -import CONST from '../../src/CONST'; import variables from '../../src/styles/variables'; jest.mock('../../src/components/Icon/Expensicons'); @@ -93,8 +92,6 @@ function SelectionListWrapper(args) { ); } -const runs = CONST.PERFORMANCE_TESTS.RUNS; - test('[SelectionList] should render 1 section and a thousand items', () => { measurePerformance(); }); @@ -104,7 +101,7 @@ test('[SelectionList] should press a list item', () => { fireEvent.press(screen.getByText('Item 5')); }; - measurePerformance(, {scenario, runs}); + measurePerformance(, {scenario}); }); test('[SelectionList] should render multiple selection and select 3 items', () => { @@ -114,7 +111,7 @@ test('[SelectionList] should render multiple selection and select 3 items', () = fireEvent.press(screen.getByText('Item 3')); }; - measurePerformance(, {scenario, runs}); + measurePerformance(, {scenario}); }); test('[SelectionList] should scroll and select a few items', () => { @@ -145,5 +142,5 @@ test('[SelectionList] should scroll and select a few items', () => { fireEvent.press(screen.getByText('Item 15')); }; - measurePerformance(, {scenario, runs}); + measurePerformance(, {scenario}); }); diff --git a/tests/perf-test/SidebarLinks.perf-test.js b/tests/perf-test/SidebarLinks.perf-test.js index 8109c00c0cea..0b10718fd0c4 100644 --- a/tests/perf-test/SidebarLinks.perf-test.js +++ b/tests/perf-test/SidebarLinks.perf-test.js @@ -31,8 +31,6 @@ const getMockedReportsMap = (length = 100) => { const mockedResponseMap = getMockedReportsMap(500); -const runs = CONST.PERFORMANCE_TESTS.RUNS; - describe('SidebarLinks', () => { beforeAll(() => { Onyx.init({ @@ -73,7 +71,7 @@ describe('SidebarLinks', () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario, runs}); + await measurePerformance(, {scenario}); }); test('[SidebarLinks] should scroll and click some of the items', async () => { @@ -108,6 +106,6 @@ describe('SidebarLinks', () => { await waitForBatchedUpdates(); - await measurePerformance(, {scenario, runs}); + await measurePerformance(, {scenario}); }); }); diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 7b2fd873f3de..3aa65331b9c2 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -32,8 +32,7 @@ const personalDetails = createCollection( (index) => createPersonalDetails(index), ); -const mockedResponseMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; -const runs = CONST.PERFORMANCE_TESTS.RUNS; +const mockedResponseMap = getMockedReports(1000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; describe('SidebarUtils', () => { beforeAll(() => { @@ -51,7 +50,7 @@ describe('SidebarUtils', () => { Onyx.clear(); }); - test('[SidebarUtils] getOptionData on 5k reports', async () => { + test('[SidebarUtils] getOptionData on 1k reports', async () => { const report = createRandomReport(1); const preferredLocale = 'en'; const policy = createRandomPolicy(1); @@ -59,22 +58,20 @@ describe('SidebarUtils', () => { await waitForBatchedUpdates(); - await measureFunction( - () => - SidebarUtils.getOptionData({ - report, - reportActions, - personalDetails, - preferredLocale, - policy, - parentReportAction, - hasViolations: false, - }), - {runs}, + await measureFunction(() => + SidebarUtils.getOptionData({ + report, + reportActions, + personalDetails, + preferredLocale, + policy, + parentReportAction, + hasViolations: false, + }), ); }); - test('[SidebarUtils] getOrderedReportIDs on 5k reports', async () => { + test('[SidebarUtils] getOrderedReportIDs on 1k reports', async () => { const currentReportId = '1'; const allReports = getMockedReports(); const betas = [CONST.BETAS.DEFAULT_ROOMS]; @@ -104,8 +101,6 @@ describe('SidebarUtils', () => { ) as unknown as OnyxCollection; await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions, transactionViolations), { - runs, - }); + await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions, transactionViolations)); }); }); From eb00ffe7424845aec1a1029fc17f91e23fb91a86 Mon Sep 17 00:00:00 2001 From: Github Date: Wed, 17 Jan 2024 14:33:28 +0100 Subject: [PATCH 59/73] remove unnecessary const --- src/CONST.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 5fee60e57617..827b72725166 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3050,13 +3050,6 @@ const CONST = { */ MAX_OPTIONS_SELECTOR_PAGE_LENGTH: 500, - /** - * Performance test setup - run the same test multiple times to get a more accurate result - */ - PERFORMANCE_TESTS: { - RUNS: 20, - }, - /** * Bank account names */ From 8d54f46edeaef820851b8ad141564365d1df64b5 Mon Sep 17 00:00:00 2001 From: Github Date: Wed, 24 Jan 2024 11:24:59 +0100 Subject: [PATCH 60/73] post merge fixes --- .../ReportActionCompose.perf-test.js | 16 ++++++++++++ tests/perf-test/SearchPage.perf-test.js | 26 ++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/perf-test/ReportActionCompose.perf-test.js b/tests/perf-test/ReportActionCompose.perf-test.js index 9384bba27c7e..27d83cb885da 100644 --- a/tests/perf-test/ReportActionCompose.perf-test.js +++ b/tests/perf-test/ReportActionCompose.perf-test.js @@ -50,6 +50,22 @@ jest.mock('../../src/libs/actions/EmojiPickerAction', () => { }; }); +jest.mock('../../src/components/withNavigationFocus', () => (Component) => { + function WithNavigationFocus(props) { + return ( + + ); + } + + WithNavigationFocus.displayName = 'WithNavigationFocus'; + + return WithNavigationFocus; +}); + beforeAll(() => Onyx.init({ keys: ONYXKEYS, diff --git a/tests/perf-test/SearchPage.perf-test.js b/tests/perf-test/SearchPage.perf-test.js index 046d651469f1..b868394b0987 100644 --- a/tests/perf-test/SearchPage.perf-test.js +++ b/tests/perf-test/SearchPage.perf-test.js @@ -40,6 +40,22 @@ jest.mock('@react-navigation/native', () => { }; }); +jest.mock('../../src/components/withNavigationFocus', () => (Component) => { + function WithNavigationFocus(props) { + return ( + + ); + } + + WithNavigationFocus.displayName = 'WithNavigationFocus'; + + return WithNavigationFocus; +}); + const getMockedReports = (length = 100) => createCollection( (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, @@ -91,8 +107,6 @@ function SearchPageWrapper(args) { ); } -const runs = CONST.PERFORMANCE_TESTS.RUNS; - test('[Search Page] should interact when text input changes', async () => { const {addListener} = TestHelper.createAddListenerMock(); @@ -117,7 +131,7 @@ test('[Search Page] should interact when text input changes', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => measurePerformance(, {scenario, runs})); + .then(() => measurePerformance(, {scenario})); }); test('[Search Page] should render options list', async () => { @@ -143,7 +157,7 @@ test('[Search Page] should render options list', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => measurePerformance(, {scenario, runs})); + .then(() => measurePerformance(, {scenario})); }); test.skip('[Search Page] should search in options list', async () => { @@ -174,7 +188,7 @@ test.skip('[Search Page] should search in options list', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => measurePerformance(, {scenario, runs})); + .then(() => measurePerformance(, {scenario})); }); test.skip('[Search Page] should click on list item', async () => { @@ -202,5 +216,5 @@ test.skip('[Search Page] should click on list item', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => measurePerformance(, {scenario, runs})); + .then(() => measurePerformance(, {scenario})); }); From 58f76ec1c97def082665b8801cece9ff65b7612d Mon Sep 17 00:00:00 2001 From: Github Date: Wed, 24 Jan 2024 11:40:44 +0100 Subject: [PATCH 61/73] fix typo --- tests/perf-test/ReportUtils.perf-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts index 76a85ab36ae4..ae3429bb9c01 100644 --- a/tests/perf-test/ReportUtils.perf-test.ts +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -106,7 +106,7 @@ describe('ReportUtils', () => { await measureFunction(() => ReportUtils.getDisplayNamesWithTooltips(personalDetails, isMultipleParticipantReport, shouldFallbackToHidden)); }); - test('[ReportUtils] getReportPreviewMessage on 51k policies', async () => { + test('[ReportUtils] getReportPreviewMessage on 1k policies', async () => { const reportAction = createRandomReportAction(1); const report = createRandomReport(1); const policy = createRandomPolicy(1); From f7b827523115d38eb54ccbd060d3f841ead74c5e Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:57:11 +0100 Subject: [PATCH 62/73] add 'undefined' safety --- src/hooks/useResponsiveLayout.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useResponsiveLayout.ts b/src/hooks/useResponsiveLayout.ts index 10f1bccf15bd..42af016bef29 100644 --- a/src/hooks/useResponsiveLayout.ts +++ b/src/hooks/useResponsiveLayout.ts @@ -10,8 +10,8 @@ type ResponsiveLayoutResult = { */ export default function useResponsiveLayout(): ResponsiveLayoutResult { const {isSmallScreenWidth} = useWindowDimensions(); - const state = navigationRef.getRootState(); - const lastRoute = state.routes.at(-1); + const state = navigationRef?.getRootState(); + const lastRoute = state?.routes?.at(-1); const lastRouteName = lastRoute?.name; const isInModal = lastRouteName === NAVIGATORS.LEFT_MODAL_NAVIGATOR || lastRouteName === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; const shouldUseNarrowLayout = isSmallScreenWidth || isInModal; From dfcbaf7334785cb9f08d3ac25419394b0107fe72 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:02:23 +0100 Subject: [PATCH 63/73] return isSmallScreenWidth and isInModal from useResponsiveLayout --- src/hooks/useResponsiveLayout.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useResponsiveLayout.ts b/src/hooks/useResponsiveLayout.ts index 42af016bef29..a825acd1039c 100644 --- a/src/hooks/useResponsiveLayout.ts +++ b/src/hooks/useResponsiveLayout.ts @@ -3,7 +3,8 @@ import NAVIGATORS from '@src/NAVIGATORS'; import useWindowDimensions from './useWindowDimensions'; type ResponsiveLayoutResult = { - shouldUseNarrowLayout: boolean; + isSmallScreenWidth: boolean; + isInModal: boolean; }; /** * Hook to determine if we are on mobile devices or in the Modal Navigator @@ -14,6 +15,5 @@ export default function useResponsiveLayout(): ResponsiveLayoutResult { const lastRoute = state?.routes?.at(-1); const lastRouteName = lastRoute?.name; const isInModal = lastRouteName === NAVIGATORS.LEFT_MODAL_NAVIGATOR || lastRouteName === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - const shouldUseNarrowLayout = isSmallScreenWidth || isInModal; - return {shouldUseNarrowLayout}; + return {isSmallScreenWidth, isInModal}; } From 26768e3d6a6bf113e26d0c230e23432fa4e6caf9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 25 Jan 2024 16:20:31 +0700 Subject: [PATCH 64/73] add comment --- src/libs/actions/IOU.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 38fb43a3ebe6..d26316b04e66 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -312,6 +312,7 @@ function getReceiptError(receipt, filename, isScanRequest = true) { } /** + * Return the object to update hasOutstandingChildRequest * @param {Object} [policy] * @param {Boolean} needsToBeManuallySubmitted * @returns {Object} @@ -329,6 +330,7 @@ function getOutstandingChildRequest(policy, needsToBeManuallySubmitted) { }; } + // We don't need to update hasOutstandingChildRequest in this case return {}; } @@ -372,7 +374,7 @@ function buildOnyxDataForMoneyRequest( needsToBeManuallySubmitted = true, ) { const isScanRequest = TransactionUtils.isScanRequest(transaction); - const hasOutstandingChildRequest = getOutstandingChildRequest(needsToBeManuallySubmitted, policy); + const outstandingChildRequest = getOutstandingChildRequest(needsToBeManuallySubmitted, policy); const optimisticData = [ { // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page @@ -383,7 +385,7 @@ function buildOnyxDataForMoneyRequest( lastReadTime: DateUtils.getDBTime(), lastMessageTranslationKey: '', iouReportID: iouReport.reportID, - hasOutstandingChildRequest, + ...outstandingChildRequest, ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), }, }, From b118e62ff3284ab179c81732a57e17f9884c9a56 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 25 Jan 2024 18:13:05 +0700 Subject: [PATCH 65/73] lint fix --- src/libs/actions/IOU.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index d26316b04e66..e6aa9398c05b 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -312,7 +312,7 @@ function getReceiptError(receipt, filename, isScanRequest = true) { } /** - * Return the object to update hasOutstandingChildRequest + * Return the object to update hasOutstandingChildRequest * @param {Object} [policy] * @param {Boolean} needsToBeManuallySubmitted * @returns {Object} From a5caca2d9e411566e7a9510d416c8dbd8930248b Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 25 Jan 2024 10:15:52 -0300 Subject: [PATCH 66/73] fix lint --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 40f535ebbaf7..34c06fad8618 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -80,7 +80,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ safeAreaPaddingBottomStyle, iouType, iouRequestType, - isSearchingForReports, didScreenTransitionEnd, }) { const {translate} = useLocalize(); From c53276d7bb3e30edc066d9c01ef58d701459e521 Mon Sep 17 00:00:00 2001 From: brunovjk Date: Thu, 25 Jan 2024 10:55:35 -0300 Subject: [PATCH 67/73] fix lint: Removed unused variables and props --- ...oneyTemporaryForRefactorRequestParticipantsSelector.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 34c06fad8618..15f98205839e 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -55,9 +55,6 @@ const propTypes = { /** The request type, ie. manual, scan, distance */ iouRequestType: PropTypes.oneOf(_.values(CONST.IOU.REQUEST_TYPE)).isRequired, - /** Whether we are searching for reports in the server */ - isSearchingForReports: PropTypes.bool, - /** Whether the parent screen transition has ended */ didScreenTransitionEnd: PropTypes.bool, }; @@ -67,7 +64,6 @@ const defaultProps = { safeAreaPaddingBottomStyle: {}, reports: {}, betas: [], - isSearchingForReports: false, didScreenTransitionEnd: false, }; @@ -369,8 +365,4 @@ export default withOnyx({ betas: { key: ONYXKEYS.BETAS, }, - isSearchingForReports: { - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - initWithStoredValues: false, - }, })(MoneyTemporaryForRefactorRequestParticipantsSelector); From 09ce2f1b494f8aaa269b5cfd4074139ba8ab5d47 Mon Sep 17 00:00:00 2001 From: Github Date: Thu, 25 Jan 2024 15:41:05 +0100 Subject: [PATCH 68/73] Perf test workflow improvement --- .github/workflows/reassurePerformanceTests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml index 64b4536d9241..ebe31f41f3d8 100644 --- a/.github/workflows/reassurePerformanceTests.yml +++ b/.github/workflows/reassurePerformanceTests.yml @@ -33,6 +33,7 @@ jobs: npx reassure --baseline git switch --force --detach - git merge --no-commit --allow-unrelated-histories "$BASELINE_BRANCH" -X ours + git checkout --ours . npm install --force npx reassure --branch From f0ebda9cad483c83909d43de909fff899064e970 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Thu, 25 Jan 2024 21:25:58 +0530 Subject: [PATCH 69/73] fix: remove redundant param shouldAddTrailSpace --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index ee7f0168940d..90c2ba0b42cf 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -358,7 +358,6 @@ function ComposerWithSuggestions({ /** * Callback to add whatever text is chosen into the main input (used f.e as callback for the emoji picker) * @param {String} text - * @param {Boolean} shouldAddTrailSpace */ const replaceSelectionWithText = useCallback( (text) => { From 27f5395a17e6819fbc43da786a21826a97e407d5 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Thu, 25 Jan 2024 21:27:15 +0530 Subject: [PATCH 70/73] fix: remove empty line --- src/libs/ComposerUtils/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ComposerUtils/index.ts b/src/libs/ComposerUtils/index.ts index 4113f7447d58..94bba5d0d00c 100644 --- a/src/libs/ComposerUtils/index.ts +++ b/src/libs/ComposerUtils/index.ts @@ -18,7 +18,6 @@ function insertText(text: string, selection: Selection, textToInsert: string): s * Insert a white space at given index of text * @param text - text that needs whitespace to be appended to */ - function insertWhiteSpaceAtIndex(text: string, index: number) { return `${text.slice(0, index)} ${text.slice(index)}`; } From 4915f12a92708d2decccf28b5fb16ac827b63d38 Mon Sep 17 00:00:00 2001 From: rayane-djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Thu, 25 Jan 2024 22:21:45 +0100 Subject: [PATCH 71/73] add shouldUseNarrowLayout --- src/hooks/useResponsiveLayout.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/useResponsiveLayout.ts b/src/hooks/useResponsiveLayout.ts index a825acd1039c..f00890116d47 100644 --- a/src/hooks/useResponsiveLayout.ts +++ b/src/hooks/useResponsiveLayout.ts @@ -3,6 +3,7 @@ import NAVIGATORS from '@src/NAVIGATORS'; import useWindowDimensions from './useWindowDimensions'; type ResponsiveLayoutResult = { + shouldUseNarrowLayout: boolean; isSmallScreenWidth: boolean; isInModal: boolean; }; @@ -15,5 +16,6 @@ export default function useResponsiveLayout(): ResponsiveLayoutResult { const lastRoute = state?.routes?.at(-1); const lastRouteName = lastRoute?.name; const isInModal = lastRouteName === NAVIGATORS.LEFT_MODAL_NAVIGATOR || lastRouteName === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - return {isSmallScreenWidth, isInModal}; + const shouldUseNarrowLayout = isSmallScreenWidth || isInModal; + return {shouldUseNarrowLayout, isSmallScreenWidth, isInModal}; } From 03f0da1a366c9e2b1a92adc9b2d07a579b72e71e Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 26 Jan 2024 03:56:41 +0530 Subject: [PATCH 72/73] fixes typescript mistake with type assertion --- src/pages/home/report/ReportActionItemMessage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx index d68fbf889f29..5f0e3ea4b72c 100644 --- a/src/pages/home/report/ReportActionItemMessage.tsx +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -9,7 +9,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; -import type {OriginalMessageSource} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; import TextCommentFragment from './comment/TextCommentFragment'; import ReportActionItemFragment from './ReportActionItemFragment'; @@ -78,7 +78,7 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid iouMessage={iouMessage} isThreadParentMessage={ReportActionsUtils.isThreadParentMessage(action, reportID)} pendingAction={action.pendingAction} - source={action.originalMessage as OriginalMessageSource} + source={(action.originalMessage as OriginalMessageAddComment['originalMessage'])?.source} accountID={action.actorAccountID ?? 0} style={style} displayAsGroup={displayAsGroup} From 0805a9380c2c0ef11311c8a13db93c0cb2d6c4bd Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 26 Jan 2024 15:46:30 +0700 Subject: [PATCH 73/73] fix ts --- src/libs/OptionsListUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index fbfcc4488e32..6332a57deec0 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1563,7 +1563,7 @@ function getOptions( if (includePersonalDetails) { // Next loop over all personal details removing any that are selectedUsers or recentChats allPersonalDetailsOptions.forEach((personalDetailOption) => { - if (optionsToExclude.some((optionToExclude) => optionToExclude.login === addSMSDomainIfPhoneNumber(personalDetailOption.login))) { + if (optionsToExclude.some((optionToExclude) => optionToExclude.login === addSMSDomainIfPhoneNumber(personalDetailOption.login ?? ''))) { return; } const {searchText, participantsList, isChatRoom} = personalDetailOption;