diff --git a/src/CONST.js b/src/CONST.js
index 26acb0412305..93fc7e222945 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -375,6 +375,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',
@@ -400,6 +401,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',
COMMENT_LENGTH_DEBOUNCE_TIME: 500,
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 9cb0d62731d1..fd61c0f8bc5c 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -170,6 +170,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 5c83dda89619..8fbb60428e5d 100644
--- a/src/components/ScreenWrapper/index.js
+++ b/src/components/ScreenWrapper/index.js
@@ -13,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 SafeAreaConsumer from '../SafeAreaConsumer';
+import TestToolsModal from '../TestToolsModal';
+import TestToolsGestureDetector from '../TestToolsGestureDetector';
import withKeyboardState from '../withKeyboardState';
class ScreenWrapper extends React.Component {
@@ -111,29 +114,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 && (
+
+ )}
+
+
+
);
}}
@@ -152,6 +158,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 4345f60fad48..5f0e913a5980 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. It does not fire when user exit the page.
onEntryTransitionEnd: 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']),
@@ -31,6 +35,8 @@ const propTypes = {
/** Whether to dismiss keyboard before leaving a screen */
shouldDismissKeyboardBeforeClose: PropTypes.bool,
+
+ ...environmentPropTypes,
};
const defaultProps = {
@@ -40,6 +46,7 @@ const defaultProps = {
includePaddingTop: true,
onEntryTransitionEnd: () => {},
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/TestToolsGestureDetector.js b/src/components/TestToolsGestureDetector.js
new file mode 100644
index 000000000000..9bd97accfe87
--- /dev/null
+++ b/src/components/TestToolsGestureDetector.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {GestureDetector, Gesture} from 'react-native-gesture-handler';
+import CONST from '../CONST';
+import withEnvironment, {environmentPropTypes} from './withEnvironment';
+import * as App from '../libs/actions/App';
+
+/**
+ * If we are in the dev environment wrap the children in a gesture detector that opens a test tools modal after 5 taps
+ */
+
+const propTypes = {
+ /** Children to wrap in TestToolsGestureDetector. */
+ children: PropTypes.node.isRequired,
+
+ ...environmentPropTypes,
+};
+
+const TestToolsGestureDetector = (props) => {
+ const quintupleTap = Gesture.Tap()
+ .numberOfTaps(5)
+
+ // Run the callbacks on the JS thread otherwise there's an error on iOS
+ .runOnJS(true)
+ .onEnd(App.toggleTestToolsModal);
+
+ // The gesture to open the test tools menu should only exist in the dev environment
+ if (props.environment === CONST.ENVIRONMENT.DEV) {
+ return (
+
+ {props.children}
+
+ );
+ }
+ return (
+ <>
+ {props.children}
+ >
+ );
+};
+
+TestToolsGestureDetector.propTypes = propTypes;
+TestToolsGestureDetector.displayName = 'TestToolsGestureDetector';
+
+export default withEnvironment(TestToolsGestureDetector);
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 b3d05bed7f6a..7f08b7a0a836 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -286,6 +286,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,
setLocaleAndNavigate,
@@ -294,4 +310,5 @@ export {
openProfile,
openApp,
reconnectApp,
+ toggleTestToolsModal,
};
diff --git a/src/styles/getModalStyles/getBaseModalStyles.js b/src/styles/getModalStyles/getBaseModalStyles.js
index 36c98880f69a..0475635a35f5 100644
--- a/src/styles/getModalStyles/getBaseModalStyles.js
+++ b/src/styles/getModalStyles/getBaseModalStyles.js
@@ -124,6 +124,37 @@ export default (type, windowDimensions, popoverAnchorPosition = {}, innerContain
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 a55774756075..1051098fbe16 100644
--- a/src/styles/utilities/sizing.js
+++ b/src/styles/utilities/sizing.js
@@ -28,6 +28,10 @@ export default {
minWidth: '25%',
},
+ mnw120: {
+ minWidth: 120,
+ },
+
w50: {
width: '50%',
},