Skip to content

Commit

Permalink
Merge pull request #25418 from Expensify/georgia-DownloadAppBanner
Browse files Browse the repository at this point in the history
Add Download Banner to mWeb
  • Loading branch information
mountiny authored Aug 23, 2023
2 parents 59b396c + 2daf94a commit 76d72e4
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 20 deletions.
18 changes: 18 additions & 0 deletions assets/images/expensify-app-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/Expensify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -198,6 +199,7 @@ function Expensify(props) {
<KeyboardShortcutsModal />
<GrowlNotification ref={Growl.growlRef} />
<PopoverReportActionContextMenu ref={ReportActionContextMenu.contextMenuRef} />
<DownloadAppModal />
<EmojiPicker ref={EmojiPickerAction.emojiPickerRef} />
{/* We include the modal for showing a new update at the top level so the option is always present. */}
{props.updateAvailable ? <UpdateAppModal /> : null}
Expand Down
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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;
Expand Down
114 changes: 94 additions & 20 deletions src/components/ConfirmContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 = {
Expand All @@ -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 (
<View style={[styles.m5, ...props.contentStyles]}>
<View style={[styles.flexRow, styles.mb4]}>
<Header title={props.title} />
<View style={isCentered ? [styles.alignItemsCenter, styles.mb6] : []}>
{!_.isEmpty(props.iconSource) ||
(_.isFunction(props.iconSource) && (
<View style={[styles.flexRow, styles.mb3]}>
<Icon
src={props.iconSource}
width={variables.downloadAppModalAppIconSize}
height={variables.downloadAppModalAppIconSize}
additionalStyles={[...props.iconAdditionalStyles]}
/>
</View>
))}

<View style={[styles.flexRow, isCentered ? {} : styles.mb4]}>
<Header
title={props.title}
textStyles={[...props.titleStyles]}
/>
</View>

{_.isString(props.prompt) ? <Text style={[...props.promptStyles, isCentered ? styles.textAlignCenter : {}]}>{props.prompt}</Text> : props.prompt}
</View>

{_.isString(props.prompt) ? <Text>{props.prompt}</Text> : props.prompt}

<Button
success={props.success}
danger={props.danger}
style={[styles.mt4]}
onPress={props.onConfirm}
pressOnEnter
text={props.confirmText || translate('common.yes')}
isDisabled={isOffline && props.shouldDisableConfirmButtonWhenOffline}
/>
{props.shouldShowCancelButton && (
<Button
style={[styles.mt3, styles.noSelect]}
onPress={props.onCancel}
text={props.cancelText || translate('common.no')}
shouldUseDefaultHover
/>
{props.shouldStackButtons ? (
<>
<Button
success={props.success}
danger={props.danger}
style={[styles.mt4]}
onPress={props.onConfirm}
pressOnEnter
text={props.confirmText || translate('common.yes')}
isDisabled={isOffline && props.shouldDisableConfirmButtonWhenOffline}
/>
{props.shouldShowCancelButton && (
<Button
style={[styles.mt3, styles.noSelect]}
onPress={props.onCancel}
text={props.cancelText || translate('common.no')}
shouldUseDefaultHover
/>
)}
</>
) : (
<View style={[styles.flexRow, styles.gap4]}>
{props.shouldShowCancelButton && (
<Button
style={[styles.noSelect, styles.flex1]}
onPress={props.onCancel}
text={props.cancelText || translate('common.no')}
shouldUseDefaultHover
medium
/>
)}
<Button
success={props.success}
danger={props.danger}
style={[styles.flex1]}
onPress={props.onConfirm}
pressOnEnter
text={props.confirmText || translate('common.yes')}
isDisabled={isOffline && props.shouldDisableConfirmButtonWhenOffline}
medium
/>
</View>
)}
</View>
);
Expand Down
33 changes: 33 additions & 0 deletions src/components/ConfirmModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ const propTypes = {
/** Should we announce the Modal visibility changes? */
shouldSetModalVisibility: PropTypes.bool,

/** Icon to display above the title */
iconSource: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

/** 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 icon */
// eslint-disable-next-line react/forbid-prop-types
iconAdditionalStyles: PropTypes.arrayOf(PropTypes.object),

/** Whether to center the icon / text content */
shouldCenterContent: PropTypes.bool,

/** Whether to stack the buttons */
shouldStackButtons: PropTypes.bool,

...windowDimensionsPropTypes,
};

Expand All @@ -59,7 +80,13 @@ const defaultProps = {
shouldShowCancelButton: true,
shouldSetModalVisibility: true,
title: '',
iconSource: null,
onModalHide: () => {},
titleStyles: [],
iconAdditionalStyles: [],
promptStyles: [],
shouldCenterContent: false,
shouldStackButtons: true,
};

function ConfirmModal(props) {
Expand All @@ -85,6 +112,12 @@ function ConfirmModal(props) {
danger={props.danger}
shouldDisableConfirmButtonWhenOffline={props.shouldDisableConfirmButtonWhenOffline}
shouldShowCancelButton={props.shouldShowCancelButton}
shouldCenterContent={props.shouldCenterContent}
iconSource={props.iconSource}
iconAdditionalStyles={props.iconAdditionalStyles}
titleStyles={props.titleStyles}
promptStyles={props.promptStyles}
shouldStackButtons={props.shouldStackButtons}
/>
</Modal>
);
Expand Down
74 changes: 74 additions & 0 deletions src/components/DownloadAppModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import ONYXKEYS from '../ONYXKEYS';
import styles from '../styles/styles';
import CONST from '../CONST';
import AppIcon from '../../assets/images/expensify-app-icon.svg';
import useLocalize from '../hooks/useLocalize';
import * as Link from '../libs/actions/Link';
import * as Browser from '../libs/Browser';
import getOperatingSystem from '../libs/getOperatingSystem';
import setShowDownloadAppModal from '../libs/actions/DownloadAppModal';
import ConfirmModal from './ConfirmModal';

const propTypes = {
/** ONYX PROP to hide banner for a user that has dismissed it */
// eslint-disable-next-line react/forbid-prop-types
showDownloadAppBanner: PropTypes.bool,
};

const defaultProps = {
showDownloadAppBanner: true,
};

function DownloadAppModal({showDownloadAppBanner}) {
const [shouldShowBanner, setshouldShowBanner] = useState(Browser.isMobile() && showDownloadAppBanner);

const {translate} = useLocalize();

const handleCloseBanner = () => {
setShowDownloadAppModal(false);
setshouldShowBanner(false);
};

let link = '';

if (getOperatingSystem() === CONST.OS.IOS) {
link = CONST.APP_DOWNLOAD_LINKS.IOS;
} else if (getOperatingSystem() === CONST.OS.ANDROID) {
link = CONST.APP_DOWNLOAD_LINKS.ANDROID;
}

const handleOpenAppStore = () => {
Link.openExternalLink(link, true);
};

return (
<ConfirmModal
title={translate('DownloadAppModal.downloadTheApp')}
isVisible={shouldShowBanner}
onConfirm={handleOpenAppStore}
onCancel={handleCloseBanner}
prompt={translate('DownloadAppModal.keepTheConversationGoing')}
confirmText={translate('common.download')}
cancelText={translate('DownloadAppModal.noThanks')}
shouldCenterContent
iconSource={AppIcon}
promptStyles={[styles.textNormal, styles.lh20]}
titleStyles={[styles.textHeadline]}
iconAdditionalStyles={[styles.appIconBorderRadius]}
shouldStackButtons={false}
/>
);
}

DownloadAppModal.displayName = 'DownloadAppModal';
DownloadAppModal.propTypes = propTypes;
DownloadAppModal.defaultProps = defaultProps;

export default withOnyx({
showDownloadAppBanner: {
key: ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER,
},
})(DownloadAppModal);
5 changes: 5 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ export default {
newFaceEnterMagicCode: ({login}) => `It's always great to see a new face around here! Please enter the magic code sent to ${login}. It should arrive within a minute or two.`,
welcomeEnterMagicCode: ({login}) => `Please enter the magic code sent to ${login}. It should arrive within a minute or two.`,
},
DownloadAppModal: {
downloadTheApp: 'Download the app',
keepTheConversationGoing: 'Keep the conversation going in New Expensify, download the app for an enhanced experience.',
noThanks: 'No thanks',
},
login: {
hero: {
header: 'Split bills, request payments, and chat with friends.',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ export default {
newFaceEnterMagicCode: ({login}) => `¡Siempre es genial ver una cara nueva por aquí! Por favor ingresa el código mágico enviado a ${login}. Debería llegar en un par de minutos.`,
welcomeEnterMagicCode: ({login}) => `Por favor, introduce el código mágico enviado a ${login}. Debería llegar en un par de minutos.`,
},
DownloadAppModal: {
downloadTheApp: 'Descarga la aplicación',
keepTheConversationGoing: 'Mantén la conversación en New Expensify, descarga la aplicación para una experiencia mejorada.',
noThanks: 'No, gracias',
},
login: {
hero: {
header: 'Divida las facturas, solicite pagos y chatee con sus amigos.',
Expand Down
11 changes: 11 additions & 0 deletions src/libs/actions/DownloadAppModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';

/**
* @param {Boolean} shouldShowBanner
*/
function setShowDownloadAppModal(shouldShowBanner) {
Onyx.set(ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER, shouldShowBanner);
}

export default setShowDownloadAppModal;
5 changes: 5 additions & 0 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ const styles = {
color: themeColors.textSupporting,
},

appIconBorderRadius: {
overflow: 'hidden',
borderRadius: 12,
},

unitCol: {
margin: 0,
padding: 0,
Expand Down
Loading

0 comments on commit 76d72e4

Please sign in to comment.