diff --git a/src/App.js b/src/App.js index 8b794f5e20d8..581012838973 100644 --- a/src/App.js +++ b/src/App.js @@ -15,6 +15,7 @@ import ComposeProviders from './components/ComposeProviders'; import SafeArea from './components/SafeArea'; import * as Environment from './libs/Environment/Environment'; import {WindowDimensionsProvider} from './components/withWindowDimensions'; +import {KeyboardStateProvider} from './components/withKeyboardState'; // For easier debugging and development, when we are in web we expose Onyx to the window, so you can more easily set data into Onyx if (window && Environment.isDevelopment()) { @@ -41,6 +42,7 @@ const App = () => ( LocaleContextProvider, HTMLEngineProvider, WindowDimensionsProvider, + KeyboardStateProvider, ]} > diff --git a/src/components/HeaderWithCloseButton.js b/src/components/HeaderWithCloseButton.js index 27ab5dbbe890..ee1970bca37d 100755 --- a/src/components/HeaderWithCloseButton.js +++ b/src/components/HeaderWithCloseButton.js @@ -9,14 +9,14 @@ import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Tooltip from './Tooltip'; -import ThreeDotsMenu, {ThreeDotsMenuItemPropTypes} from './ThreeDotsMenu'; -import VirtualKeyboard from '../libs/VirtualKeyboard'; import getButtonState from '../libs/getButtonState'; import * as StyleUtils from '../styles/StyleUtils'; -import withDelayToggleButtonState, {withDelayToggleButtonStatePropTypes} from './withDelayToggleButtonState'; import compose from '../libs/compose'; +import ThreeDotsMenu, {ThreeDotsMenuItemPropTypes} from './ThreeDotsMenu'; +import withDelayToggleButtonState, {withDelayToggleButtonStatePropTypes} from './withDelayToggleButtonState'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import withKeyboardState, {keyboardStatePropTypes} from './withKeyboardState'; const propTypes = { /** Title of the Header */ @@ -79,8 +79,8 @@ const propTypes = { }), ...withLocalizePropTypes, - ...withDelayToggleButtonStatePropTypes, + ...keyboardStatePropTypes, }; const defaultProps = { @@ -142,7 +142,7 @@ class HeaderWithCloseButton extends Component { { - if (VirtualKeyboard.isOpen()) { + if (this.props.isKeyboardShown) { Keyboard.dismiss(); } this.props.onBackButtonPress(); @@ -224,4 +224,5 @@ HeaderWithCloseButton.defaultProps = defaultProps; export default compose( withLocalize, withDelayToggleButtonState, + withKeyboardState, )(HeaderWithCloseButton); diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 46474eb73705..1029750b9ca5 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -45,7 +45,7 @@ class PDFView extends Component { } componentDidUpdate() { - this.props.onToggleKeyboard(this.props.isShown); + this.props.onToggleKeyboard(this.props.isKeyboardShown); } handleFailureToLoadPDF(error) { diff --git a/src/components/PDFView/pdfViewPropTypes.js b/src/components/PDFView/pdfViewPropTypes.js index 281c135fe438..5f2c7e381dd0 100644 --- a/src/components/PDFView/pdfViewPropTypes.js +++ b/src/components/PDFView/pdfViewPropTypes.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import stylePropTypes from '../../styles/stylePropTypes'; import {windowDimensionsPropTypes} from '../withWindowDimensions'; +import {keyboardStatePropTypes} from '../withKeyboardState'; const propTypes = { /** URL to full-sized image */ @@ -13,6 +14,7 @@ const propTypes = { onToggleKeyboard: PropTypes.func, ...windowDimensionsPropTypes, + ...keyboardStatePropTypes, }; const defaultProps = { diff --git a/src/components/withKeyboardState.js b/src/components/withKeyboardState.js index b1159bdb376d..345ff5bb8f92 100755 --- a/src/components/withKeyboardState.js +++ b/src/components/withKeyboardState.js @@ -1,64 +1,79 @@ -import React, {Component} from 'react'; +/* eslint-disable react/no-unused-state */ +import React, {forwardRef, createContext} from 'react'; import PropTypes from 'prop-types'; import {Keyboard} from 'react-native'; import getComponentDisplayName from '../libs/getComponentDisplayName'; -const withKeyboardStatePropTypes = { - /** Returns whether keyboard is open */ - isShown: PropTypes.bool.isRequired, +const KeyboardStateContext = createContext(null); +const keyboardStatePropTypes = { + /** Whether or not the keyboard is open */ + isKeyboardShown: PropTypes.bool.isRequired, }; -export default function withKeyboardState(WrappedComponent) { - const WithKeyboardState = class extends Component { - constructor(props) { - super(props); - this.state = { - isShown: false, - }; - } +const keyboardStateProviderPropTypes = { + /* Actual content wrapped by this component */ + children: PropTypes.node.isRequired, +}; + +class KeyboardStateProvider extends React.Component { + constructor(props) { + super(props); + + this.state = { + isKeyboardShown: false, + }; + } - componentDidMount() { - this.keyboardDidShowListener = Keyboard.addListener( - 'keyboardDidShow', - () => { - this.setState({isShown: true}); - }, - ); - this.keyboardDidHideListener = Keyboard.addListener( - 'keyboardDidHide', - () => { - this.setState({isShown: false}); - }, - ); - } + componentDidMount() { + this.keyboardDidShowListener = Keyboard.addListener( + 'keyboardDidShow', + () => { + this.setState({isKeyboardShown: true}); + }, + ); + this.keyboardDidHideListener = Keyboard.addListener( + 'keyboardDidHide', + () => { + this.setState({isKeyboardShown: false}); + }, + ); + } - componentWillUnmount() { - this.keyboardDidShowListener.remove(); - this.keyboardDidHideListener.remove(); - } + componentWillUnmount() { + this.keyboardDidShowListener.remove(); + this.keyboardDidHideListener.remove(); + } - render() { - // eslint-disable-next-line react/jsx-props-no-spreading - return ; - } - }; + render() { + return ( + + {this.props.children} + + ); + } +} + +KeyboardStateProvider.propTypes = keyboardStateProviderPropTypes; - WithKeyboardState.displayName = `WithKeyboardState(${getComponentDisplayName(WrappedComponent)})`; - WithKeyboardState.propTypes = { - forwardedRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({current: PropTypes.instanceOf(React.Component)}), - ]), - }; - WithKeyboardState.defaultProps = { - forwardedRef: undefined, - }; - return React.forwardRef((props, ref) => ( - // eslint-disable-next-line react/jsx-props-no-spreading - +/** + * @param {React.Component} WrappedComponent + * @returns {React.Component} + */ +export default function withKeyboardState(WrappedComponent) { + const WithKeyboardState = forwardRef((props, ref) => ( + + {keyboardStateProps => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + )} + )); + + WithKeyboardState.displayName = `withKeyboardState(${getComponentDisplayName(WrappedComponent)})`; + return WithKeyboardState; } export { - withKeyboardStatePropTypes, + KeyboardStateProvider, + keyboardStatePropTypes, }; diff --git a/src/libs/VirtualKeyboard/index.js b/src/libs/VirtualKeyboard/index.js deleted file mode 100644 index 9b86a124efdc..000000000000 --- a/src/libs/VirtualKeyboard/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import _ from 'underscore'; -import * as Browser from '../Browser'; - -/** - * Is the virtual keyboard open? - * - * @returns {Boolean|null} – null if the VirtualKeyboard API is unavailable - */ -function isOpen() { - if (!_.has(navigator, 'virtualKeyboard')) { - return null; - } - return navigator.virtualKeyboard.boundingRect.y > 0; -} - -/** - * As of January 2022, the VirtualKeyboard web API is not available in all browsers yet - * If it is unavailable, we default to assuming that the virtual keyboard is open on mobile devices. - * See https://github.com/Expensify/App/issues/6767 for additional context. - * - * @returns {Boolean} - */ -function shouldAssumeIsOpen() { - const isOpened = isOpen(); - return _.isNull(isOpened) ? Browser.isMobile() : isOpened; -} - -export default { - isOpen, - shouldAssumeIsOpen, -}; diff --git a/src/libs/VirtualKeyboard/index.native.js b/src/libs/VirtualKeyboard/index.native.js deleted file mode 100644 index dc3905f3d599..000000000000 --- a/src/libs/VirtualKeyboard/index.native.js +++ /dev/null @@ -1,33 +0,0 @@ -import {Keyboard} from 'react-native'; - -let isVirtualKeyboardOpen = false; - -Keyboard.addListener( - 'keyboardDidShow', - () => { - isVirtualKeyboardOpen = true; - }, -); - -Keyboard.addListener( - 'keyboardDidHide', - () => { - isVirtualKeyboardOpen = false; - }, -); - -/** - * Is the virtual keyboard open? - * - * Note – the web equivalent of this function may return null. - * - * @returns {Boolean} - */ -function isOpen() { - return isVirtualKeyboardOpen; -} - -export default { - isOpen, - shouldAssumeIsOpen: isOpen, -}; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 7811d1c25ee1..35149943c78a 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -21,11 +21,8 @@ import ReportTypingIndicator from './ReportTypingIndicator'; import AttachmentModal from '../../../components/AttachmentModal'; import compose from '../../../libs/compose'; import PopoverMenu from '../../../components/PopoverMenu'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; -import withDrawerState from '../../../components/withDrawerState'; import CONST from '../../../CONST'; import canFocusInputOnScreenFocus from '../../../libs/canFocusInputOnScreenFocus'; -import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import Permissions from '../../../libs/Permissions'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; @@ -39,7 +36,6 @@ import {withNetwork, withPersonalDetails} from '../../../components/OnyxProvider import * as User from '../../../libs/actions/User'; import Tooltip from '../../../components/Tooltip'; import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton'; -import VirtualKeyboard from '../../../libs/VirtualKeyboard'; import canUseTouchScreen from '../../../libs/canUseTouchscreen'; import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView'; import OfflineIndicator from '../../../components/OfflineIndicator'; @@ -49,6 +45,10 @@ import * as EmojiUtils from '../../../libs/EmojiUtils'; import reportPropTypes from '../../reportPropTypes'; import ReportDropUI from './ReportDropUI'; import DragAndDrop from '../../../components/DragAndDrop'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; +import withDrawerState from '../../../components/withDrawerState'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import withKeyboardState, {keyboardStatePropTypes} from '../../../components/withKeyboardState'; const propTypes = { /** Beta features list */ @@ -99,6 +99,7 @@ const propTypes = { ...windowDimensionsPropTypes, ...withLocalizePropTypes, ...withCurrentUserPersonalDetailsPropTypes, + ...keyboardStatePropTypes, }; const defaultProps = { @@ -430,7 +431,8 @@ class ReportActionCompose extends React.Component { * @param {Object} e */ triggerHotkeyActions(e) { - if (!e || VirtualKeyboard.shouldAssumeIsOpen()) { + // Do not trigger actions for mobileWeb or native clients that have the keyboard open because for those devices, we want the return key to insert newlines rather than submit the form + if (!e || this.props.isSmallScreenWidth || this.props.isKeyboardShown) { return; } @@ -741,6 +743,7 @@ export default compose( withNetwork(), withPersonalDetails(), withCurrentUserPersonalDetails, + withKeyboardState, withOnyx({ betas: { key: ONYXKEYS.BETAS, diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 67264ea02ed6..0f65c3634025 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -10,18 +10,18 @@ import Composer from '../../../components/Composer'; import * as Report from '../../../libs/actions/Report'; import * as ReportScrollManager from '../../../libs/ReportScrollManager'; import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; -import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import Button from '../../../components/Button'; import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; import compose from '../../../libs/compose'; import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; -import VirtualKeyboard from '../../../libs/VirtualKeyboard'; import * as EmojiUtils from '../../../libs/EmojiUtils'; import reportPropTypes from '../../reportPropTypes'; import ExceededCommentLength from '../../../components/ExceededCommentLength'; import CONST from '../../../CONST'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import withKeyboardState, {keyboardStatePropTypes} from '../../../components/withKeyboardState'; const propTypes = { /** All the data of the action */ @@ -43,14 +43,12 @@ const propTypes = { // eslint-disable-next-line react/no-unused-prop-types report: reportPropTypes, - // Whether or not the emoji picker is disabled + /** Whether or not the emoji picker is disabled */ shouldDisableEmojiPicker: PropTypes.bool, - /** Window Dimensions Props */ - ...windowDimensionsPropTypes, - - /** Localization props */ ...withLocalizePropTypes, + ...windowDimensionsPropTypes, + ...keyboardStatePropTypes, }; const defaultProps = { @@ -203,7 +201,8 @@ class ReportActionItemMessageEdit extends React.Component { * @param {Event} e */ triggerSaveOrCancel(e) { - if (!e || VirtualKeyboard.shouldAssumeIsOpen()) { + // Do not trigger actions for mobileWeb or native clients that have the keyboard open because for those devices, we want the return key to insert newlines rather than submit the form + if (!e || this.props.isSmallScreenWidth || this.props.isKeyboardShown) { return; } if (e.key === 'Enter' && !e.shiftKey) { @@ -241,7 +240,7 @@ class ReportActionItemMessageEdit extends React.Component { onFocus={() => { this.setState({isFocused: true}); ReportScrollManager.scrollToIndex({animated: true, index: this.props.index}, true); - toggleReportActionComposeView(false, VirtualKeyboard.shouldAssumeIsOpen()); + toggleReportActionComposeView(false, this.props.isSmallScreenWidth); }} onBlur={(event) => { // Return to prevent re-render when save/cancel button is pressed which cancels the onPress event by re-rendering @@ -249,7 +248,7 @@ class ReportActionItemMessageEdit extends React.Component { return; } this.setState({isFocused: false}); - toggleReportActionComposeView(true, VirtualKeyboard.shouldAssumeIsOpen()); + toggleReportActionComposeView(true, this.props.isSmallScreenWidth); }} selection={this.state.selection} onSelectionChange={this.onSelectionChange} @@ -293,6 +292,7 @@ ReportActionItemMessageEdit.defaultProps = defaultProps; export default compose( withLocalize, withWindowDimensions, + withKeyboardState, )(React.forwardRef((props, ref) => ( /* eslint-disable-next-line react/jsx-props-no-spreading */ diff --git a/src/pages/signin/SignInPageLayout/SignInPageContent.js b/src/pages/signin/SignInPageLayout/SignInPageContent.js index c692c93c868e..5f3ccb8a268a 100755 --- a/src/pages/signin/SignInPageLayout/SignInPageContent.js +++ b/src/pages/signin/SignInPageLayout/SignInPageContent.js @@ -10,7 +10,7 @@ import TermsAndLicenses from '../TermsAndLicenses'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import SignInPageForm from '../../../components/SignInPageForm'; import compose from '../../../libs/compose'; -import withKeyboardState from '../../../components/withKeyboardState'; +import withKeyboardState, {keyboardStatePropTypes} from '../../../components/withKeyboardState'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView'; @@ -27,12 +27,12 @@ const propTypes = { ...withLocalizePropTypes, ...windowDimensionsPropTypes, + ...keyboardStatePropTypes, }; const SignInPageContent = (props) => { const dismissKeyboardWhenTappedOutsideOfInput = () => { - // This prop comes from withKeyboardState - if (!props.isShown) { + if (!props.isKeyboardShown) { return; } Keyboard.dismiss();