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%',
},