-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Add Download Banner to mWeb #25418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Download Banner to mWeb #25418
Changes from all commits
d20ba5e
ecd7d39
0c24cfa
aefdaeb
8d14fa3
fde38d5
e61e3f0
81f1536
b19d81d
b415c22
ae8c7e7
ec791de
bd78429
586c22a
1fffcb8
d92db8e
769bc7b
f485cae
9bcb429
955e5ad
163f58c
04ce142
bc88a5e
f1633a6
849639b
66b56e9
2daf94a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
grgia marked this conversation as resolved.
|
||
|
|
||
| /** 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; | ||
|
grgia marked this conversation as resolved.
|
||
|
|
||
| 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 ? ( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This block is just swapping the buttons since props.shouldStackButtons is default behavior
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah actually I was also wondering if it's worth just repositioning the buttons with flex order instead of duplicating the buttons. Maybe it's more annoying than I think though!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good shout! Since this PR is urgent, I think we should ignore non-blocking optimizations for now though
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we make an issue to track this change otherwise we never gonna update it 😂 |
||
| <> | ||
| <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> | ||
| ); | ||
|
|
||
| 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'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's a bit tidier to add the app icon to Expensicons, that way it can also be reused easily.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we also make an issue for this? @grgia |
||
| 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); | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -246,6 +246,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: { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont think other keys are capitalized
Suggested change
|
||||||
| 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.', | ||||||
|
|
||||||
| 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; |
Uh oh!
There was an error while loading. Please reload this page.