diff --git a/src/CONST.js b/src/CONST.js index 48206b8f9755..118162c41b82 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -369,6 +369,7 @@ const CONST = { CONFIRM: 'confirm', CENTERED: 'centered', CENTERED_UNSWIPEABLE: 'centered_unswipeable', + CENTERED_SMALL: 'centered_small', BOTTOM_DOCKED: 'bottom_docked', POPOVER: 'popover', RIGHT_DOCKED: 'right_docked', @@ -394,6 +395,7 @@ const CONST = { WARM: 'warm', REPORT_ACTION_ITEM_LAYOUT_DEBOUNCE_TIME: 1500, SHOW_LOADING_SPINNER_DEBOUNCE_TIME: 250, + TEST_TOOLS_MODAL_THROTTLE_TIME: 800, TOOLTIP_SENSE: 1000, TRIE_INITIALIZATION: 'trie_initialization', }, diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index c6d709a83059..60a8c4b19ca7 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -161,6 +161,9 @@ export default { // Is Keyboard shortcuts modal open? IS_SHORTCUTS_MODAL_OPEN: 'isShortcutsModalOpen', + // Is the test tools modal open? + IS_TEST_TOOLS_MODAL_OPEN: 'isTestToolsModalOpen', + // Stores information about active wallet transfer amount, selectedAccountID, status, etc WALLET_TRANSFER: 'walletTransfer', diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index e7ba7e5d0e2e..3b4dec0f9561 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -1,5 +1,6 @@ import {View} from 'react-native'; import React from 'react'; +import {GestureDetector, Gesture} from 'react-native-gesture-handler'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import KeyboardAvoidingView from '../KeyboardAvoidingView'; @@ -12,10 +13,13 @@ import OfflineIndicator from '../OfflineIndicator'; import compose from '../../libs/compose'; import withNavigation from '../withNavigation'; import withWindowDimensions from '../withWindowDimensions'; +import withEnvironment from '../withEnvironment'; import ONYXKEYS from '../../ONYXKEYS'; import {withNetwork} from '../OnyxProvider'; import {propTypes, defaultProps} from './propTypes'; +import * as App from '../../libs/actions/App'; import SafeAreaConsumer from '../SafeAreaConsumer'; +import TestToolsModal from '../TestToolsModal'; class ScreenWrapper extends React.Component { constructor(props) { @@ -72,6 +76,19 @@ class ScreenWrapper extends React.Component { } render() { + // Open the test tools menu on 5 taps in dev only + const isDevEnvironment = this.props.environment === CONST.ENVIRONMENT.DEV; + const quintupleTap = Gesture.Tap() + .numberOfTaps(5) + + // Run the callbacks on the JS thread otherwise there's an error on iOS + .runOnJS(true) + .onEnd(() => { + if (!isDevEnvironment) { + return; + } + App.toggleTestToolsModal(); + }); return ( {({ @@ -89,29 +106,32 @@ class ScreenWrapper extends React.Component { } return ( - - - - {// If props.children is a function, call it to provide the insets to the children. - _.isFunction(this.props.children) - ? this.props.children({ - insets, - safeAreaPaddingBottomStyle, - didScreenTransitionEnd: this.state.didScreenTransitionEnd, - }) - : this.props.children - } - {this.props.isSmallScreenWidth && ( - - )} - - + + + + + {(this.props.environment === CONST.ENVIRONMENT.DEV) && } + {// If props.children is a function, call it to provide the insets to the children. + _.isFunction(this.props.children) + ? this.props.children({ + insets, + safeAreaPaddingBottomStyle, + didScreenTransitionEnd: this.state.didScreenTransitionEnd, + }) + : this.props.children + } + {this.props.isSmallScreenWidth && ( + + )} + + + ); }} @@ -129,6 +149,10 @@ export default compose( modal: { key: ONYXKEYS.MODAL, }, + isTestToolsModalOpen: { + key: ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN, + }, }), withNetwork(), + withEnvironment, )(ScreenWrapper); diff --git a/src/components/ScreenWrapper/propTypes.js b/src/components/ScreenWrapper/propTypes.js index 46cc9ed6315b..54ba0feb1b28 100644 --- a/src/components/ScreenWrapper/propTypes.js +++ b/src/components/ScreenWrapper/propTypes.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import {environmentPropTypes} from '../withEnvironment'; const propTypes = { /** Array of additional styles to add */ @@ -19,6 +20,9 @@ const propTypes = { // Called when navigated Screen's transition is finished. onTransitionEnd: PropTypes.func, + // Is the test tools modal open? + isTestToolsModalOpen: PropTypes.bool, + /** The behavior to pass to the KeyboardAvoidingView, requires some trial and error depending on the layout/devices used. * Search 'switch(behavior)' in ./node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js for more context */ keyboardAvoidingViewBehavior: PropTypes.oneOf(['padding', 'height', 'position']), @@ -28,6 +32,8 @@ const propTypes = { /** Indicates when an Alert modal is about to be visible */ willAlertModalBecomeVisible: PropTypes.bool, }), + + ...environmentPropTypes, }; const defaultProps = { @@ -36,6 +42,7 @@ const defaultProps = { includePaddingTop: true, onTransitionEnd: () => {}, modal: {}, + isTestToolsModalOpen: false, keyboardAvoidingViewBehavior: 'padding', }; diff --git a/src/components/TestToolMenu.js b/src/components/TestToolMenu.js index a5e61942f240..ec99689a05f6 100644 --- a/src/components/TestToolMenu.js +++ b/src/components/TestToolMenu.js @@ -37,7 +37,7 @@ const defaultProps = { const TestToolMenu = props => ( <> - + Test Preferences diff --git a/src/components/TestToolRow.js b/src/components/TestToolRow.js index 5364f44537b0..deefaaf3adc0 100644 --- a/src/components/TestToolRow.js +++ b/src/components/TestToolRow.js @@ -13,7 +13,7 @@ const propTypes = { }; const TestToolRow = props => ( - + {props.title} diff --git a/src/components/TestToolsModal.js b/src/components/TestToolsModal.js new file mode 100644 index 000000000000..92aae8e3c810 --- /dev/null +++ b/src/components/TestToolsModal.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import {View} from 'react-native'; +import ONYXKEYS from '../ONYXKEYS'; +import Modal from './Modal'; +import CONST from '../CONST'; +import * as App from '../libs/actions/App'; +import TestToolMenu from './TestToolMenu'; +import styles from '../styles/styles'; + +const propTypes = { + /** Whether the test tools modal is open */ + isTestToolsModalOpen: PropTypes.bool, +}; + +const defaultProps = { + isTestToolsModalOpen: false, +}; + +const TestToolsModal = props => ( + + + + + +); + +TestToolsModal.propTypes = propTypes; +TestToolsModal.defaultProps = defaultProps; +TestToolsModal.displayName = 'TestToolsModal'; + +export default withOnyx({ + modal: { + key: ONYXKEYS.MODAL, + }, + isTestToolsModalOpen: { + key: ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN, + }, +})(TestToolsModal); diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index b405ae82aa49..e44db00feec1 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -268,6 +268,22 @@ function openProfile() { Navigation.navigate(ROUTES.SETTINGS_PROFILE); } +let isTestToolsModalOpen = false; +Onyx.connect({ + key: ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN, + callback: val => isTestToolsModalOpen = val || false, +}); + +/** + * Toggle the test tools modal open or closed. Throttle the toggle to fix Android Chrome where there seems to be an extra tap which closes the modal. + * Throttling also makes the modal stay open if you accidentally tap an extra time, which is easy to do. + */ +function toggleTestToolsModal() { + const toggle = () => Onyx.set(ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN, !isTestToolsModalOpen); + const throttledToggle = _.throttle(toggle, CONST.TIMING.TEST_TOOLS_MODAL_THROTTLE_TIME); + throttledToggle(); +} + export { setLocale, setSidebarLoaded, @@ -275,4 +291,5 @@ export { openProfile, openApp, reconnectApp, + toggleTestToolsModal, }; diff --git a/src/styles/getModalStyles/getBaseModalStyles.js b/src/styles/getModalStyles/getBaseModalStyles.js index aea136f6818a..b23f7e6516f2 100644 --- a/src/styles/getModalStyles/getBaseModalStyles.js +++ b/src/styles/getModalStyles/getBaseModalStyles.js @@ -123,6 +123,37 @@ export default (type, windowDimensions, popoverAnchorPosition = {}, containerSty shouldAddTopSafeAreaPadding = isSmallScreenWidth; shouldAddBottomSafeAreaPadding = false; break; + case CONST.MODAL.MODAL_TYPE.CENTERED_SMALL: + // A centered modal that takes up the minimum possible screen space on all devices + modalStyle = { + ...modalStyle, + ...{ + alignItems: 'center', + }, + }; + modalContainerStyle = { + // Shadow Styles + shadowColor: themeColors.shadow, + shadowOffset: { + width: 0, + height: 0, + }, + shadowOpacity: 0.1, + shadowRadius: 5, + + borderRadius: 12, + borderWidth: 0, + }; + + // Allow this modal to be dismissed with a swipe down or swipe right + swipeDirection = ['down', 'right']; + animationIn = 'fadeIn'; + animationOut = 'fadeOut'; + shouldAddTopSafeAreaMargin = false; + shouldAddBottomSafeAreaMargin = false; + shouldAddTopSafeAreaPadding = false; + shouldAddBottomSafeAreaPadding = false; + break; case CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED: modalStyle = { ...modalStyle, diff --git a/src/styles/utilities/sizing.js b/src/styles/utilities/sizing.js index 70826444f72d..6267f9b098da 100644 --- a/src/styles/utilities/sizing.js +++ b/src/styles/utilities/sizing.js @@ -24,6 +24,10 @@ export default { minWidth: '25%', }, + mnw120: { + minWidth: 120, + }, + w50: { width: '50%', },