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

Fix keyboard issues in add attachment flow #15337

Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
44ff6f6
Fix keyboard issues in add attachment flow
priyeshshah11 Feb 22, 2023
17d37d7
fixed refocusing on first popover close bug & mweb refocus after dism…
priyeshshah11 Feb 22, 2023
7a9ee23
Added the blur to fix android keyboard not opening issue
priyeshshah11 Feb 22, 2023
7771fb0
fixed android keyboard not opening bug
priyeshshah11 Feb 24, 2023
f00881b
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Feb 24, 2023
cdb9402
Revert "Merge branch 'main' of https://github.com/priyeshshah11/App i…
priyeshshah11 Feb 24, 2023
a76e4fb
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Feb 24, 2023
31b0a4a
updated coment for onModalHide prop
priyeshshah11 Feb 25, 2023
8520ae5
added a potential fix for android keyboard not opening issue
priyeshshah11 Feb 28, 2023
48b0278
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Feb 28, 2023
ba17fe7
code cleanup
priyeshshah11 Mar 1, 2023
17c6759
Resolved merge conflicts
priyeshshah11 Mar 1, 2023
6b156eb
fixed lint errors
priyeshshah11 Mar 1, 2023
2906e0f
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Mar 29, 2023
77087b8
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Apr 12, 2023
163bee8
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Apr 12, 2023
ac3bdde
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Apr 17, 2023
b2e4adc
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Apr 27, 2023
bd9794a
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Apr 28, 2023
997253f
removing the setTimeout hack that was added to reopen keyboard on mod…
priyeshshah11 Apr 28, 2023
892bec9
Merge branch 'main' of https://github.com/priyeshshah11/App into fix-…
priyeshshah11 Jul 11, 2023
8d66be0
fixed merge issues
priyeshshah11 Jul 11, 2023
8da22e0
fixed merge issues
priyeshshah11 Jul 11, 2023
ed3a666
fixed lint issues
priyeshshah11 Jul 11, 2023
517e9a9
fixed prettier issues
priyeshshah11 Jul 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ const propTypes = {

/** The types of files that can be selected with this picker. */
type: PropTypes.oneOf([CONST.ATTACHMENT_PICKER_TYPE.FILE, CONST.ATTACHMENT_PICKER_TYPE.IMAGE]),

/** Optional callback to fire when we want to do something on close. */
onClose: PropTypes.func,
};

const defaultProps = {
type: CONST.ATTACHMENT_PICKER_TYPE.FILE,
onClose: () => {},
};

export {
Expand Down
13 changes: 11 additions & 2 deletions src/components/AttachmentPicker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,21 @@ class AttachmentPicker extends React.Component {

// We are stopping the event propagation because triggering the `click()` on the hidden input
// causes the event to unexpectedly bubble up to anything wrapping this component e.g. Pressable
onClick={e => e.stopPropagation()}
onClick={(e) => {
e.stopPropagation();

// We add this focus event listener to call the onClose callback when user does not select any files
// i.e. clicks cancel in the native file picker modal - this is when the app gets the focus back
window.addEventListener('focus', this.onClose, {
once: true, // this removes the listener after running once
});
}}
accept={getAcceptableFileTypes(this.props.type)}
/>
{this.props.children({
openPicker: ({onPicked}) => {
openPicker: ({onPicked, onClose}) => {
this.onPicked = onPicked;
this.onClose = onClose;
this.fileInput.click();
},
})}
Expand Down
16 changes: 14 additions & 2 deletions src/components/AttachmentPicker/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class AttachmentPicker extends Component {

this.state = {
isVisible: false,
onClose: () => {},
};

this.menuItemData = [
Expand Down Expand Up @@ -187,6 +188,8 @@ class AttachmentPicker extends Component {
return new Promise((resolve, reject) => {
imagePickerFunc(getImagePickerOptions(this.props.type), (response) => {
if (response.didCancel) {
this.state.onClose();

// When the user cancelled resolve with no attachment
return resolve();
}
Expand Down Expand Up @@ -237,6 +240,7 @@ class AttachmentPicker extends Component {
showDocumentPicker() {
return RNDocumentPicker.pick(documentPickerOptions).catch((error) => {
if (RNDocumentPicker.isCancel(error)) {
this.state.onClose();
return;
}

Expand Down Expand Up @@ -299,15 +303,23 @@ class AttachmentPicker extends Component {
*/
renderChildren() {
return this.props.children({
openPicker: ({onPicked}) => this.open(onPicked),
openPicker: ({onPicked, onClose}) => {
this.open(onPicked);
if (onClose) {
this.setState({onClose});
}
},
});
}

render() {
return (
<>
<Popover
onClose={this.close}
onClose={() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priyeshshah11 We're using onClose here to trigger the focus which will face the same issue on Android where it fails to open the keyboard. We might want to use use onModalHide here instead.

this.close();
this.state.onClose();
}}
isVisible={this.state.isVisible}
anchorPosition={styles.createMenuPosition}
onModalHide={this.onModalHide}
Expand Down
48 changes: 38 additions & 10 deletions src/components/Modal/index.ios.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
import React from 'react';
import React, {useState, useEffect} from 'react';
import {Keyboard} from 'react-native';
import compose from '../../libs/compose';
import withKeyboardState from '../withKeyboardState';
import withWindowDimensions from '../withWindowDimensions';
import BaseModal from './BaseModal';
import {propTypes, defaultProps} from './modalPropTypes';

const Modal = props => (
<BaseModal
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
>
{props.children}
</BaseModal>
);
const Modal = (props) => {
const [isVisible, setIsVisible] = useState(false);

// This closes the keyboard before opening the modal to avoid the issue where iOS automatically reopens
// the keyboard if it was already open before any modals were opened. We manage the keyboard behaviour
// explicitly at other places in the app to be consistent across all platforms
useEffect(() => {
if (!props.isVisible) {
setIsVisible(false);
} else if (props.isKeyboardShown) {
const keyboardListener = Keyboard.addListener('keyboardDidHide', () => {
setIsVisible(true);
keyboardListener.remove();
});
Keyboard.dismiss();
} else {
setIsVisible(true);
}
}, [props.isVisible, props.isKeyboardShown]);

return (
<BaseModal
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
isVisible={isVisible}
>
{props.children}
</BaseModal>
);
};

Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;
Modal.displayName = 'Modal';
export default withWindowDimensions(Modal);
export default compose(
withWindowDimensions,
withKeyboardState,
)(Modal);
41 changes: 38 additions & 3 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,15 @@ class ReportActionCompose extends React.Component {
this.getInputPlaceholder = this.getInputPlaceholder.bind(this);
this.getIOUOptions = this.getIOUOptions.bind(this);
this.addAttachment = this.addAttachment.bind(this);
this.finishAddAttachmentFlow = this.finishAddAttachmentFlow.bind(this);
this.comment = props.comment;

// React Native will retain focus on an input for native devices but web/mWeb behave differently so we have some focus management
// code that will refocus the compose input after a user closes a modal or some other actions, see usage of ReportActionComposeFocusManager
this.willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutside();

this.state = {
isInAddAttachmentFlow: false,
isFocused: this.willBlurTextInputOnTapOutside && !this.props.modal.isVisible && !this.props.modal.willAlertModalBecomeVisible,
isFullComposerAvailable: props.isComposerFullSize,
textInputShouldClear: false,
Expand Down Expand Up @@ -179,7 +181,7 @@ class ReportActionCompose extends React.Component {
// We want to focus or refocus the input when a modal has been closed and the underlying screen is focused.
// We avoid doing this on native platforms since the software keyboard popping
// open creates a jarring and broken UX.
if (this.willBlurTextInputOnTapOutside && this.props.isFocused
if (this.willBlurTextInputOnTapOutside && this.props.isFocused && !this.state.isInAddAttachmentFlow
&& prevProps.modal.isVisible && !this.props.modal.isVisible) {
this.focus();
}
Expand Down Expand Up @@ -302,6 +304,15 @@ class ReportActionCompose extends React.Component {
this.setState({maxLines});
}

/**
* Set isInAddAttachmentFlow state
*
* @param {Boolean} value
*/
setIsInAddAttachmentFlow(value) {
this.setState({isInAddAttachmentFlow: value});
}

isEmptyChat() {
return _.size(this.props.reportActions) === 1;
}
Expand Down Expand Up @@ -473,6 +484,17 @@ class ReportActionCompose extends React.Component {
this.setTextInputShouldClear(false);
}

/**
* Set the focus back to the composer
*/
finishAddAttachmentFlow() {
this.setIsInAddAttachmentFlow(false);

// Explicitly focus the composer from here as componentDidUpdate does not do that in native
// because willBlurTextInputOnTapOutside is false on native which doesn't let it focus
this.focus(true);
}

/**
* Add a new comment to this chat
*
Expand Down Expand Up @@ -535,6 +557,7 @@ class ReportActionCompose extends React.Component {
]}
>
<AttachmentModal
onModalHide={this.finishAddAttachmentFlow}
headerTitle={this.props.translate('reportActionCompose.sendAttachment')}
onConfirm={this.addAttachment}
>
Expand Down Expand Up @@ -592,6 +615,10 @@ class ReportActionCompose extends React.Component {

// Drop focus to avoid blue focus ring.
this.actionButton.blur();

// we blur the input here to avoid an android specific issue where the keyboard
// doesn't open when we re-focus the input due to it never losing focus
this.textInput.blur();
this.setMenuVisibility(true);
}}
style={styles.composerSizeButton}
Expand All @@ -605,16 +632,24 @@ class ReportActionCompose extends React.Component {
<PopoverMenu
animationInTiming={CONST.ANIMATION_IN_TIMING}
isVisible={this.state.isMenuVisible}
onClose={() => this.setMenuVisibility(false)}
onItemSelected={() => this.setMenuVisibility(false)}
onClose={() => {
this.setMenuVisibility(false);
this.finishAddAttachmentFlow();
}}
onItemSelected={() => {
this.setMenuVisibility(false);
this.setIsInAddAttachmentFlow(false);
}}
anchorPosition={styles.createMenuPositionReportActionCompose}
menuItems={[...this.getIOUOptions(reportParticipants),
{
icon: Expensicons.Paperclip,
text: this.props.translate('reportActionCompose.addAttachment'),
onSelected: () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priyeshshah11 we need to add the this.focus(true); inside onClose of PopoverMenu as well, otherwise if the user clicks + and then click outside, it will not focus the input

this.setIsInAddAttachmentFlow(true);
openPicker({
onPicked: displayFileInModal,
onClose: this.finishAddAttachmentFlow,
});
},
},
Expand Down