Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set the focus on Edit Box after edit action. #3441

Merged
merged 10 commits into from
Jun 11, 2021
8 changes: 6 additions & 2 deletions src/components/Modal/BaseModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ class BaseModal extends PureComponent {
*/
hideModalAndRemoveEventListeners() {
this.unsubscribeFromKeyEvents();
setModalVisibility(false);
if (this.props.shouldSetModalVisibility) {
setModalVisibility(false);
}
this.props.onModalHide();
}

Expand Down Expand Up @@ -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}
Expand Down
4 changes: 4 additions & 0 deletions src/components/Modal/ModalPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -49,6 +52,7 @@ const propTypes = {
};

const defaultProps = {
shouldSetModalVisibility: true,
onSubmit: null,
type: '',
onModalHide: () => {},
Expand Down
6 changes: 3 additions & 3 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,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: () => {
Expand Down
37 changes: 37 additions & 0 deletions src/libs/ReportActionComposeFocusManager.js
Original file line number Diff line number Diff line change
@@ -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,
};
5 changes: 4 additions & 1 deletion src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -150,6 +151,7 @@ class ReportActionCompose extends React.Component {
}

componentDidMount() {
ReportActionComposeFocusManager.onComposerFocus(this.focus);
Dimensions.addEventListener('change', this.measureEmojiPopoverAnchorPosition);
}

Expand All @@ -165,6 +167,7 @@ class ReportActionCompose extends React.Component {
}

componentWillUnmount() {
ReportActionComposeFocusManager.clear();
Dimensions.removeEventListener('change', this.measureEmojiPopoverAnchorPosition);
}

Expand Down Expand Up @@ -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(() => {
Expand Down
27 changes: 17 additions & 10 deletions src/pages/home/report/ReportActionContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -87,7 +88,7 @@ class ReportActionContextMenu extends React.Component {
} else {
Clipboard.setString(html);
}
this.hidePopover(true);
this.hidePopover(true, ReportActionComposeFocusManager.focus);
},
},

Expand All @@ -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);
},
},

Expand All @@ -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() : '',
);
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved

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}),
},
];

Expand Down Expand Up @@ -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() {
Expand Down
9 changes: 8 additions & 1 deletion src/pages/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class ReportActionItem extends Component {
constructor(props) {
super(props);

this.onPopoverHide = () => {};
this.state = {
isPopoverVisible: false,
cursorPosition: {
Expand Down Expand Up @@ -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});
}

Expand Down Expand Up @@ -268,10 +273,12 @@ class ReportActionItem extends Component {
<PopoverWithMeasuredContent
isVisible={this.state.isPopoverVisible}
onClose={this.hidePopover}
onModalHide={this.onPopoverHide}
anchorPosition={this.state.popoverAnchorPosition}
animationIn="fadeIn"
animationOutTiming={1}
measureContent={this.measureContent}
shouldSetModalVisibility={false}
>
<ReportActionContextMenu
isVisible
Expand Down
2 changes: 2 additions & 0 deletions src/pages/home/report/ReportActionItemMessageEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {scrollToIndex} from '../../../libs/ReportScrollManager';
import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import Button from '../../../components/Button';
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';

const propTypes = {
/** All the data of the action */
Expand Down Expand Up @@ -73,6 +74,7 @@ class ReportActionItemMessageEdit extends React.Component {
deleteDraft() {
saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, '');
toggleReportActionComposeView(true, this.props.isSmallScreenWidth);
ReportActionComposeFocusManager.focus();
}

/**
Expand Down