From 5c1aebc8c79a4bfad1668c5c0379f2c4f9557a1f Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 20 Jul 2023 09:58:43 +0200 Subject: [PATCH 1/8] Add delay to focus in ReportActionItemMessageEdit --- .../report/ReportActionItemMessageEdit.js | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index d45f0442f3f7..b74c5451b804 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -270,6 +270,25 @@ function ReportActionItemMessageEdit(props) { [deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft], ); + /** + * Focus the composer text input + */ + const focus = React.useCallback(() => { + // There could be other animations running while we trigger manual focus. + // This prevents focus from making those animations janky. + InteractionManager.runAfterInteractions(() => { + if (!textInputRef.current) { + return; + } + + // Keyboard is not opened after Emoji Picker is closed + // SetTimeout is used as a workaround + // https://github.com/react-native-modal/react-native-modal/issues/114 + // We carefully choose a delay. 100ms is found enough for keyboard to open. + setTimeout(() => textInputRef.current.focus(), 100); + }); + }, []); + return ( <> @@ -345,7 +364,7 @@ function ReportActionItemMessageEdit(props) { InteractionManager.runAfterInteractions(() => textInputRef.current.focus())} + onModalHide={focus} onEmojiSelected={addEmojiToTextBox} nativeID={emojiButtonID} reportAction={props.action} From aa137654b4944e1c0456e2a7454971ab55b11460 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 20 Jul 2023 10:26:04 +0200 Subject: [PATCH 2/8] Set isFocused when focusing --- src/pages/home/report/ReportActionItemMessageEdit.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index b74c5451b804..6092dca60df6 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -285,7 +285,10 @@ function ReportActionItemMessageEdit(props) { // SetTimeout is used as a workaround // https://github.com/react-native-modal/react-native-modal/issues/114 // We carefully choose a delay. 100ms is found enough for keyboard to open. - setTimeout(() => textInputRef.current.focus(), 100); + setTimeout(() => { + setIsFocused(true); + textInputRef.current.focus(); + }, 100); }); }, []); From 186c509742d7aa8b08d3bcfc6b72150282aa3dca Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Fri, 21 Jul 2023 11:53:52 +0200 Subject: [PATCH 3/8] Extract function to the lib --- src/libs/focusWithDelay.js | 35 +++++++++++++++++++ src/pages/home/report/ReportActionCompose.js | 31 +++------------- .../report/ReportActionItemMessageEdit.js | 25 ++++--------- 3 files changed, 45 insertions(+), 46 deletions(-) create mode 100644 src/libs/focusWithDelay.js diff --git a/src/libs/focusWithDelay.js b/src/libs/focusWithDelay.js new file mode 100644 index 000000000000..5371d8670eb8 --- /dev/null +++ b/src/libs/focusWithDelay.js @@ -0,0 +1,35 @@ +import {InteractionManager} from 'react-native'; + +/** + * Create a function that focuses a text input. + * @param {Object} textInput the text input to focus + * @returns {Function} a function that focuses the text input with a configurable delay + */ + +function createFocusFunction(textInput) { + /** + * Focus the text input + * @param {Boolean} [shouldelay=false] Impose delay before focusing the text input + */ + return function focus(shouldelay = false) { + // There could be other animations running while we trigger manual focus. + // This prevents focus from making those animations janky. + InteractionManager.runAfterInteractions(() => { + if (!textInput) { + return; + } + + if (!shouldelay) { + textInput.focus(); + } else { + // Keyboard is not opened after Emoji Picker is closed + // SetTimeout is used as a workaround + // https://github.com/react-native-modal/react-native-modal/issues/114 + // We carefully choose a delay. 100ms is found enough for keyboard to open. + setTimeout(() => textInput.focus(), 100); + } + }); + }; +} + +export default createFocusFunction; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 8155f09b2aac..4fec151ea4f9 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -1,9 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {View, InteractionManager, LayoutAnimation, NativeModules, findNodeHandle} from 'react-native'; +import {View, LayoutAnimation, NativeModules, findNodeHandle} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; +import createFocusFunction from '../../../libs/focusWithDelay'; import styles from '../../../styles/styles'; import themeColors from '../../../styles/themes/default'; import Composer from '../../../components/Composer'; @@ -173,7 +174,7 @@ class ReportActionCompose extends React.Component { this.submitForm = this.submitForm.bind(this); this.setIsFocused = this.setIsFocused.bind(this); this.setIsFullComposerAvailable = this.setIsFullComposerAvailable.bind(this); - this.focus = this.focus.bind(this); + this.focus = createFocusFunction(this.textInput).bind(this); this.replaceSelectionWithText = this.replaceSelectionWithText.bind(this); this.focusComposerOnKeyPress = this.focusComposerOnKeyPress.bind(this); this.checkComposerVisibility = this.checkComposerVisibility.bind(this); @@ -367,6 +368,7 @@ class ReportActionCompose extends React.Component { setTextInputRef(el) { ReportActionComposeFocusManager.composerRef.current = el; this.textInput = el; + this.focus = createFocusFunction(this.textInput).bind(this); } /** @@ -730,31 +732,6 @@ class ReportActionCompose extends React.Component { this.replaceSelectionWithText(e.key, false); } - /** - * Focus the composer text input - * @param {Boolean} [shouldelay=false] Impose delay before focusing the composer - * @memberof ReportActionCompose - */ - focus(shouldelay = false) { - // There could be other animations running while we trigger manual focus. - // This prevents focus from making those animations janky. - InteractionManager.runAfterInteractions(() => { - if (!this.textInput) { - return; - } - - if (!shouldelay) { - this.textInput.focus(); - } else { - // Keyboard is not opened after Emoji Picker is closed - // SetTimeout is used as a workaround - // https://github.com/react-native-modal/react-native-modal/issues/114 - // We carefully choose a delay. 100ms is found enough for keyboard to open. - setTimeout(() => this.textInput.focus(), 100); - } - }); - } - /** * Save our report comment in Onyx. We debounce this method in the constructor so that it's not called too often * to update Onyx and re-render this component. diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 6092dca60df6..441696833372 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -35,6 +35,7 @@ import useKeyboardState from '../../../hooks/useKeyboardState'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; import useReportScrollManager from '../../../hooks/useReportScrollManager'; import * as EmojiPickerAction from '../../../libs/actions/EmojiPickerAction'; +import focusAfterDelay from '../../../libs/focusWithDelay'; const propTypes = { /** All the data of the action */ @@ -273,24 +274,7 @@ function ReportActionItemMessageEdit(props) { /** * Focus the composer text input */ - const focus = React.useCallback(() => { - // There could be other animations running while we trigger manual focus. - // This prevents focus from making those animations janky. - InteractionManager.runAfterInteractions(() => { - if (!textInputRef.current) { - return; - } - - // Keyboard is not opened after Emoji Picker is closed - // SetTimeout is used as a workaround - // https://github.com/react-native-modal/react-native-modal/issues/114 - // We carefully choose a delay. 100ms is found enough for keyboard to open. - setTimeout(() => { - setIsFocused(true); - textInputRef.current.focus(); - }, 100); - }); - }, []); + const focus = focusAfterDelay(textInputRef.current); return ( <> @@ -367,7 +351,10 @@ function ReportActionItemMessageEdit(props) { { + setIsFocused(true); + focus(true); + }} onEmojiSelected={addEmojiToTextBox} nativeID={emojiButtonID} reportAction={props.action} From ecaa8bc4387396fddba6d048c6c9b8d31177702b Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:03:56 +0200 Subject: [PATCH 4/8] Refactor --- src/libs/focusWithDelay.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/focusWithDelay.js b/src/libs/focusWithDelay.js index 5371d8670eb8..4890e346b4f7 100644 --- a/src/libs/focusWithDelay.js +++ b/src/libs/focusWithDelay.js @@ -6,12 +6,12 @@ import {InteractionManager} from 'react-native'; * @returns {Function} a function that focuses the text input with a configurable delay */ -function createFocusFunction(textInput) { +function focusWithDelay(textInput) { /** * Focus the text input * @param {Boolean} [shouldelay=false] Impose delay before focusing the text input */ - return function focus(shouldelay = false) { + return (shouldelay = false) => { // There could be other animations running while we trigger manual focus. // This prevents focus from making those animations janky. InteractionManager.runAfterInteractions(() => { @@ -32,4 +32,4 @@ function createFocusFunction(textInput) { }; } -export default createFocusFunction; +export default focusWithDelay; From e6e8662b62846e28617585bb7c1e89143296c6c8 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Fri, 21 Jul 2023 14:12:21 +0200 Subject: [PATCH 5/8] Seperate files to platforms --- src/libs/focusWithDelay.js | 35 -------------------- src/libs/focusWithDelay/focusWithDelay.js | 40 +++++++++++++++++++++++ src/libs/focusWithDelay/index.js | 7 ++++ src/libs/focusWithDelay/index.native.js | 6 ++++ 4 files changed, 53 insertions(+), 35 deletions(-) delete mode 100644 src/libs/focusWithDelay.js create mode 100644 src/libs/focusWithDelay/focusWithDelay.js create mode 100644 src/libs/focusWithDelay/index.js create mode 100644 src/libs/focusWithDelay/index.native.js diff --git a/src/libs/focusWithDelay.js b/src/libs/focusWithDelay.js deleted file mode 100644 index 4890e346b4f7..000000000000 --- a/src/libs/focusWithDelay.js +++ /dev/null @@ -1,35 +0,0 @@ -import {InteractionManager} from 'react-native'; - -/** - * Create a function that focuses a text input. - * @param {Object} textInput the text input to focus - * @returns {Function} a function that focuses the text input with a configurable delay - */ - -function focusWithDelay(textInput) { - /** - * Focus the text input - * @param {Boolean} [shouldelay=false] Impose delay before focusing the text input - */ - return (shouldelay = false) => { - // There could be other animations running while we trigger manual focus. - // This prevents focus from making those animations janky. - InteractionManager.runAfterInteractions(() => { - if (!textInput) { - return; - } - - if (!shouldelay) { - textInput.focus(); - } else { - // Keyboard is not opened after Emoji Picker is closed - // SetTimeout is used as a workaround - // https://github.com/react-native-modal/react-native-modal/issues/114 - // We carefully choose a delay. 100ms is found enough for keyboard to open. - setTimeout(() => textInput.focus(), 100); - } - }); - }; -} - -export default focusWithDelay; diff --git a/src/libs/focusWithDelay/focusWithDelay.js b/src/libs/focusWithDelay/focusWithDelay.js new file mode 100644 index 000000000000..8d66b4324678 --- /dev/null +++ b/src/libs/focusWithDelay/focusWithDelay.js @@ -0,0 +1,40 @@ +import {InteractionManager} from 'react-native'; + +/** + * Creates a function that can be used to focus a text input + * @param {Boolean} disableDelay whether to force focus without a delay (on web and desktop) + * @returns {Function} a focusWithDelay function + */ +function focusWithDelay(disableDelay = false) { + /** + * Create a function that focuses a text input. + * @param {Object} textInput the text input to focus + * @returns {Function} a function that focuses the text input with a configurable delay + */ + return (textInput) => + /** + * Focus the text input + * @param {Boolean} [shouldelay=false] Impose delay before focusing the text input + */ + (shouldelay = false) => { + // There could be other animations running while we trigger manual focus. + // This prevents focus from making those animations janky. + InteractionManager.runAfterInteractions(() => { + if (!textInput) { + return; + } + + if (disableDelay || !shouldelay) { + textInput.focus(); + } else { + // Keyboard is not opened after Emoji Picker is closed + // SetTimeout is used as a workaround + // https://github.com/react-native-modal/react-native-modal/issues/114 + // We carefully choose a delay. 100ms is found enough for keyboard to open. + setTimeout(() => textInput.focus(), 100); + } + }); + }; +} + +export default focusWithDelay; diff --git a/src/libs/focusWithDelay/index.js b/src/libs/focusWithDelay/index.js new file mode 100644 index 000000000000..cba991657452 --- /dev/null +++ b/src/libs/focusWithDelay/index.js @@ -0,0 +1,7 @@ +import focusWithDelay from './focusWithDelay'; + +/** + * We pass true to disable the delay on the web because it doesn't display the keyboard + * so we don't have to use the hack (explained in the focusWithDelay.js file). + */ +export default focusWithDelay(true); diff --git a/src/libs/focusWithDelay/index.native.js b/src/libs/focusWithDelay/index.native.js new file mode 100644 index 000000000000..27fb19fe1570 --- /dev/null +++ b/src/libs/focusWithDelay/index.native.js @@ -0,0 +1,6 @@ +import focusWithDelay from './focusWithDelay'; + +/** + * We enable the delay on native to display the keyboard correctly + */ +export default focusWithDelay(false); From f600d338ac1778ee7bc8383d691b04f720783446 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:17:07 +0200 Subject: [PATCH 6/8] Address review --- src/libs/focusWithDelay/focusWithDelay.js | 6 +++--- src/libs/focusWithDelay/index.js | 4 ++-- src/pages/home/report/ReportActionCompose.js | 6 +++--- src/pages/home/report/ReportActionItemMessageEdit.js | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/libs/focusWithDelay/focusWithDelay.js b/src/libs/focusWithDelay/focusWithDelay.js index 8d66b4324678..143d5dd12430 100644 --- a/src/libs/focusWithDelay/focusWithDelay.js +++ b/src/libs/focusWithDelay/focusWithDelay.js @@ -14,9 +14,9 @@ function focusWithDelay(disableDelay = false) { return (textInput) => /** * Focus the text input - * @param {Boolean} [shouldelay=false] Impose delay before focusing the text input + * @param {Boolean} [shouldDelay=false] Impose delay before focusing the text input */ - (shouldelay = false) => { + (shouldDelay = false) => { // There could be other animations running while we trigger manual focus. // This prevents focus from making those animations janky. InteractionManager.runAfterInteractions(() => { @@ -24,7 +24,7 @@ function focusWithDelay(disableDelay = false) { return; } - if (disableDelay || !shouldelay) { + if (disableDelay || !shouldDelay) { textInput.focus(); } else { // Keyboard is not opened after Emoji Picker is closed diff --git a/src/libs/focusWithDelay/index.js b/src/libs/focusWithDelay/index.js index cba991657452..faeb43147c5c 100644 --- a/src/libs/focusWithDelay/index.js +++ b/src/libs/focusWithDelay/index.js @@ -1,7 +1,7 @@ import focusWithDelay from './focusWithDelay'; /** - * We pass true to disable the delay on the web because it doesn't display the keyboard - * so we don't have to use the hack (explained in the focusWithDelay.js file). + * We pass true to disable the delay on the web because it doesn't require + * using the workaround (explained in the focusWithDelay.js file). */ export default focusWithDelay(true); diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index ddbfee220534..45e16cb02420 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -6,7 +6,7 @@ import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; -import createFocusFunction from '../../../libs/focusWithDelay'; +import focusWithDelay from '../../../libs/focusWithDelay'; import styles from '../../../styles/styles'; import themeColors from '../../../styles/themes/default'; import Composer from '../../../components/Composer'; @@ -178,7 +178,7 @@ class ReportActionCompose extends React.Component { this.submitForm = this.submitForm.bind(this); this.setIsFocused = this.setIsFocused.bind(this); this.setIsFullComposerAvailable = this.setIsFullComposerAvailable.bind(this); - this.focus = createFocusFunction(this.textInput).bind(this); + this.focus = focusWithDelay(this.textInput).bind(this); this.replaceSelectionWithText = this.replaceSelectionWithText.bind(this); this.focusComposerOnKeyPress = this.focusComposerOnKeyPress.bind(this); this.checkComposerVisibility = this.checkComposerVisibility.bind(this); @@ -380,7 +380,7 @@ class ReportActionCompose extends React.Component { if (_.isFunction(this.props.animatedRef)) { this.props.animatedRef(el); } - this.focus = createFocusFunction(this.textInput).bind(this); + this.focus = focusWithDelay(this.textInput).bind(this); } /** diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 5feab890038c..fb404c11db33 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -36,7 +36,7 @@ import useKeyboardState from '../../../hooks/useKeyboardState'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; import useReportScrollManager from '../../../hooks/useReportScrollManager'; import * as EmojiPickerAction from '../../../libs/actions/EmojiPickerAction'; -import focusAfterDelay from '../../../libs/focusWithDelay'; +import focusWithDelay from '../../../libs/focusWithDelay'; const propTypes = { /** All the data of the action */ @@ -275,7 +275,7 @@ function ReportActionItemMessageEdit(props) { /** * Focus the composer text input */ - const focus = focusAfterDelay(textInputRef.current); + const focus = focusWithDelay(textInputRef.current); return ( <> From 6586dbf59e7a61c1da489ef2cb8f08d1fbbca032 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 8 Aug 2023 09:49:34 +0200 Subject: [PATCH 7/8] Remove redundant line --- src/pages/home/report/ReportActionCompose.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index e6dca36d9d00..7b349ac462f3 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -178,7 +178,6 @@ class ReportActionCompose extends React.Component { this.submitForm = this.submitForm.bind(this); this.setIsFocused = this.setIsFocused.bind(this); this.setIsFullComposerAvailable = this.setIsFullComposerAvailable.bind(this); - this.focus = focusWithDelay(this.textInput).bind(this); this.replaceSelectionWithText = this.replaceSelectionWithText.bind(this); this.focusComposerOnKeyPress = this.focusComposerOnKeyPress.bind(this); this.checkComposerVisibility = this.checkComposerVisibility.bind(this); From 4d721e0aa54af9c5e2a01a6b7e0275d98a102138 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:21:21 +0200 Subject: [PATCH 8/8] Revert "Remove redundant line" This reverts commit 6586dbf59e7a61c1da489ef2cb8f08d1fbbca032. --- src/pages/home/report/ReportActionCompose.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 7b349ac462f3..e6dca36d9d00 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -178,6 +178,7 @@ class ReportActionCompose extends React.Component { this.submitForm = this.submitForm.bind(this); this.setIsFocused = this.setIsFocused.bind(this); this.setIsFullComposerAvailable = this.setIsFullComposerAvailable.bind(this); + this.focus = focusWithDelay(this.textInput).bind(this); this.replaceSelectionWithText = this.replaceSelectionWithText.bind(this); this.focusComposerOnKeyPress = this.focusComposerOnKeyPress.bind(this); this.checkComposerVisibility = this.checkComposerVisibility.bind(this);