From 33e6dc11a9ce2336757214963d28c077d3f8baed Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:25:35 +1300 Subject: [PATCH 01/13] Fix Composer context menu not opening on Android --- src/components/Composer/implementation/index.native.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index a02767d24c87..29b807230384 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -172,7 +172,8 @@ function Composer( }} onClear={onClear} showSoftInputOnFocus={showSoftInputOnFocus} - contextMenuHidden={contextMenuHidden} + // Prevent the context menu from showing when tapping the composer to focus it on iOS. + contextMenuHidden={getPlatform() === CONST.PLATFORM.IOS ? contextMenuHidden : false} /> ); } From 12a7e06b1176881e04ba04ba1fc255211fe0c3bf Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:33:26 +1300 Subject: [PATCH 02/13] Migrate imports --- src/components/Composer/implementation/index.native.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 29b807230384..4a3642c9a6eb 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -15,8 +15,8 @@ import useResetComposerFocus from '@hooks/useResetComposerFocus'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as EmojiUtils from '@libs/EmojiUtils'; -import * as FileUtils from '@libs/fileDownload/FileUtils'; +import {containsOnlyEmojis} from '@libs/EmojiUtils'; +import {splitExtensionFromFileName} from '@libs/fileDownload/FileUtils'; import getPlatform from '@libs/getPlatform'; import CONST from '@src/CONST'; @@ -45,7 +45,7 @@ function Composer( ) { const textInput = useRef(null); const {isFocused, shouldResetFocusRef} = useResetComposerFocus(textInput); - const textContainsOnlyEmojis = useMemo(() => EmojiUtils.containsOnlyEmojis(value ?? ''), [value]); + const textContainsOnlyEmojis = useMemo(() => containsOnlyEmojis(value ?? ''), [value]); const theme = useTheme(); const markdownStyle = useMarkdownStyle(value, !isGroupPolicyReport ? excludeReportMentionStyle : excludeNoStyles); const styles = useThemeStyles(); @@ -129,7 +129,7 @@ function Composer( const mimeType = clipboardContent?.type ?? ''; const fileURI = clipboardContent?.data; const baseFileName = fileURI?.split('/').pop() ?? 'file'; - const {fileName: stem, fileExtension: originalFileExtension} = FileUtils.splitExtensionFromFileName(baseFileName); + const {fileName: stem, fileExtension: originalFileExtension} = splitExtensionFromFileName(baseFileName); const fileExtension = originalFileExtension || (mimeDb[mimeType].extensions?.[0] ?? 'bin'); const fileName = `${stem}.${fileExtension}`; const file: FileObject = {uri: fileURI, name: fileName, type: mimeType}; From b86f04b5f53308cf6e117311be4b7bbc4c72f554 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:46:15 +1300 Subject: [PATCH 03/13] Fix Composer context menu not opening on iOS Safari --- .../ComposerWithSuggestions.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index f88c39fa9457..60a166665ede 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -783,9 +783,16 @@ function ComposerWithSuggestions( [measureParentContainer, cursorPositionValue, selection], ); + const isTouchEndedRef = useRef(false); + return ( <> - + { + isTouchEndedRef.current = true; + }} + > { + if (!isTouchEndedRef.current) { + // Don't open the keyboard on long press so the callout menu can show. + return; + } setShowSoftInputOnFocus(true); }, CONST.ANIMATED_TRANSITION); return; From d54d87fb0623ec8662c5100190319ad8ef71b1b6 Mon Sep 17 00:00:00 2001 From: Qichen Zhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:08:13 +1300 Subject: [PATCH 04/13] Unify behavior for iOS Chrome and Safari --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 60a166665ede..efb898281310 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -28,7 +28,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as Browser from '@libs/Browser'; +import {isMobileWebkit} from '@libs/Browser'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import {forceClearInput} from '@libs/ComponentUtils'; import * as ComposerUtils from '@libs/ComposerUtils'; @@ -826,14 +826,14 @@ function ComposerWithSuggestions( shouldCalculateCaretPosition onLayout={onLayout} onScroll={hideSuggestionMenu} - shouldContainScroll={Browser.isMobileSafari()} + shouldContainScroll={isMobileWebkit()} isGroupPolicyReport={isGroupPolicyReport} showSoftInputOnFocus={showSoftInputOnFocus} onTouchStart={() => { if (showSoftInputOnFocus) { return; } - if (Browser.isMobileSafari()) { + if (isMobileWebkit()) { isTouchEndedRef.current = false; setTimeout(() => { if (!isTouchEndedRef.current) { From 435e9ef3035b917b9567171e58c81841cefc91e8 Mon Sep 17 00:00:00 2001 From: Qichen Zhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:14:51 +1300 Subject: [PATCH 05/13] Unify behavior for iOS Chrome and Safari --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index efb898281310..0d500370e468 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -28,7 +28,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isMobileWebkit} from '@libs/Browser'; +import {isMobileWebKit} from '@libs/Browser'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import {forceClearInput} from '@libs/ComponentUtils'; import * as ComposerUtils from '@libs/ComposerUtils'; @@ -826,14 +826,14 @@ function ComposerWithSuggestions( shouldCalculateCaretPosition onLayout={onLayout} onScroll={hideSuggestionMenu} - shouldContainScroll={isMobileWebkit()} + shouldContainScroll={isMobileWebKit()} isGroupPolicyReport={isGroupPolicyReport} showSoftInputOnFocus={showSoftInputOnFocus} onTouchStart={() => { if (showSoftInputOnFocus) { return; } - if (isMobileWebkit()) { + if (isMobileWebKit()) { isTouchEndedRef.current = false; setTimeout(() => { if (!isTouchEndedRef.current) { From 72f4bb09c1f81b07def68e22ad6634c6b8c31a38 Mon Sep 17 00:00:00 2001 From: Qichen Zhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:19:58 +1300 Subject: [PATCH 06/13] Migrate imports --- .../ComposerWithSuggestions.tsx | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 0d500370e468..f185efa4dd66 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -31,16 +31,16 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {isMobileWebKit} from '@libs/Browser'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import {forceClearInput} from '@libs/ComponentUtils'; -import * as ComposerUtils from '@libs/ComposerUtils'; +import {canSkipTriggerHotkeys, findCommonSuffixLength, insertText, insertWhiteSpaceAtIndex} from '@libs/ComposerUtils'; import convertToLTRForComposer from '@libs/convertToLTRForComposer'; import {getDraftComment} from '@libs/DraftCommentUtils'; -import * as EmojiUtils from '@libs/EmojiUtils'; +import {containsOnlyEmojis, extractEmojis, getAddedEmojis, getPreferredSkinToneIndex, replaceAndExtractEmojis} from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import getPlatform from '@libs/getPlatform'; -import * as KeyDownListener from '@libs/KeyboardShortcut/KeyDownPressListener'; +import {addKeyDownPressListener, removeKeyDownPressListener} from '@libs/KeyboardShortcut/KeyDownPressListener'; import Parser from '@libs/Parser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; -import * as ReportUtils from '@libs/ReportUtils'; +import {isValidReportIDFromPath, shouldAutoFocusOnKeyPress} from '@libs/ReportUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; import getCursorPosition from '@pages/home/report/ReportActionCompose/getCursorPosition'; @@ -48,10 +48,10 @@ import getScrollPosition from '@pages/home/report/ReportActionCompose/getScrollP import type {SuggestionsRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose'; import SilentCommentUpdater from '@pages/home/report/ReportActionCompose/SilentCommentUpdater'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; -import * as EmojiPickerActions from '@userActions/EmojiPickerAction'; -import * as InputFocus from '@userActions/InputFocus'; -import * as Modal from '@userActions/Modal'; -import * as Report from '@userActions/Report'; +import {OnEmojiSelected, isEmojiPickerVisible} from '@userActions/EmojiPickerAction'; +import {inputFocusChange} from '@userActions/InputFocus'; +import {areAllModalsHidden} from '@userActions/Modal'; +import {broadcastUserIsTyping, saveReportActionDraft, saveReportDraftComment} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -155,7 +155,7 @@ type SwitchToCurrentReportProps = { type ComposerRef = { blur: () => void; focus: (shouldDelay?: boolean) => void; - replaceSelectionWithText: EmojiPickerActions.OnEmojiSelected; + replaceSelectionWithText: OnEmojiSelected; getCurrentText: () => string; isFocused: () => boolean; /** @@ -174,7 +174,7 @@ const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; */ const debouncedBroadcastUserIsTyping = lodashDebounce( (reportID: string) => { - Report.broadcastUserIsTyping(reportID); + broadcastUserIsTyping(reportID); }, 1000, { @@ -247,7 +247,7 @@ function ComposerWithSuggestions( const draftComment = getDraftComment(reportID) ?? ''; const [value, setValue] = useState(() => { if (draftComment) { - emojisPresentBefore.current = EmojiUtils.extractEmojis(draftComment); + emojisPresentBefore.current = extractEmojis(draftComment); } return draftComment; }); @@ -255,7 +255,7 @@ function ComposerWithSuggestions( const commentRef = useRef(value); const [modal] = useOnyx(ONYXKEYS.MODAL); - const [preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex}); + const [preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: getPreferredSkinToneIndex}); const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED); const lastTextRef = useRef(value); @@ -266,7 +266,7 @@ function ComposerWithSuggestions( const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; - const shouldAutoFocus = !modal?.isVisible && shouldShowComposeInput && Modal.areAllModalsHidden() && isFocused && !didHideComposerInput; + const shouldAutoFocus = !modal?.isVisible && shouldShowComposeInput && areAllModalsHidden() && isFocused && !didHideComposerInput; const valueRef = useRef(value); valueRef.current = value; @@ -307,7 +307,7 @@ function ComposerWithSuggestions( const debouncedSaveReportComment = useMemo( () => lodashDebounce((selectedReportID: string, newComment: string | null) => { - Report.saveReportDraftComment(selectedReportID, newComment); + saveReportDraftComment(selectedReportID, newComment); isCommentPendingSaved.current = false; }, 1000), [], @@ -319,7 +319,7 @@ function ComposerWithSuggestions( callback(); return; } - Report.saveReportDraftComment(preexistingReportID, commentRef.current, callback); + saveReportDraftComment(preexistingReportID, commentRef.current, callback); }); return () => { @@ -350,7 +350,7 @@ function ComposerWithSuggestions( if (currentIndex < newText.length) { startIndex = currentIndex; - const commonSuffixLength = ComposerUtils.findCommonSuffixLength(prevText, newText, selection?.end ?? 0); + const commonSuffixLength = findCommonSuffixLength(prevText, newText, selection?.end ?? 0); // if text is getting pasted over find length of common suffix and subtract it from new text length if (commonSuffixLength > 0 || (selection?.end ?? 0) - selection.start > 0) { endIndex = newText.length - commonSuffixLength; @@ -374,11 +374,11 @@ function ComposerWithSuggestions( (commentValue: string, shouldDebounceSaveComment?: boolean) => { raiseIsScrollLikelyLayoutTriggered(); const {startIndex, endIndex, diff} = findNewlyAddedChars(lastTextRef.current, commentValue); - const isEmojiInserted = diff.length && endIndex > startIndex && diff.trim() === diff && EmojiUtils.containsOnlyEmojis(diff); - const commentWithSpaceInserted = isEmojiInserted ? ComposerUtils.insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue; - const {text: newComment, emojis, cursorPosition} = EmojiUtils.replaceAndExtractEmojis(commentWithSpaceInserted, preferredSkinTone, preferredLocale); + const isEmojiInserted = diff.length && endIndex > startIndex && diff.trim() === diff && containsOnlyEmojis(diff); + const commentWithSpaceInserted = isEmojiInserted ? insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue; + const {text: newComment, emojis, cursorPosition} = replaceAndExtractEmojis(commentWithSpaceInserted, preferredSkinTone, preferredLocale); if (emojis.length) { - const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); + const newEmojis = getAddedEmojis(emojis, emojisPresentBefore.current); if (newEmojis.length) { // Ensure emoji suggestions are hidden after inserting emoji even when the selection is not changed if (suggestionsRef.current) { @@ -417,7 +417,7 @@ function ComposerWithSuggestions( isCommentPendingSaved.current = true; debouncedSaveReportComment(reportID, newCommentConverted); } else { - Report.saveReportDraftComment(reportID, newCommentConverted); + saveReportDraftComment(reportID, newCommentConverted); } if (newCommentConverted) { debouncedBroadcastUserIsTyping(reportID); @@ -433,7 +433,7 @@ function ComposerWithSuggestions( (text: string) => { // selection replacement should be debounced to avoid conflicts with text typing // (f.e. when emoji is being picked and 1 second still did not pass after user finished typing) - updateComment(ComposerUtils.insertText(commentRef.current, selection, text), true); + updateComment(insertText(commentRef.current, selection, text), true); }, [selection, updateComment], ); @@ -441,7 +441,7 @@ function ComposerWithSuggestions( const handleKeyPress = useCallback( (event: NativeSyntheticEvent) => { const webEvent = event as unknown as KeyboardEvent; - if (!webEvent || ComposerUtils.canSkipTriggerHotkeys(shouldUseNarrowLayout, isKeyboardShown)) { + if (!webEvent || canSkipTriggerHotkeys(shouldUseNarrowLayout, isKeyboardShown)) { return; } @@ -461,7 +461,7 @@ function ComposerWithSuggestions( webEvent.preventDefault(); if (lastReportAction) { const message = Array.isArray(lastReportAction?.message) ? lastReportAction?.message?.at(-1) ?? null : lastReportAction?.message ?? null; - Report.saveReportActionDraft(reportID, lastReportAction, Parser.htmlToMarkdown(message?.html ?? '')); + saveReportActionDraft(reportID, lastReportAction, Parser.htmlToMarkdown(message?.html ?? '')); } } // Flag emojis like "Wales" have several code points. Default backspace key action does not remove such flag emojis completely. @@ -544,7 +544,7 @@ function ComposerWithSuggestions( if (!suggestionsRef.current) { return false; } - InputFocus.inputFocusChange(false); + inputFocusChange(false); return suggestionsRef.current.setShouldBlockSuggestionCalc(false); }, [suggestionsRef]); @@ -579,7 +579,7 @@ function ComposerWithSuggestions( */ const checkComposerVisibility = useCallback(() => { // Checking whether the screen is focused or not, helps avoid `modal.isVisible` false when popups are closed, even if the modal is opened. - const isComposerCoveredUp = !isFocused || EmojiPickerActions.isEmojiPickerVisible() || isMenuVisible || !!modal?.isVisible || modal?.willAlertModalBecomeVisible; + const isComposerCoveredUp = !isFocused || isEmojiPickerVisible() || isMenuVisible || !!modal?.isVisible || modal?.willAlertModalBecomeVisible; return !isComposerCoveredUp; }, [isMenuVisible, modal, isFocused]); @@ -590,7 +590,7 @@ function ComposerWithSuggestions( return; } - if (!ReportUtils.shouldAutoFocusOnKeyPress(e)) { + if (!shouldAutoFocusOnKeyPress(e)) { return; } @@ -622,21 +622,21 @@ function ComposerWithSuggestions( }, []); useEffect(() => { - const unsubscribeNavigationBlur = navigation.addListener('blur', () => KeyDownListener.removeKeyDownPressListener(focusComposerOnKeyPress)); + const unsubscribeNavigationBlur = navigation.addListener('blur', () => removeKeyDownPressListener(focusComposerOnKeyPress)); const unsubscribeNavigationFocus = navigation.addListener('focus', () => { - KeyDownListener.addKeyDownPressListener(focusComposerOnKeyPress); + addKeyDownPressListener(focusComposerOnKeyPress); // The report isn't unmounted and can be focused again after going back from another report so we should update the composerRef again ReportActionComposeFocusManager.composerRef.current = textInputRef.current; setUpComposeFocusManager(); }); - KeyDownListener.addKeyDownPressListener(focusComposerOnKeyPress); + addKeyDownPressListener(focusComposerOnKeyPress); setUpComposeFocusManager(); return () => { ReportActionComposeFocusManager.clear(); - KeyDownListener.removeKeyDownPressListener(focusComposerOnKeyPress); + removeKeyDownPressListener(focusComposerOnKeyPress); unsubscribeNavigationBlur(); unsubscribeNavigationFocus(); }; @@ -672,7 +672,7 @@ function ComposerWithSuggestions( } if (editFocused) { - InputFocus.inputFocusChange(false); + inputFocusChange(false); return; } focus(true); @@ -863,7 +863,7 @@ function ComposerWithSuggestions( resetKeyboardInput={resetKeyboardInput} /> - {ReportUtils.isValidReportIDFromPath(reportID) && ( + {isValidReportIDFromPath(reportID) && ( Date: Tue, 28 Jan 2025 13:33:53 +1300 Subject: [PATCH 07/13] Migrate imports --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index f185efa4dd66..aa793aa4861e 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -48,7 +48,8 @@ import getScrollPosition from '@pages/home/report/ReportActionCompose/getScrollP import type {SuggestionsRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose'; import SilentCommentUpdater from '@pages/home/report/ReportActionCompose/SilentCommentUpdater'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; -import {OnEmojiSelected, isEmojiPickerVisible} from '@userActions/EmojiPickerAction'; +import {isEmojiPickerVisible} from '@userActions/EmojiPickerAction'; +import type {OnEmojiSelected} from '@userActions/EmojiPickerAction'; import {inputFocusChange} from '@userActions/InputFocus'; import {areAllModalsHidden} from '@userActions/Modal'; import {broadcastUserIsTyping, saveReportActionDraft, saveReportDraftComment} from '@userActions/Report'; From ac30cd2e94622c9aeab6b072de2ec8ee75f44fd2 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:38:04 +1300 Subject: [PATCH 08/13] Revert overly broad changes --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index aa793aa4861e..e07b098026ef 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -28,7 +28,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isMobileWebKit} from '@libs/Browser'; +import {isMobileSafari, isMobileWebKit} from '@libs/Browser'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import {forceClearInput} from '@libs/ComponentUtils'; import {canSkipTriggerHotkeys, findCommonSuffixLength, insertText, insertWhiteSpaceAtIndex} from '@libs/ComposerUtils'; @@ -827,7 +827,7 @@ function ComposerWithSuggestions( shouldCalculateCaretPosition onLayout={onLayout} onScroll={hideSuggestionMenu} - shouldContainScroll={isMobileWebKit()} + shouldContainScroll={isMobileSafari()} isGroupPolicyReport={isGroupPolicyReport} showSoftInputOnFocus={showSoftInputOnFocus} onTouchStart={() => { From 460b76e143ca6b3c149ad8da83823ed7a2b1aa9b Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 28 Jan 2025 20:14:46 +1300 Subject: [PATCH 09/13] Fix storybook --- src/stories/Composer.stories.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/stories/Composer.stories.tsx b/src/stories/Composer.stories.tsx index a92dc0e789a0..1c2e06199798 100644 --- a/src/stories/Composer.stories.tsx +++ b/src/stories/Composer.stories.tsx @@ -5,7 +5,7 @@ import React, {useState} from 'react'; import {Image, View} from 'react-native'; import type {FileObject} from '@components/AttachmentModal'; import Composer from '@components/Composer'; -import type {ComposerProps} from '@components/Composer/types'; +import type {ComposerProps, CustomSelectionChangeEvent, TextSelection} from '@components/Composer/types'; import RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; import withNavigationFallback from '@components/withNavigationFallback'; @@ -33,6 +33,7 @@ function Default(props: ComposerProps) { const [pastedFile, setPastedFile] = useState(null); const [comment, setComment] = useState(props.defaultValue); const renderedHTML = parser.replace(comment ?? ''); + const [selection, setSelection] = useState(() => ({start: props.defaultValue?.length ?? 0, end: props.defaultValue?.length ?? 0, positionX: 0, positionY: 0})); return ( @@ -41,8 +42,13 @@ function Default(props: ComposerProps) { // eslint-disable-next-line react/jsx-props-no-spreading {...props} multiline + value={comment} onChangeText={setComment} onPasteFile={setPastedFile} + selection={selection} + onSelectionChange={(e: CustomSelectionChangeEvent) => { + setSelection(e.nativeEvent.selection); + }} style={[defaultStyles.textInputCompose, defaultStyles.w100, defaultStyles.verticalAlignTop]} /> From f288ffbbdea899ab62d8fee3e60c259c7728964e Mon Sep 17 00:00:00 2001 From: Qichen Zhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 1 Feb 2025 18:37:01 +1300 Subject: [PATCH 10/13] Update Composer.stories.tsx --- src/stories/Composer.stories.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/stories/Composer.stories.tsx b/src/stories/Composer.stories.tsx index 1c2e06199798..efb8e05614e2 100644 --- a/src/stories/Composer.stories.tsx +++ b/src/stories/Composer.stories.tsx @@ -28,12 +28,17 @@ const story: Meta = { const parser = new ExpensiMark(); +const DEFAULT_VALUE = `Composer can do the following: + + * It can contain MD e.g. *bold* _italic_ + * Supports Pasted Images via Ctrl+V`; + function Default(props: ComposerProps) { const StyleUtils = useStyleUtils(); const [pastedFile, setPastedFile] = useState(null); - const [comment, setComment] = useState(props.defaultValue); + const [comment, setComment] = useState(DEFAULT_VALUE); const renderedHTML = parser.replace(comment ?? ''); - const [selection, setSelection] = useState(() => ({start: props.defaultValue?.length ?? 0, end: props.defaultValue?.length ?? 0, positionX: 0, positionY: 0})); + const [selection, setSelection] = useState(() => ({start: DEFAULT_VALUE.length, end: DEFAULT_VALUE.length, positionX: 0, positionY: 0})); return ( @@ -79,10 +84,6 @@ Default.args = { autoFocus: true, placeholder: 'Compose Text Here', placeholderTextColor: defaultTheme.placeholderText, - defaultValue: `Composer can do the following: - - * It can contain MD e.g. *bold* _italic_ - * Supports Pasted Images via Ctrl+V`, isDisabled: false, maxLines: 16, }; From 4c1c9630eefe36b43d45a6bb98c9da085dc0293e Mon Sep 17 00:00:00 2001 From: Qichen Zhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 1 Feb 2025 19:01:36 +1300 Subject: [PATCH 11/13] Add a comment --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index e07b098026ef..cccef71987a4 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -836,6 +836,7 @@ function ComposerWithSuggestions( } if (isMobileWebKit()) { isTouchEndedRef.current = false; + // In iOS browsers, open the keyboard after a timeout, or it will close briefly. setTimeout(() => { if (!isTouchEndedRef.current) { // Don't open the keyboard on long press so the callout menu can show. From 0697e72869d95b53c5710b31655b5a7da6d94f0d Mon Sep 17 00:00:00 2001 From: Qichen Zhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:35:47 +1300 Subject: [PATCH 12/13] Replace fireEvent with userEvent to test against disabled buttons --- tests/unit/CalendarPickerTest.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/CalendarPickerTest.tsx b/tests/unit/CalendarPickerTest.tsx index 5e6dbaf26ce0..0c3e6f87c054 100644 --- a/tests/unit/CalendarPickerTest.tsx +++ b/tests/unit/CalendarPickerTest.tsx @@ -1,5 +1,5 @@ import type ReactNavigationNative from '@react-navigation/native'; -import {fireEvent, render, screen, within} from '@testing-library/react-native'; +import {fireEvent, render, screen, userEvent, within} from '@testing-library/react-native'; import {addMonths, addYears, subMonths, subYears} from 'date-fns'; import type {ComponentType} from 'react'; import CalendarPicker from '@components/DatePicker/CalendarPicker'; @@ -121,11 +121,9 @@ describe('CalendarPicker', () => { expect(onSelectedMock).toHaveBeenCalledWith('2022-02-15'); }); - test('should block the back arrow when there is no available dates in the previous month', () => { + test('should block the back arrow when there is no available dates in the previous month', async () => { const minDate = new Date('2003-02-01'); const value = new Date('2003-02-17'); - - // given the min date is 1 render( { ); // When the previous month arrow is pressed - fireEvent.press(screen.getByTestId('prev-month-arrow')); + const user = userEvent.setup(); + await user.press(screen.getByTestId('prev-month-arrow')); // Then the previous month should not be called as the previous month button is disabled - const prevMonth = subMonths(new Date(), 1).getMonth(); + const prevMonth = subMonths(value, 1).getMonth(); expect(screen.queryByText(monthNames.at(prevMonth) ?? '')).not.toBeOnTheScreen(); }); - test('should block the next arrow when there is no available dates in the next month', () => { + test('should block the next arrow when there is no available dates in the next month', async () => { const maxDate = new Date('2003-02-24'); - const value = '2003-02-17'; + const value = new Date('2003-02-17'); render( { ); // When the next month arrow is pressed - fireEvent.press(screen.getByTestId('next-month-arrow')); + const user = userEvent.setup(); + await user.press(screen.getByTestId('next-month-arrow')); // Then the next month should not be called as the next month button is disabled - const nextMonth = addMonths(new Date(), 1).getMonth(); + const nextMonth = addMonths(value, 1).getMonth(); expect(screen.queryByText(monthNames.at(nextMonth) ?? '')).not.toBeOnTheScreen(); }); From 82cc582d0c7df54cefe6f27d3c902e19ff8924c5 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:26:00 +1300 Subject: [PATCH 13/13] Revert "Replace fireEvent with userEvent to test against disabled buttons" This reverts commit 0697e72869d95b53c5710b31655b5a7da6d94f0d. --- tests/unit/CalendarPickerTest.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/CalendarPickerTest.tsx b/tests/unit/CalendarPickerTest.tsx index 0c3e6f87c054..5e6dbaf26ce0 100644 --- a/tests/unit/CalendarPickerTest.tsx +++ b/tests/unit/CalendarPickerTest.tsx @@ -1,5 +1,5 @@ import type ReactNavigationNative from '@react-navigation/native'; -import {fireEvent, render, screen, userEvent, within} from '@testing-library/react-native'; +import {fireEvent, render, screen, within} from '@testing-library/react-native'; import {addMonths, addYears, subMonths, subYears} from 'date-fns'; import type {ComponentType} from 'react'; import CalendarPicker from '@components/DatePicker/CalendarPicker'; @@ -121,9 +121,11 @@ describe('CalendarPicker', () => { expect(onSelectedMock).toHaveBeenCalledWith('2022-02-15'); }); - test('should block the back arrow when there is no available dates in the previous month', async () => { + test('should block the back arrow when there is no available dates in the previous month', () => { const minDate = new Date('2003-02-01'); const value = new Date('2003-02-17'); + + // given the min date is 1 render( { ); // When the previous month arrow is pressed - const user = userEvent.setup(); - await user.press(screen.getByTestId('prev-month-arrow')); + fireEvent.press(screen.getByTestId('prev-month-arrow')); // Then the previous month should not be called as the previous month button is disabled - const prevMonth = subMonths(value, 1).getMonth(); + const prevMonth = subMonths(new Date(), 1).getMonth(); expect(screen.queryByText(monthNames.at(prevMonth) ?? '')).not.toBeOnTheScreen(); }); - test('should block the next arrow when there is no available dates in the next month', async () => { + test('should block the next arrow when there is no available dates in the next month', () => { const maxDate = new Date('2003-02-24'); - const value = new Date('2003-02-17'); + const value = '2003-02-17'; render( { ); // When the next month arrow is pressed - const user = userEvent.setup(); - await user.press(screen.getByTestId('next-month-arrow')); + fireEvent.press(screen.getByTestId('next-month-arrow')); // Then the next month should not be called as the next month button is disabled - const nextMonth = addMonths(value, 1).getMonth(); + const nextMonth = addMonths(new Date(), 1).getMonth(); expect(screen.queryByText(monthNames.at(nextMonth) ?? '')).not.toBeOnTheScreen(); });