From 0b4807dda633311d716329f09df41b08559df1e9 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 27 Jan 2025 15:18:26 +0100 Subject: [PATCH 1/3] Fix composer focus when navigating when starting new chat --- src/libs/actions/Report.ts | 9 ++++++--- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 7 ++++--- .../report/ReportActionCompose/ReportActionCompose.tsx | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9afa4886365a..c5d4dae30615 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1179,10 +1179,13 @@ function navigateToAndOpenReport( // We want to pass newChat here because if anything is passed in that param (even an existing chat), we will try to create a chat on the server openReport(report?.reportID, '', userLogins, newChat, undefined, undefined, undefined, avatarFile); if (shouldDismissModal) { - Navigation.dismissModalWithReport(report); - return; + Navigation.dismissModal(); } - Navigation.navigateToReportWithPolicyCheck({report}); + + // In some cases when RHP modal gets hidden and then we navigate to report Composer focus breaks, wrapping navigation in setTimeout fixes this + setTimeout(() => { + Navigation.isNavigationReady().then(() => Navigation.navigateToReportWithPolicyCheck({report})); + }, 0); } /** diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index f88c39fa9457..45a0f19bdd5e 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -646,7 +646,8 @@ function ComposerWithSuggestions( const prevIsFocused = usePrevious(isFocused); useEffect(() => { - if (modal?.isVisible && !prevIsModalVisible) { + const isModalVisible = modal?.isVisible; + if (isModalVisible && !prevIsModalVisible) { // eslint-disable-next-line react-compiler/react-compiler, no-param-reassign isNextModalWillOpenRef.current = false; } @@ -654,6 +655,7 @@ function ComposerWithSuggestions( // We want to blur the input immediately when a screen is out of focus. if (!isFocused) { textInputRef.current?.blur(); + return; } // We want to focus or refocus the input when a modal has been closed or the underlying screen is refocused. @@ -663,8 +665,7 @@ function ComposerWithSuggestions( !( (willBlurTextInputOnTapOutside || (shouldAutoFocus && canFocusInputOnScreenFocus())) && !isNextModalWillOpenRef.current && - !modal?.isVisible && - isFocused && + !isModalVisible && (!!prevIsModalVisible || !prevIsFocused) ) ) { diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 0a461bdf756a..a380b3e99db0 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -467,7 +467,7 @@ function ReportActionCompose({ reportParticipantIDs={reportParticipantIDs} isComposerFullSize={isComposerFullSize} isBlockedFromConcierge={isBlockedFromConcierge} - disabled={!!disabled} + disabled={disabled} setMenuVisibility={setMenuVisibility} isMenuVisible={isMenuVisible} onTriggerAttachmentPicker={onTriggerAttachmentPicker} @@ -505,7 +505,7 @@ function ReportActionCompose({ displayFileInModal={displayFileInModal} onCleared={submitForm} isBlockedFromConcierge={isBlockedFromConcierge} - disabled={!!disabled} + disabled={disabled} setIsCommentEmpty={setIsCommentEmpty} handleSendMessage={handleSendMessage} shouldShowComposeInput={shouldShowComposeInput} From 2b169c0d5b9d48a9a47187b51ff12e8180a18b51 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 27 Jan 2025 16:11:39 +0100 Subject: [PATCH 2/3] Simplify ReportSplitNavigator --- .../Navigators/ReportsSplitNavigator.tsx | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/ReportsSplitNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/ReportsSplitNavigator.tsx index c50728362b06..6d7f9198a000 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/ReportsSplitNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/ReportsSplitNavigator.tsx @@ -1,6 +1,5 @@ import {useRoute} from '@react-navigation/native'; import React, {useState} from 'react'; -import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import usePermissions from '@hooks/usePermissions'; import createSplitNavigator from '@libs/Navigation/AppNavigator/createSplitNavigator'; @@ -38,26 +37,24 @@ function ReportsSplitNavigator() { return ( - - - - - - + + + + ); } From b4ab2d3de8cec549ccff224fce1db99d26883d3f Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 28 Jan 2025 12:17:33 +0100 Subject: [PATCH 3/3] Simplify focusing logic and fix focusing composer after message edit. Removed or modernized dead code around focusing logic and subscribing to onyx. --- src/libs/ReportActionComposeFocusManager.ts | 5 +- .../home/report/PureReportActionItem.tsx | 6 +- .../report/ReportActionItemMessageEdit.tsx | 79 +++---------------- 3 files changed, 17 insertions(+), 73 deletions(-) diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts index 2967a49512ea..b79249cd027f 100644 --- a/src/libs/ReportActionComposeFocusManager.ts +++ b/src/libs/ReportActionComposeFocusManager.ts @@ -1,8 +1,8 @@ +import {findFocusedRoute} from '@react-navigation/native'; import React from 'react'; import type {MutableRefObject} from 'react'; import type {TextInput} from 'react-native'; import SCREENS from '@src/SCREENS'; -import getTopmostRouteName from './Navigation/helpers/getTopmostRouteName'; import isReportOpenInRHP from './Navigation/helpers/isReportOpenInRHP'; import navigationRef from './Navigation/navigationRef'; @@ -38,7 +38,8 @@ function onComposerFocus(callback: FocusCallback | null, isPriorityCallback = fa function focus(shouldFocusForNonBlurInputOnTapOutside?: boolean) { /** Do not trigger the refocusing when the active route is not the report screen */ const navigationState = navigationRef.getState(); - if (!navigationState || (!isReportOpenInRHP(navigationState) && getTopmostRouteName(navigationState) !== SCREENS.REPORT)) { + const focusedRoute = findFocusedRoute(navigationState); + if (!navigationState || (!isReportOpenInRHP(navigationState) && focusedRoute?.name !== SCREENS.REPORT)) { return; } diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index c74531acf317..72a9d9931b3a 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -366,7 +366,7 @@ function PureReportActionItem({ const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); const reactionListRef = useContext(ReactionListContext); const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); - const textInputRef = useRef(null); + const composerTextInputRef = useRef(null); const popoverAnchorRef = useRef>(null); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); @@ -443,7 +443,7 @@ function PureReportActionItem({ return; } - focusComposerWithDelay(textInputRef.current)(true); + focusComposerWithDelay(composerTextInputRef.current)(true); }, [prevDraftMessage, draftMessage]); useEffect(() => { @@ -951,7 +951,7 @@ function PureReportActionItem({ reportID={reportID} policyID={report?.policyID} index={index} - ref={textInputRef} + ref={composerTextInputRef} shouldDisableEmojiPicker={ (chatIncludesConcierge(report) && isBlockedFromConcierge(blockedFromConcierge)) || isArchivedNonExpenseReport(report, reportNameValuePairs) } diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 1862e2b96596..0812def281ce 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -27,7 +27,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {setShouldShowComposeInput} from '@libs/actions/Composer'; import {clearActive, isActive as isEmojiPickerActive, isEmojiPickerVisible} from '@libs/actions/EmojiPickerAction'; -import {composerFocusKeepFocusOn, callback as inputFocusCallback, inputFocusChange} from '@libs/actions/InputFocus'; +import {composerFocusKeepFocusOn} from '@libs/actions/InputFocus'; import {deleteReportActionDraft, editReportComment, saveReportActionDraft} from '@libs/actions/Report'; import {canSkipTriggerHotkeys, insertText} from '@libs/ComposerUtils'; import DomUtils from '@libs/DomUtils'; @@ -35,7 +35,6 @@ import {extractEmojis, replaceAndExtractEmojis} from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import type {Selection} from '@libs/focusComposerWithDelay/types'; import focusEditAfterCancelDelete from '@libs/focusEditAfterCancelDelete'; -import onyxSubscribe from '@libs/onyxSubscribe'; import Parser from '@libs/Parser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import reportActionItemEventHandler from '@libs/ReportActionItemEventHandler'; @@ -98,7 +97,6 @@ function ReportActionItemMessageEdit( const mobileInputScrollPosition = useRef(0); const cursorPositionValue = useSharedValue({x: 0, y: 0}); const tag = useSharedValue(-1); - const isInitialMount = useRef(true); const emojisPresentBefore = useRef([]); const [draft, setDraft] = useState(() => { if (draftMessage) { @@ -110,11 +108,14 @@ function ReportActionItemMessageEdit( const [isFocused, setIsFocused] = useState(false); const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); const debouncedValidateCommentMaxLength = useMemo(() => lodashDebounce(validateCommentMaxLength, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME), [validateCommentMaxLength]); - const [modal, setModal] = useState({ - willAlertModalBecomeVisible: false, - isVisible: false, - }); - const [onyxFocused, setOnyxFocused] = useState(false); + + const [ + modal = { + willAlertModalBecomeVisible: false, + isVisible: false, + }, + ] = useOnyx(ONYXKEYS.MODAL); + const [onyxInputFocused = false] = useOnyx(ONYXKEYS.INPUT_FOCUSED); const textInputRef = useRef<(HTMLTextAreaElement & TextInput) | null>(null); const isFocusedRef = useRef(false); @@ -136,34 +137,8 @@ function ReportActionItemMessageEdit( }, [draftMessage, action, prevDraftMessage]); useEffect(() => { - composerFocusKeepFocusOn(textInputRef.current as HTMLElement, isFocused, modal, onyxFocused); - }, [isFocused, modal, onyxFocused]); - - useEffect(() => { - const unsubscribeOnyxModal = onyxSubscribe({ - key: ONYXKEYS.MODAL, - callback: (modalArg) => { - if (modalArg === undefined) { - return; - } - setModal(modalArg); - }, - }); - - const unsubscribeOnyxFocused = onyxSubscribe({ - key: ONYXKEYS.INPUT_FOCUSED, - callback: (modalArg) => { - if (modalArg === undefined) { - return; - } - setOnyxFocused(modalArg); - }, - }); - return () => { - unsubscribeOnyxModal(); - unsubscribeOnyxFocused(); - }; - }, []); + composerFocusKeepFocusOn(textInputRef.current as HTMLElement, isFocused, modal, onyxInputFocused); + }, [isFocused, modal, onyxInputFocused]); useEffect( // Remove focus callback on unmount to avoid stale callbacks @@ -202,38 +177,6 @@ function ReportActionItemMessageEdit( }, true); }, [focus]); - useEffect( - () => { - if (isInitialMount.current) { - isInitialMount.current = false; - return; - } - - return () => { - inputFocusCallback(() => setIsFocused(false)); - inputFocusChange(false); - - // Skip if the current report action is not active - if (!isActive()) { - return; - } - - if (isEmojiPickerActive(action.reportActionID)) { - clearActive(); - } - if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { - ReportActionContextMenu.clearActiveReportAction(); - } - - // Show the main composer when the focused message is deleted from another client - // to prevent the main composer stays hidden until we switch to another chat. - setShouldShowComposeInputKeyboardAware(true); - }; - }, - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- this cleanup needs to be called only on unmount - [action.reportActionID], - ); - // show the composer after editing is complete for devices that hide the composer during editing. useEffect(() => () => setShouldShowComposeInput(true), []);