From f9e525820fdded49b940dba13d4e16d7fd67b7e5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 10 Oct 2024 15:39:09 +0700 Subject: [PATCH 01/10] fix: Task description is covered by the keyboard pop up --- .../ComposerWithSuggestions.tsx | 177 +++++++----------- 1 file changed, 72 insertions(+), 105 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index e63bd952b4ab..37f05f5d780c 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -1,6 +1,6 @@ import {useIsFocused, useNavigation} from '@react-navigation/native'; import lodashDebounce from 'lodash/debounce'; -import type {ForwardedRef, MutableRefObject, RefAttributes, RefObject} from 'react'; +import type {ForwardedRef, MutableRefObject, RefObject} from 'react'; import React, {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import type { LayoutChangeEvent, @@ -14,7 +14,7 @@ import type { import {DeviceEventEmitter, findNodeHandle, InteractionManager, NativeModules, View} from 'react-native'; import {useFocusedInputHandler} from 'react-native-keyboard-controller'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import {useAnimatedRef, useSharedValue} from 'react-native-reanimated'; import type {Emoji} from '@assets/emojis/types'; import type {FileObject} from '@components/AttachmentModal'; @@ -65,113 +65,95 @@ type SyncSelection = { type NewlyAddedChars = {startIndex: number; endIndex: number; diff: string}; -type ComposerWithSuggestionsOnyxProps = { - /** The parent report actions for the report */ - parentReportActions: OnyxEntry; +type ComposerWithSuggestionsProps = Partial & { + /** Report ID */ + reportID: string; - /** The modal state */ - modal: OnyxEntry; + /** Callback to focus composer */ + onFocus: () => void; - /** The preferred skin tone of the user */ - preferredSkinTone: number; + /** Callback to blur composer */ + onBlur: (event: NativeSyntheticEvent) => void; - /** Whether the input is focused */ - editFocused: OnyxEntry; -}; - -type ComposerWithSuggestionsProps = ComposerWithSuggestionsOnyxProps & - Partial & { - /** Report ID */ - reportID: string; - - /** Callback to focus composer */ - onFocus: () => void; - - /** Callback to blur composer */ - onBlur: (event: NativeSyntheticEvent) => void; + /** Callback when layout of composer changes */ + onLayout?: (event: LayoutChangeEvent) => void; - /** Callback when layout of composer changes */ - onLayout?: (event: LayoutChangeEvent) => void; + /** Callback to update the value of the composer */ + onValueChange: (value: string) => void; - /** Callback to update the value of the composer */ - onValueChange: (value: string) => void; + /** Callback when the composer got cleared on the UI thread */ + onCleared?: (text: string) => void; - /** Callback when the composer got cleared on the UI thread */ - onCleared?: (text: string) => void; + /** Whether the composer is full size */ + isComposerFullSize: boolean; - /** Whether the composer is full size */ - isComposerFullSize: boolean; + /** Whether the menu is visible */ + isMenuVisible: boolean; - /** Whether the menu is visible */ - isMenuVisible: boolean; + /** The placeholder for the input */ + inputPlaceholder: string; - /** The placeholder for the input */ - inputPlaceholder: string; + /** Function to display a file in a modal */ + displayFileInModal: (file: FileObject) => void; - /** Function to display a file in a modal */ - displayFileInModal: (file: FileObject) => void; + /** Whether the user is blocked from concierge */ + isBlockedFromConcierge: boolean; - /** Whether the user is blocked from concierge */ - isBlockedFromConcierge: boolean; + /** Whether the input is disabled */ + disabled: boolean; - /** Whether the input is disabled */ - disabled: boolean; + /** Whether the full composer is available */ + isFullComposerAvailable: boolean; - /** Whether the full composer is available */ - isFullComposerAvailable: boolean; + /** Function to set whether the full composer is available */ + setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void; - /** Function to set whether the full composer is available */ - setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void; + /** Function to set whether the comment is empty */ + setIsCommentEmpty: (isCommentEmpty: boolean) => void; - /** Function to set whether the comment is empty */ - setIsCommentEmpty: (isCommentEmpty: boolean) => void; + /** Function to handle sending a message */ + handleSendMessage: () => void; - /** Function to handle sending a message */ - handleSendMessage: () => void; + /** Whether the compose input should show */ + shouldShowComposeInput: OnyxEntry; - /** Whether the compose input should show */ - shouldShowComposeInput: OnyxEntry; + /** Function to measure the parent container */ + measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; - /** Function to measure the parent container */ - measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; + /** Whether the scroll is likely to trigger a layout */ + isScrollLikelyLayoutTriggered: RefObject; - /** Whether the scroll is likely to trigger a layout */ - isScrollLikelyLayoutTriggered: RefObject; + /** Function to raise the scroll is likely layout triggered */ + raiseIsScrollLikelyLayoutTriggered: () => void; - /** Function to raise the scroll is likely layout triggered */ - raiseIsScrollLikelyLayoutTriggered: () => void; + /** The ref to the suggestions */ + suggestionsRef: React.RefObject; - /** The ref to the suggestions */ - suggestionsRef: React.RefObject; + /** The ref to the next modal will open */ + isNextModalWillOpenRef: MutableRefObject; - /** The ref to the next modal will open */ - isNextModalWillOpenRef: MutableRefObject; + /** Wheater chat is empty */ + isEmptyChat?: boolean; - /** Whether the edit is focused */ - editFocused: boolean; + /** The last report action */ + lastReportAction?: OnyxEntry; - /** Wheater chat is empty */ - isEmptyChat?: boolean; + /** Whether to include chronos */ + includeChronos?: boolean; - /** The last report action */ - lastReportAction?: OnyxEntry; + /** The parent report action ID */ + parentReportActionID?: string; - /** Whether to include chronos */ - includeChronos?: boolean; + /** The parent report ID */ + // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC + parentReportID: string | undefined; - /** The parent report action ID */ - parentReportActionID?: string; + /** Whether report is from group policy */ + isGroupPolicyReport: boolean; - /** The parent report ID */ - // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC - parentReportID: string | undefined; - - /** Whether report is from group policy */ - isGroupPolicyReport: boolean; - - /** policy ID of the report */ - policyID: string; - }; + /** policy ID of the report */ + policyID: string; +}; type SwitchToCurrentReportProps = { preexistingReportID: string; @@ -223,11 +205,6 @@ const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); */ function ComposerWithSuggestions( { - // Onyx - modal, - preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, - parentReportActions, - // Props: Report reportID, includeChronos, @@ -236,6 +213,7 @@ function ComposerWithSuggestions( parentReportActionID, isGroupPolicyReport, policyID, + parentReportID, // Focus onFocus, @@ -263,13 +241,18 @@ function ComposerWithSuggestions( // Refs suggestionsRef, isNextModalWillOpenRef, - editFocused, // For testing children, }: ComposerWithSuggestionsProps, ref: ForwardedRef, ) { + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); + const [modal] = useOnyx(ONYXKEYS.MODAL); + const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex}); + const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED); + const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {canEvict: false, initWithStoredValues: false}); + const {isKeyboardShown} = useKeyboardState(); const theme = useTheme(); const styles = useThemeStyles(); @@ -303,7 +286,7 @@ function ComposerWithSuggestions( !modal?.isVisible && Modal.areAllModalsHidden() && isFocused && - (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction))) && + (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction) && !ReportUtils.isTaskReport(report))) && shouldShowComposeInput; const valueRef = useRef(value); @@ -837,22 +820,6 @@ ComposerWithSuggestions.displayName = 'ComposerWithSuggestions'; const ComposerWithSuggestionsWithRef = forwardRef(ComposerWithSuggestions); -export default withOnyx, ComposerWithSuggestionsOnyxProps>({ - modal: { - key: ONYXKEYS.MODAL, - }, - preferredSkinTone: { - key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - selector: EmojiUtils.getPreferredSkinToneIndex, - }, - editFocused: { - key: ONYXKEYS.INPUT_FOCUSED, - }, - parentReportActions: { - key: ({parentReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, - canEvict: false, - initWithStoredValues: false, - }, -})(memo(ComposerWithSuggestionsWithRef)); +export default memo(ComposerWithSuggestionsWithRef); export type {ComposerWithSuggestionsProps, ComposerRef}; From 1e4e27505d5171218aa4ca340a8f34a7b47b6ba3 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 15 Oct 2024 15:48:48 +0700 Subject: [PATCH 02/10] fix test --- .../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 37f05f5d780c..ad1c6ec548c8 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -247,7 +247,6 @@ function ComposerWithSuggestions( }: ComposerWithSuggestionsProps, ref: ForwardedRef, ) { - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const [modal] = useOnyx(ONYXKEYS.MODAL); const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex}); const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED); @@ -286,7 +285,8 @@ function ComposerWithSuggestions( !modal?.isVisible && Modal.areAllModalsHidden() && isFocused && - (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction) && !ReportUtils.isTaskReport(report))) && + (shouldFocusInputOnScreenFocus || + (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction) && !ReportActionsUtils.isCreatedTaskReportAction(parentReportAction))) && shouldShowComposeInput; const valueRef = useRef(value); From e381a1a271dc2cc190b9c72b41f7ce083b10b7c6 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 15 Oct 2024 16:19:01 +0700 Subject: [PATCH 03/10] fix test --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index ad1c6ec548c8..1dac07f71780 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -250,7 +250,7 @@ function ComposerWithSuggestions( const [modal] = useOnyx(ONYXKEYS.MODAL); const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex}); const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED); - const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {canEvict: false, initWithStoredValues: false}); + const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {canEvict: false}); const {isKeyboardShown} = useKeyboardState(); const theme = useTheme(); From 8bdd1cf4bfa2d91d82783c37fed0cfcc2c010ec6 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 16 Oct 2024 15:16:34 +0700 Subject: [PATCH 04/10] fix perf-test --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 1dac07f71780..0676b0345510 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -280,7 +280,7 @@ function ComposerWithSuggestions( const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; - const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1']; + const parentReportAction = useMemo(() => parentReportActions?.[parentReportActionID ?? '-1'], [parentReportActionID, parentReportActions]); const shouldAutoFocus = !modal?.isVisible && Modal.areAllModalsHidden() && From cccef9bef421c7382ff16cfed1b2c15c9ac4fa17 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 16 Oct 2024 17:02:08 +0700 Subject: [PATCH 05/10] use ref for modal daat --- .../ComposerWithSuggestions.tsx | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 0676b0345510..0e7fef19d605 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -38,6 +38,7 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import getPlatform from '@libs/getPlatform'; import * as KeyDownListener from '@libs/KeyboardShortcut/KeyDownPressListener'; +import onyxSubscribe from '@libs/onyxSubscribe'; import Parser from '@libs/Parser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -247,7 +248,8 @@ function ComposerWithSuggestions( }: ComposerWithSuggestionsProps, ref: ForwardedRef, ) { - const [modal] = useOnyx(ONYXKEYS.MODAL); + const isVisibleRef = useRef(false); + const willAlertModalBecomeVisibleRef = useRef(false); const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex}); const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED); const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {canEvict: false}); @@ -277,12 +279,29 @@ function ComposerWithSuggestions( lastTextRef.current = value; }, [value]); + useEffect(() => { + const unsubscribe = onyxSubscribe({ + key: ONYXKEYS.MODAL, + callback: (modalArg) => { + if (!modalArg) { + return; + } + isVisibleRef.current = !!modalArg.isVisible; + willAlertModalBecomeVisibleRef.current = !!modalArg.willAlertModalBecomeVisible; + }, + }); + + return () => { + unsubscribe(); + }; + }, []); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; const parentReportAction = useMemo(() => parentReportActions?.[parentReportActionID ?? '-1'], [parentReportActionID, parentReportActions]); const shouldAutoFocus = - !modal?.isVisible && + !isVisibleRef.current && Modal.areAllModalsHidden() && isFocused && (shouldFocusInputOnScreenFocus || @@ -570,9 +589,9 @@ 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 || EmojiPickerActions.isEmojiPickerVisible() || isMenuVisible || !!isVisibleRef.current || willAlertModalBecomeVisibleRef.current; return !isComposerCoveredUp; - }, [isMenuVisible, modal, isFocused]); + }, [isMenuVisible, isFocused]); const focusComposerOnKeyPress = useCallback( (e: KeyboardEvent) => { @@ -633,10 +652,11 @@ function ComposerWithSuggestions( }; }, [focusComposerOnKeyPress, navigation, setUpComposeFocusManager]); - const prevIsModalVisible = usePrevious(modal?.isVisible); + const prevIsModalVisible = usePrevious(isVisibleRef.current); const prevIsFocused = usePrevious(isFocused); + useEffect(() => { - if (modal?.isVisible && !prevIsModalVisible) { + if (isVisibleRef.current && !prevIsModalVisible) { // eslint-disable-next-line react-compiler/react-compiler, no-param-reassign isNextModalWillOpenRef.current = false; } @@ -649,7 +669,7 @@ function ComposerWithSuggestions( // We want to focus or refocus the input when a modal has been closed or the underlying screen is refocused. // We avoid doing this on native platforms since the software keyboard popping // open creates a jarring and broken UX. - if (!((willBlurTextInputOnTapOutside || shouldAutoFocus) && !isNextModalWillOpenRef.current && !modal?.isVisible && isFocused && (!!prevIsModalVisible || !prevIsFocused))) { + if (!((willBlurTextInputOnTapOutside || shouldAutoFocus) && !isNextModalWillOpenRef.current && !isVisibleRef.current && isFocused && (!!prevIsModalVisible || !prevIsFocused))) { return; } @@ -658,7 +678,7 @@ function ComposerWithSuggestions( return; } focus(true); - }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal?.isVisible, isNextModalWillOpenRef, shouldAutoFocus]); + }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, isNextModalWillOpenRef, shouldAutoFocus]); useEffect(() => { // Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit From ebb488e22c79f40c4a927cb14ea4a0e61e7ab933 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 22 Oct 2024 17:15:33 +0700 Subject: [PATCH 06/10] use useOnyx --- .../ComposerWithSuggestions.tsx | 35 +++++-------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 0e7fef19d605..0e20ada7c7ff 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -38,7 +38,6 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import getPlatform from '@libs/getPlatform'; import * as KeyDownListener from '@libs/KeyboardShortcut/KeyDownPressListener'; -import onyxSubscribe from '@libs/onyxSubscribe'; import Parser from '@libs/Parser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -248,8 +247,7 @@ function ComposerWithSuggestions( }: ComposerWithSuggestionsProps, ref: ForwardedRef, ) { - const isVisibleRef = useRef(false); - const willAlertModalBecomeVisibleRef = useRef(false); + const [modal] = useOnyx(ONYXKEYS.MODAL); const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex}); const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED); const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {canEvict: false}); @@ -279,29 +277,12 @@ function ComposerWithSuggestions( lastTextRef.current = value; }, [value]); - useEffect(() => { - const unsubscribe = onyxSubscribe({ - key: ONYXKEYS.MODAL, - callback: (modalArg) => { - if (!modalArg) { - return; - } - isVisibleRef.current = !!modalArg.isVisible; - willAlertModalBecomeVisibleRef.current = !!modalArg.willAlertModalBecomeVisible; - }, - }); - - return () => { - unsubscribe(); - }; - }, []); - const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; const parentReportAction = useMemo(() => parentReportActions?.[parentReportActionID ?? '-1'], [parentReportActionID, parentReportActions]); const shouldAutoFocus = - !isVisibleRef.current && + !modal?.isVisible && Modal.areAllModalsHidden() && isFocused && (shouldFocusInputOnScreenFocus || @@ -589,9 +570,9 @@ 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 || !!isVisibleRef.current || willAlertModalBecomeVisibleRef.current; + const isComposerCoveredUp = !isFocused || EmojiPickerActions.isEmojiPickerVisible() || isMenuVisible || !!modal?.isVisible || modal?.willAlertModalBecomeVisible; return !isComposerCoveredUp; - }, [isMenuVisible, isFocused]); + }, [isFocused, isMenuVisible, modal?.isVisible, modal?.willAlertModalBecomeVisible]); const focusComposerOnKeyPress = useCallback( (e: KeyboardEvent) => { @@ -652,11 +633,11 @@ function ComposerWithSuggestions( }; }, [focusComposerOnKeyPress, navigation, setUpComposeFocusManager]); - const prevIsModalVisible = usePrevious(isVisibleRef.current); + const prevIsModalVisible = usePrevious(modal?.isVisible); const prevIsFocused = usePrevious(isFocused); useEffect(() => { - if (isVisibleRef.current && !prevIsModalVisible) { + if (modal?.isVisible && !prevIsModalVisible) { // eslint-disable-next-line react-compiler/react-compiler, no-param-reassign isNextModalWillOpenRef.current = false; } @@ -669,7 +650,7 @@ function ComposerWithSuggestions( // We want to focus or refocus the input when a modal has been closed or the underlying screen is refocused. // We avoid doing this on native platforms since the software keyboard popping // open creates a jarring and broken UX. - if (!((willBlurTextInputOnTapOutside || shouldAutoFocus) && !isNextModalWillOpenRef.current && !isVisibleRef.current && isFocused && (!!prevIsModalVisible || !prevIsFocused))) { + if (!((willBlurTextInputOnTapOutside || shouldAutoFocus) && !isNextModalWillOpenRef.current && !modal?.isVisible && isFocused && (!!prevIsModalVisible || !prevIsFocused))) { return; } @@ -678,7 +659,7 @@ function ComposerWithSuggestions( return; } focus(true); - }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, isNextModalWillOpenRef, shouldAutoFocus]); + }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, isNextModalWillOpenRef, shouldAutoFocus, modal?.isVisible]); useEffect(() => { // Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit From fbc69b27bbe062789e31a07e7f5869e082a4fcc7 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 25 Oct 2024 23:27:40 +0700 Subject: [PATCH 07/10] revert useOnyx --- .../ComposerWithSuggestions.tsx | 181 +++++++++++------- 1 file changed, 107 insertions(+), 74 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 0e20ada7c7ff..aeff0d70054e 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -1,6 +1,6 @@ import {useIsFocused, useNavigation} from '@react-navigation/native'; import lodashDebounce from 'lodash/debounce'; -import type {ForwardedRef, MutableRefObject, RefObject} from 'react'; +import type {ForwardedRef, MutableRefObject, RefAttributes, RefObject} from 'react'; import React, {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import type { LayoutChangeEvent, @@ -14,7 +14,7 @@ import type { import {DeviceEventEmitter, findNodeHandle, InteractionManager, NativeModules, View} from 'react-native'; import {useFocusedInputHandler} from 'react-native-keyboard-controller'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import {useAnimatedRef, useSharedValue} from 'react-native-reanimated'; import type {Emoji} from '@assets/emojis/types'; import type {FileObject} from '@components/AttachmentModal'; @@ -65,95 +65,113 @@ type SyncSelection = { type NewlyAddedChars = {startIndex: number; endIndex: number; diff: string}; -type ComposerWithSuggestionsProps = Partial & { - /** Report ID */ - reportID: string; +type ComposerWithSuggestionsOnyxProps = { + /** The parent report actions for the report */ + parentReportActions: OnyxEntry; - /** Callback to focus composer */ - onFocus: () => void; + /** The modal state */ + modal: OnyxEntry; - /** Callback to blur composer */ - onBlur: (event: NativeSyntheticEvent) => void; + /** The preferred skin tone of the user */ + preferredSkinTone: number; - /** Callback when layout of composer changes */ - onLayout?: (event: LayoutChangeEvent) => void; + /** Whether the input is focused */ + editFocused: OnyxEntry; +}; - /** Callback to update the value of the composer */ - onValueChange: (value: string) => void; +type ComposerWithSuggestionsProps = ComposerWithSuggestionsOnyxProps & + Partial & { + /** Report ID */ + reportID: string; - /** Callback when the composer got cleared on the UI thread */ - onCleared?: (text: string) => void; + /** Callback to focus composer */ + onFocus: () => void; - /** Whether the composer is full size */ - isComposerFullSize: boolean; + /** Callback to blur composer */ + onBlur: (event: NativeSyntheticEvent) => void; - /** Whether the menu is visible */ - isMenuVisible: boolean; + /** Callback when layout of composer changes */ + onLayout?: (event: LayoutChangeEvent) => void; - /** The placeholder for the input */ - inputPlaceholder: string; + /** Callback to update the value of the composer */ + onValueChange: (value: string) => void; - /** Function to display a file in a modal */ - displayFileInModal: (file: FileObject) => void; + /** Callback when the composer got cleared on the UI thread */ + onCleared?: (text: string) => void; - /** Whether the user is blocked from concierge */ - isBlockedFromConcierge: boolean; + /** Whether the composer is full size */ + isComposerFullSize: boolean; - /** Whether the input is disabled */ - disabled: boolean; + /** Whether the menu is visible */ + isMenuVisible: boolean; - /** Whether the full composer is available */ - isFullComposerAvailable: boolean; + /** The placeholder for the input */ + inputPlaceholder: string; - /** Function to set whether the full composer is available */ - setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void; + /** Function to display a file in a modal */ + displayFileInModal: (file: FileObject) => void; - /** Function to set whether the comment is empty */ - setIsCommentEmpty: (isCommentEmpty: boolean) => void; + /** Whether the user is blocked from concierge */ + isBlockedFromConcierge: boolean; - /** Function to handle sending a message */ - handleSendMessage: () => void; + /** Whether the input is disabled */ + disabled: boolean; - /** Whether the compose input should show */ - shouldShowComposeInput: OnyxEntry; + /** Whether the full composer is available */ + isFullComposerAvailable: boolean; - /** Function to measure the parent container */ - measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; + /** Function to set whether the full composer is available */ + setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void; - /** Whether the scroll is likely to trigger a layout */ - isScrollLikelyLayoutTriggered: RefObject; + /** Function to set whether the comment is empty */ + setIsCommentEmpty: (isCommentEmpty: boolean) => void; - /** Function to raise the scroll is likely layout triggered */ - raiseIsScrollLikelyLayoutTriggered: () => void; + /** Function to handle sending a message */ + handleSendMessage: () => void; - /** The ref to the suggestions */ - suggestionsRef: React.RefObject; + /** Whether the compose input should show */ + shouldShowComposeInput: OnyxEntry; - /** The ref to the next modal will open */ - isNextModalWillOpenRef: MutableRefObject; + /** Function to measure the parent container */ + measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; - /** Wheater chat is empty */ - isEmptyChat?: boolean; + /** Whether the scroll is likely to trigger a layout */ + isScrollLikelyLayoutTriggered: RefObject; - /** The last report action */ - lastReportAction?: OnyxEntry; + /** Function to raise the scroll is likely layout triggered */ + raiseIsScrollLikelyLayoutTriggered: () => void; - /** Whether to include chronos */ - includeChronos?: boolean; + /** The ref to the suggestions */ + suggestionsRef: React.RefObject; - /** The parent report action ID */ - parentReportActionID?: string; + /** The ref to the next modal will open */ + isNextModalWillOpenRef: MutableRefObject; - /** The parent report ID */ - // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC - parentReportID: string | undefined; + /** Whether the edit is focused */ + editFocused: boolean; - /** Whether report is from group policy */ - isGroupPolicyReport: boolean; + /** Wheater chat is empty */ + isEmptyChat?: boolean; - /** policy ID of the report */ - policyID: string; -}; + /** The last report action */ + lastReportAction?: OnyxEntry; + + /** Whether to include chronos */ + includeChronos?: boolean; + + /** The parent report action ID */ + parentReportActionID?: string; + + /** The parent report ID */ + // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC + parentReportID: string | undefined; + + /** Whether report is from group policy */ + isGroupPolicyReport: boolean; + + /** policy ID of the report */ + policyID: string; + }; type SwitchToCurrentReportProps = { preexistingReportID: string; @@ -205,6 +223,11 @@ const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); */ function ComposerWithSuggestions( { + // Onyx + modal, + preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, + parentReportActions, + // Props: Report reportID, includeChronos, @@ -213,7 +236,6 @@ function ComposerWithSuggestions( parentReportActionID, isGroupPolicyReport, policyID, - parentReportID, // Focus onFocus, @@ -241,17 +263,13 @@ function ComposerWithSuggestions( // Refs suggestionsRef, isNextModalWillOpenRef, + editFocused, // For testing children, }: ComposerWithSuggestionsProps, ref: ForwardedRef, ) { - const [modal] = useOnyx(ONYXKEYS.MODAL); - const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex}); - const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED); - const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {canEvict: false}); - const {isKeyboardShown} = useKeyboardState(); const theme = useTheme(); const styles = useThemeStyles(); @@ -280,7 +298,7 @@ function ComposerWithSuggestions( const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; - const parentReportAction = useMemo(() => parentReportActions?.[parentReportActionID ?? '-1'], [parentReportActionID, parentReportActions]); + const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1']; const shouldAutoFocus = !modal?.isVisible && Modal.areAllModalsHidden() && @@ -572,7 +590,7 @@ function ComposerWithSuggestions( // 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; return !isComposerCoveredUp; - }, [isFocused, isMenuVisible, modal?.isVisible, modal?.willAlertModalBecomeVisible]); + }, [isMenuVisible, modal, isFocused]); const focusComposerOnKeyPress = useCallback( (e: KeyboardEvent) => { @@ -635,7 +653,6 @@ function ComposerWithSuggestions( const prevIsModalVisible = usePrevious(modal?.isVisible); const prevIsFocused = usePrevious(isFocused); - useEffect(() => { if (modal?.isVisible && !prevIsModalVisible) { // eslint-disable-next-line react-compiler/react-compiler, no-param-reassign @@ -659,7 +676,7 @@ function ComposerWithSuggestions( return; } focus(true); - }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, isNextModalWillOpenRef, shouldAutoFocus, modal?.isVisible]); + }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal?.isVisible, isNextModalWillOpenRef, shouldAutoFocus]); useEffect(() => { // Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit @@ -821,6 +838,22 @@ ComposerWithSuggestions.displayName = 'ComposerWithSuggestions'; const ComposerWithSuggestionsWithRef = forwardRef(ComposerWithSuggestions); -export default memo(ComposerWithSuggestionsWithRef); +export default withOnyx, ComposerWithSuggestionsOnyxProps>({ + modal: { + key: ONYXKEYS.MODAL, + }, + preferredSkinTone: { + key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + selector: EmojiUtils.getPreferredSkinToneIndex, + }, + editFocused: { + key: ONYXKEYS.INPUT_FOCUSED, + }, + parentReportActions: { + key: ({parentReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + canEvict: false, + initWithStoredValues: false, + }, +})(memo(ComposerWithSuggestionsWithRef)); export type {ComposerWithSuggestionsProps, ComposerRef}; From 9c5d4cbc4b349476d8c5f8d42b5517c19eee2ab0 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Sat, 26 Oct 2024 00:36:43 +0700 Subject: [PATCH 08/10] fix test --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index aeff0d70054e..9629f85a4fc5 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -298,7 +298,7 @@ function ComposerWithSuggestions( const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; - const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1']; + const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1']; const shouldAutoFocus = !modal?.isVisible && Modal.areAllModalsHidden() && From d8086abc2196876f231d28abf7ebc35d341e1531 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Sat, 26 Oct 2024 00:36:55 +0700 Subject: [PATCH 09/10] fix lint --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 9629f85a4fc5..aeff0d70054e 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -298,7 +298,7 @@ function ComposerWithSuggestions( const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; - const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1']; + const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1']; const shouldAutoFocus = !modal?.isVisible && Modal.areAllModalsHidden() && From 9a36caceba0456d9661f20202a4022a1a49806ef Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Sat, 26 Oct 2024 03:01:38 +0700 Subject: [PATCH 10/10] use isTaskReport function --- .../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 aeff0d70054e..9ae2eaa2eaad 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -14,7 +14,7 @@ import type { import {DeviceEventEmitter, findNodeHandle, InteractionManager, NativeModules, View} from 'react-native'; import {useFocusedInputHandler} from 'react-native-keyboard-controller'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import {useAnimatedRef, useSharedValue} from 'react-native-reanimated'; import type {Emoji} from '@assets/emojis/types'; import type {FileObject} from '@components/AttachmentModal'; @@ -298,13 +298,13 @@ function ComposerWithSuggestions( const {shouldUseNarrowLayout} = useResponsiveLayout(); const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES; + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1']; const shouldAutoFocus = !modal?.isVisible && Modal.areAllModalsHidden() && isFocused && - (shouldFocusInputOnScreenFocus || - (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction) && !ReportActionsUtils.isCreatedTaskReportAction(parentReportAction))) && + (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction) && !ReportUtils.isTaskReport(report))) && shouldShowComposeInput; const valueRef = useRef(value);