diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js index f0b5fb6a1a7a..56604e48e119 100644 --- a/src/components/Modal/BaseModal.js +++ b/src/components/Modal/BaseModal.js @@ -28,7 +28,9 @@ class BaseModal extends PureComponent { */ hideModalAndRemoveEventListeners() { this.unsubscribeFromKeyEvents(); - setModalVisibility(false); + if (this.props.shouldSetModalVisibility) { + setModalVisibility(false); + } this.props.onModalHide(); } @@ -79,7 +81,9 @@ class BaseModal extends PureComponent { onBackButtonPress={this.props.onClose} onModalShow={() => { this.subscribeToKeyEvents(); - setModalVisibility(true); + if (this.props.shouldSetModalVisibility) { + setModalVisibility(true); + } this.props.onModalShow(); }} onModalHide={this.hideModalAndRemoveEventListeners} diff --git a/src/components/Modal/ModalPropTypes.js b/src/components/Modal/ModalPropTypes.js index 6e6f7dd850d7..8da6403308a3 100644 --- a/src/components/Modal/ModalPropTypes.js +++ b/src/components/Modal/ModalPropTypes.js @@ -4,6 +4,9 @@ import CONST from '../../CONST'; import {windowDimensionsPropTypes} from '../withWindowDimensions'; const propTypes = { + /** Should we announce the Modal visibility changes? */ + shouldSetModalVisibility: PropTypes.bool, + /** Callback method fired when the user requests to close the modal */ onClose: PropTypes.func.isRequired, @@ -49,6 +52,7 @@ const propTypes = { }; const defaultProps = { + shouldSetModalVisibility: true, onSubmit: null, type: '', onModalHide: () => {}, diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 08f0efbf6c54..e9d45ba58a1b 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -73,11 +73,11 @@ Onyx.connect({ const RootStack = createCustomModalStackNavigator(); // We want to delay the re-rendering for components(e.g. ReportActionCompose) -// that depends on modal visibility until Modal is completely closed or its transition has ended -// When modal screen is focused and animation transition is ended, update modal visibility in Onyx +// that depends on modal visibility until Modal is completely closed and its focused +// When modal screen is focused, update modal visibility in Onyx // https://reactnavigation.org/docs/navigation-events/ const modalScreenListeners = { - transitionEnd: () => { + focus: () => { setModalVisibility(true); }, beforeRemove: () => { diff --git a/src/libs/ReportActionComposeFocusManager.js b/src/libs/ReportActionComposeFocusManager.js new file mode 100644 index 000000000000..9edd46c798fe --- /dev/null +++ b/src/libs/ReportActionComposeFocusManager.js @@ -0,0 +1,37 @@ +import _ from 'underscore'; + +let focusCallback = null; + +/** + * Register a callback to be called when focus is requested. + * Typical uses of this would be call the focus on the ReportActionComposer. + * + * @param {Function} callback callback to register + */ +function onComposerFocus(callback) { + focusCallback = callback; +} + +/** + * Request focus on the ReportActionComposer + * + */ +function focus() { + if (_.isFunction(focusCallback)) { + focusCallback(); + } +} + +/** + * Clear the registered focus callback + * + */ +function clear() { + focusCallback = null; +} + +export default { + onComposerFocus, + focus, + clear, +}; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index af29d8387657..8130c85ed813 100755 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -51,6 +51,7 @@ import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import ReportActionPropTypes from './ReportActionPropTypes'; import {canEditReportAction} from '../../../libs/reportUtils'; +import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; const propTypes = { /** Beta features list */ @@ -150,6 +151,7 @@ class ReportActionCompose extends React.Component { } componentDidMount() { + ReportActionComposeFocusManager.onComposerFocus(this.focus); Dimensions.addEventListener('change', this.measureEmojiPopoverAnchorPosition); } @@ -165,6 +167,7 @@ class ReportActionCompose extends React.Component { } componentWillUnmount() { + ReportActionComposeFocusManager.clear(); Dimensions.removeEventListener('change', this.measureEmojiPopoverAnchorPosition); } @@ -203,7 +206,7 @@ class ReportActionCompose extends React.Component { * Focus the composer text input */ focus() { - if (this.textInput) { + if (this.shouldFocusInputOnScreenFocus && this.props.isFocused && this.textInput) { // There could be other animations running while we trigger manual focus. // This prevents focus from making those animations janky. InteractionManager.runAfterInteractions(() => { diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index bd4ee0bc481d..c38b8d7db847 100755 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -18,6 +18,7 @@ import compose from '../../../libs/compose'; import {isReportMessageAttachment, canEditReportAction} from '../../../libs/reportUtils'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import ConfirmModal from '../../../components/ConfirmModal'; +import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; const propTypes = { /** The ID of the report this report action is attached to. */ @@ -87,7 +88,7 @@ class ReportActionContextMenu extends React.Component { } else { Clipboard.setString(html); } - this.hidePopover(true); + this.hidePopover(true, ReportActionComposeFocusManager.focus); }, }, @@ -106,7 +107,7 @@ class ReportActionContextMenu extends React.Component { onPress: () => { updateLastReadActionID(this.props.reportID, this.props.reportAction.sequenceNumber); setNewMarkerPosition(this.props.reportID, this.props.reportAction.sequenceNumber); - this.hidePopover(true); + this.hidePopover(true, ReportActionComposeFocusManager.focus); }, }, @@ -115,21 +116,26 @@ class ReportActionContextMenu extends React.Component { icon: Pencil, shouldShow: () => canEditReportAction(this.props.reportAction), onPress: () => { - this.hidePopover(); - saveReportActionDraft( + const editAction = () => saveReportActionDraft( this.props.reportID, this.props.reportAction.reportActionID, _.isEmpty(this.props.draftMessage) ? this.getActionText() : '', ); + + if (this.props.isMini) { + // No popover to hide, call editAction immediately + editAction(); + } else { + // Hide popover, then call editAction + this.hidePopover(false, editAction); + } }, }, { text: this.props.translate('reportActionContextMenu.deleteComment'), icon: Trashcan, shouldShow: () => canEditReportAction(this.props.reportAction), - onPress: () => { - this.setState({isDeleteCommentConfirmModalVisible: true}); - }, + onPress: () => this.setState({isDeleteCommentConfirmModalVisible: true}), }, ]; @@ -165,14 +171,15 @@ class ReportActionContextMenu extends React.Component { * Hides the popover menu with an optional delay * * @param {Boolean} shouldDelay whether the menu should close after a delay + * @param {Function} [onHideCallback=() => {}] Callback to be called after Popover Menu is hidden * @memberof ReportActionContextMenu */ - hidePopover(shouldDelay) { + hidePopover(shouldDelay, onHideCallback = () => {}) { if (!shouldDelay) { - this.props.hidePopover(); + this.props.hidePopover(onHideCallback); return; } - setTimeout(this.props.hidePopover, 800); + setTimeout(() => this.props.hidePopover(onHideCallback), 800); } render() { diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index f71726ec1cae..9b8b7d6d0947 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -61,6 +61,7 @@ class ReportActionItem extends Component { constructor(props) { super(props); + this.onPopoverHide = () => {}; this.state = { isPopoverVisible: false, cursorPosition: { @@ -173,8 +174,12 @@ class ReportActionItem extends Component { /** * Hide the ReportActionContextMenu modal popover. + * @param {Function} onHideCallback Callback to be called after popover is completely hidden */ - hidePopover() { + hidePopover(onHideCallback) { + if (_.isFunction(onHideCallback)) { + this.onPopoverHide = onHideCallback; + } this.setState({isPopoverVisible: false}); } @@ -268,10 +273,12 @@ class ReportActionItem extends Component {