diff --git a/assets/images/expensify-app-icon.svg b/assets/images/expensify-app-icon.svg new file mode 100644 index 000000000000..a0adfe7dd952 --- /dev/null +++ b/assets/images/expensify-app-icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/src/Expensify.js b/src/Expensify.js index a1c398d0bb51..ede42c2873dd 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -31,6 +31,7 @@ import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; import * as EmojiPickerAction from './libs/actions/EmojiPickerAction'; import * as DemoActions from './libs/actions/DemoActions'; +import DownloadAppModal from './components/DownloadAppModal'; import DeeplinkWrapper from './components/DeeplinkWrapper'; // This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection @@ -198,6 +199,7 @@ function Expensify(props) { + {/* We include the modal for showing a new update at the top level so the option is always present. */} {props.updateAvailable ? : null} diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 074a5e99e6b1..d4d2ab1f90a6 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -87,6 +87,9 @@ const ONYXKEYS = { SESSION: 'session', BETAS: 'betas', + /** Denotes if the Download App Banner has been dismissed */ + SHOW_DOWNLOAD_APP_BANNER: 'showDownloadAppBanner', + /** NVP keys * Contains the user's payPalMe data */ PAYPAL: 'paypal', @@ -286,6 +289,7 @@ type OnyxValues = { [ONYXKEYS.ACTIVE_CLIENTS]: string[]; [ONYXKEYS.DEVICE_ID]: string; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; + [ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER]: boolean; [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.Request[]; [ONYXKEYS.QUEUED_ONYX_UPDATES]: OnyxTypes.QueuedOnyxUpdates; [ONYXKEYS.CURRENT_DATE]: string; diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js index 6981fd451309..9a72d4e7d584 100644 --- a/src/components/ConfirmContent.js +++ b/src/components/ConfirmContent.js @@ -8,6 +8,8 @@ import Button from './Button'; import useLocalize from '../hooks/useLocalize'; import useNetwork from '../hooks/useNetwork'; import Text from './Text'; +import variables from '../styles/variables'; +import Icon from './Icon'; const propTypes = { /** Title of the modal */ @@ -40,9 +42,30 @@ const propTypes = { /** Whether we should show the cancel button */ shouldShowCancelButton: PropTypes.bool, + /** Icon to display above the title */ + iconSource: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + + /** Whether to center the icon / text content */ + shouldCenterContent: PropTypes.bool, + + /** Whether to stack the buttons */ + shouldStackButtons: PropTypes.bool, + + /** Styles for title */ + // eslint-disable-next-line react/forbid-prop-types + titleStyles: PropTypes.arrayOf(PropTypes.object), + + /** Styles for prompt */ + // eslint-disable-next-line react/forbid-prop-types + promptStyles: PropTypes.arrayOf(PropTypes.object), + /** Styles for view */ // eslint-disable-next-line react/forbid-prop-types contentStyles: PropTypes.arrayOf(PropTypes.object), + + /** Styles for icon */ + // eslint-disable-next-line react/forbid-prop-types + iconAdditionalStyles: PropTypes.arrayOf(PropTypes.object), }; const defaultProps = { @@ -55,36 +78,87 @@ const defaultProps = { shouldDisableConfirmButtonWhenOffline: false, shouldShowCancelButton: true, contentStyles: [], + iconSource: null, + shouldCenterContent: false, + shouldStackButtons: true, + titleStyles: [], + promptStyles: [], + iconAdditionalStyles: [], }; function ConfirmContent(props) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); + const isCentered = props.shouldCenterContent; + return ( - -
+ + {!_.isEmpty(props.iconSource) || + (_.isFunction(props.iconSource) && ( + + + + ))} + + +
+ + + {_.isString(props.prompt) ? {props.prompt} : props.prompt} - {_.isString(props.prompt) ? {props.prompt} : props.prompt} - -