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 all 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 once the modal is hidden. */
onModalHide: PropTypes.func,
};

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

export {propTypes, defaultProps};
14 changes: 12 additions & 2 deletions src/components/AttachmentPicker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function getAcceptableFileTypes(type) {
function AttachmentPicker(props) {
const fileInput = useRef();
const onPicked = useRef();
const onModalHide = useRef();
return (
<>
<input
Expand All @@ -51,12 +52,21 @@ function AttachmentPicker(props) {
}}
// 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 onModalHide 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', onModalHide.current, {
once: true, // this removes the listener after running once
});
}}
accept={getAcceptableFileTypes(props.type)}
/>
{props.children({
openPicker: ({onPicked: newOnPicked}) => {
openPicker: ({onPicked: newOnPicked, onModalHide: newOnModalHide}) => {
onPicked.current = newOnPicked;
onModalHide.current = newOnModalHide;
fileInput.current.click();
},
})}
Expand Down
21 changes: 19 additions & 2 deletions src/components/AttachmentPicker/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class AttachmentPicker extends Component {

this.state = {
isVisible: false,
onModalHide: () => {},
focusedIndex: -1,
};

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

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

Expand Down Expand Up @@ -341,7 +345,12 @@ class AttachmentPicker extends Component {
*/
renderChildren() {
return this.props.children({
openPicker: ({onPicked}) => this.open(onPicked),
openPicker: ({onPicked, onModalHide}) => {
this.open(onPicked);
if (onModalHide) {
this.setState({onModalHide});
}
},
});
}

Expand All @@ -352,7 +361,15 @@ class AttachmentPicker extends Component {
onClose={this.close}
isVisible={this.state.isVisible}
anchorPosition={styles.createMenuPosition}
onModalHide={this.onModalHide}
onModalHide={() => {
// this.modalHide is triggered when the modal is closed by selecting an item and
// this.state.onModalHide is triggered when the modal is closed by touching outside or back button
if (this.onModalHide) {
this.onModalHide();
} else {
this.state.onModalHide();
}
}}
>
<View style={this.props.isSmallScreenWidth ? {} : styles.createMenuContainer}>
<ArrowKeyFocusManager
Expand Down
27 changes: 25 additions & 2 deletions src/components/Modal/index.ios.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
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';

function 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>
Expand All @@ -17,4 +40,4 @@ function Modal(props) {
Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;
Modal.displayName = 'Modal';
export default withWindowDimensions(Modal);
export default compose(withWindowDimensions, withKeyboardState)(Modal);
19 changes: 12 additions & 7 deletions src/components/PopoverMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,24 @@ function PopoverMenu(props) {
{isActive: props.isVisible},
);

const resetFocusAndHideModal = () => {
setFocusedIndex(-1); // Reset the focusedIndex on modal hide
if (selectedItemIndex !== null) {
props.menuItems[selectedItemIndex].onSelected();
setSelectedItemIndex(null);
} else if (props.onModalHide) {
// trigger the onModalHide callback only when modal is closed by clicking outside or back button
props.onModalHide();
}
};

return (
<PopoverWithMeasuredContent
anchorPosition={props.anchorPosition}
anchorAlignment={props.anchorAlignment}
onClose={props.onClose}
isVisible={props.isVisible}
onModalHide={() => {
setFocusedIndex(-1);
if (selectedItemIndex !== null) {
props.menuItems[selectedItemIndex].onSelected();
setSelectedItemIndex(null);
}
}}
onModalHide={resetFocusAndHideModal}
animationIn={props.animationIn}
animationOut={props.animationOut}
animationInTiming={props.animationInTiming}
Expand Down
42 changes: 40 additions & 2 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class ReportActionCompose extends React.Component {
this.getMoneyRequestOptions = this.getMoneyRequestOptions.bind(this);
this.getTaskOption = this.getTaskOption.bind(this);
this.addAttachment = this.addAttachment.bind(this);
this.finishAddAttachmentFlow = this.finishAddAttachmentFlow.bind(this);
this.insertSelectedEmoji = this.insertSelectedEmoji.bind(this);
this.insertSelectedMention = this.insertSelectedMention.bind(this);
this.setExceededMaxCommentLength = this.setExceededMaxCommentLength.bind(this);
Expand Down Expand Up @@ -206,6 +207,7 @@ class ReportActionCompose extends React.Component {
const isMobileSafari = Browser.isMobileSafari();

this.state = {
isInAddAttachmentFlow: false,
isFocused: this.shouldFocusInputOnScreenFocus && !this.props.modal.isVisible && !this.props.modal.willAlertModalBecomeVisible && this.props.shouldShowComposeInput,
isFullComposerAvailable: props.isComposerFullSize,
textInputShouldClear: false,
Expand Down Expand Up @@ -257,7 +259,13 @@ class ReportActionCompose extends React.Component {
// 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 (this.willBlurTextInputOnTapOutside && !this.props.modal.isVisible && this.props.isFocused && (prevProps.modal.isVisible || !prevProps.isFocused)) {
if (
this.willBlurTextInputOnTapOutside &&
!this.props.modal.isVisible &&
this.props.isFocused &&
!this.state.isInAddAttachmentFlow &&
(prevProps.modal.isVisible || !prevProps.isFocused)
) {
this.focus();
}

Expand Down Expand Up @@ -412,6 +420,15 @@ class ReportActionCompose extends React.Component {
}
}

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

/**
* Determines if we can show the task option
* @param {Array} reportParticipants
Expand Down Expand Up @@ -878,6 +895,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 @@ -953,6 +981,7 @@ class ReportActionCompose extends React.Component {
onConfirm={this.addAttachment}
onModalShow={() => this.setState({isAttachmentPreviewActive: true})}
onModalHide={() => {
this.finishAddAttachmentFlow();
this.shouldBlockEmojiCalc = false;
this.shouldBlockMentionCalc = false;
this.setState({isAttachmentPreviewActive: false});
Expand Down Expand Up @@ -1016,6 +1045,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 @@ -1031,7 +1064,11 @@ class ReportActionCompose extends React.Component {
animationInTiming={CONST.ANIMATION_IN_TIMING}
isVisible={this.state.isMenuVisible}
onClose={() => this.setMenuVisibility(false)}
onItemSelected={() => this.setMenuVisibility(false)}
onModalHide={this.finishAddAttachmentFlow}
onItemSelected={(item) => {
this.setMenuVisibility(false);
this.setIsInAddAttachmentFlow(item.text === this.props.translate('reportActionCompose.addAttachment'));
}}
anchorPosition={styles.createMenuPositionReportActionCompose(this.props.windowHeight)}
anchorAlignment={{horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM}}
menuItems={[
Expand All @@ -1050,6 +1087,7 @@ class ReportActionCompose extends React.Component {

openPicker({
onPicked: displayFileInModal,
onModalHide: this.finishAddAttachmentFlow,
});
},
},
Expand Down