diff --git a/src/assets/images/AndroidWallet-Thumbnail-Dark.png b/src/assets/images/AndroidWallet-Thumbnail-Dark.png new file mode 100644 index 0000000000..2fc3706a07 Binary files /dev/null and b/src/assets/images/AndroidWallet-Thumbnail-Dark.png differ diff --git a/src/assets/images/AndroidWallet-Thumbnail-Light.png b/src/assets/images/AndroidWallet-Thumbnail-Light.png new file mode 100644 index 0000000000..f7989b311f Binary files /dev/null and b/src/assets/images/AndroidWallet-Thumbnail-Light.png differ diff --git a/src/assets/images/androidAnnouncementBannerQR.png b/src/assets/images/androidAnnouncementBannerQR.png new file mode 100644 index 0000000000..d02d9767c2 Binary files /dev/null and b/src/assets/images/androidAnnouncementBannerQR.png differ diff --git a/src/components/AccountDrawer/UniwalletModal.tsx b/src/components/AccountDrawer/UniwalletModal.tsx index f258a00f41..11b6e2632e 100644 --- a/src/components/AccountDrawer/UniwalletModal.tsx +++ b/src/components/AccountDrawer/UniwalletModal.tsx @@ -111,11 +111,11 @@ function InfoSection() { - Don't have Uniswap Wallet? + Don't have a Uniswap wallet? {isAndroidGALaunched ? ( - Get the Uniswap app on iOS and Android to safely store and swap tokens. + Safely store and swap tokens with the Uniswap app. Available on iOS and Android. ) : ( Download in the App Store to safely store your tokens and NFTs, swap tokens, and connect to crypto apps. diff --git a/src/components/Banner/AndroidAnnouncementBanner/index.tsx b/src/components/Banner/AndroidAnnouncementBanner/index.tsx new file mode 100644 index 0000000000..2902aa6a44 --- /dev/null +++ b/src/components/Banner/AndroidAnnouncementBanner/index.tsx @@ -0,0 +1,72 @@ +import { Trans } from '@lingui/macro' +import { InterfaceElementName } from '@uniswap/analytics-events' +import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch' +import { useScreenSize } from 'hooks/useScreenSize' +import { useLocation } from 'react-router-dom' +import { useHideAndroidAnnouncementBanner } from 'state/user/hooks' +import { ThemedText } from 'theme/components' +import { useIsDarkMode } from 'theme/components/ThemeToggle' +import { openDownloadApp } from 'utils/openDownloadApp' +import { isMobileSafari } from 'utils/userAgent' + +import androidAnnouncementBannerQR from '../../../assets/images/androidAnnouncementBannerQR.png' +import darkAndroidThumbnail from '../../../assets/images/AndroidWallet-Thumbnail-Dark.png' +import lightAndroidThumbnail from '../../../assets/images/AndroidWallet-Thumbnail-Light.png' +import { + Container, + DownloadButton, + PopupContainer, + StyledQrCode, + StyledXButton, + TextContainer, + Thumbnail, +} from './styled' + +export default function AndroidAnnouncementBanner() { + const [hideAndroidAnnouncementBanner, toggleHideAndroidAnnouncementBanner] = useHideAndroidAnnouncementBanner() + const location = useLocation() + const isLandingScreen = location.search === '?intro=true' || location.pathname === '/' + const screenSize = useScreenSize() + + const shouldDisplay = Boolean(!hideAndroidAnnouncementBanner && !isLandingScreen) + const isDarkMode = useIsDarkMode() + + const isAndroidGALaunched = useAndroidGALaunchFlagEnabled() + const onClick = () => + openDownloadApp({ + element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON, + isAndroidGALaunched, + }) + + if (!isAndroidGALaunched || isMobileSafari) return null + + return ( + + + + + + Uniswap on Android + + + Available now - download from the Google Play Store today + + + Download now + + + + { + // prevent click from bubbling to UI on the page underneath, i.e. clicking a token row + e.preventDefault() + e.stopPropagation() + toggleHideAndroidAnnouncementBanner() + }} + /> + + + ) +} diff --git a/src/components/Banner/AndroidAnnouncementBanner/styled.tsx b/src/components/Banner/AndroidAnnouncementBanner/styled.tsx new file mode 100644 index 0000000000..a635746f5a --- /dev/null +++ b/src/components/Banner/AndroidAnnouncementBanner/styled.tsx @@ -0,0 +1,92 @@ +import { ButtonText } from 'components/Button' +import { OpacityHoverState } from 'components/Common' +import { X } from 'react-feather' +import styled from 'styled-components' +import { BREAKPOINTS } from 'theme' +import { Z_INDEX } from 'theme/zIndex' + +export const PopupContainer = styled.div<{ show: boolean }>` + ${({ show }) => !show && 'display: none'}; + background-color: ${({ theme }) => theme.surface2}; + color: ${({ theme }) => theme.neutral1}; + position: fixed; + z-index: ${Z_INDEX.sticky}; + user-select: none; + border-radius: 20px; + bottom: 40px; + right: 20px; + width: 360px; + height: 92px; + border: 1.3px solid ${({ theme }) => theme.surface3}; + + @media only screen and (max-width: ${BREAKPOINTS.md}px) { + bottom: 62px; + } + + @media only screen and (max-width: ${BREAKPOINTS.xs}px) { + width: unset; + right: 10px; + left: 10px; + } +` +export const StyledXButton = styled(X)` + cursor: pointer; + position: absolute; + top: -30px; + right: 0px; + padding: 4px; + border-radius: 50%; + + background-color: ${({ theme }) => theme.surface5}; + color: ${({ theme }) => theme.neutral2}; + ${OpacityHoverState}; + + @media only screen and (max-width: ${BREAKPOINTS.xs}px) { + top: 8px; + right: 8px; + } +` + +export const Container = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + height: 100%; + overflow: hidden; + border-radius: 20px; + gap: 16px; +` +export const Thumbnail = styled.img` + width: 82px; +` +export const TextContainer = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 2px; + color: ${({ theme }) => theme.neutral2}; + padding: 10px 0px 10px; + line-height: 16px; + + @media only screen and (max-width: ${BREAKPOINTS.xs}px) { + width: 220px; + } +` +export const StyledQrCode = styled.img` + padding: 2px; + border-radius: 8px; + width: 64px; + height: 64px; + background-color: ${({ theme }) => theme.white}; + margin-right: 16px; + + @media only screen and (max-width: ${BREAKPOINTS.xs}px) { + display: none; + } +` +export const DownloadButton = styled(ButtonText)` + line-height: 16px; + font-size: 14px; + color: ${({ theme }) => theme.accent1}; +` diff --git a/src/components/Banner/BaseAnnouncementBanner/index.tsx b/src/components/Banner/BaseAnnouncementBanner/index.tsx deleted file mode 100644 index e092b4e364..0000000000 --- a/src/components/Banner/BaseAnnouncementBanner/index.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Trans } from '@lingui/macro' -import { InterfaceElementName } from '@uniswap/analytics-events' -import { ChainId } from '@uniswap/sdk-core' -import { useWeb3React } from '@web3-react/core' -import { ReactComponent as AppleLogo } from 'assets/svg/apple_logo.svg' -import baseLogoUrl from 'assets/svg/base_background_icon.svg' -import { ReactComponent as UniswapAppLogo } from 'assets/svg/uniswap_app_logo.svg' -import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch' -import { useScreenSize } from 'hooks/useScreenSize' -import { useLocation } from 'react-router-dom' -import { useHideBaseWalletBanner } from 'state/user/hooks' -import { ThemedText } from 'theme/components' -import { openDownloadApp, openWalletMicrosite } from 'utils/openDownloadApp' -import { isAndroid, isIOS, isMobileSafari } from 'utils/userAgent' - -import { BannerButton, BaseBackgroundImage, ButtonRow, PopupContainer, StyledXButton } from './styled' - -export default function BaseWalletBanner() { - const { chainId } = useWeb3React() - const [hideBaseWalletBanner, toggleHideBaseWalletBanner] = useHideBaseWalletBanner() - const location = useLocation() - const isLandingScreen = location.search === '?intro=true' || location.pathname === '/' - - const shouldDisplay = Boolean(!hideBaseWalletBanner && !isLandingScreen && chainId === ChainId.BASE) - - const screenSize = useScreenSize() - - const isAndroidGALaunched = useAndroidGALaunchFlagEnabled() - - if (isMobileSafari) return null - - return ( - - { - // prevent click from bubbling to UI on the page underneath, i.e. clicking a token row - e.preventDefault() - e.stopPropagation() - toggleHideBaseWalletBanner() - }} - /> - - - - - - Swap on{' '} - - - {' '} - BASE in the Uniswap wallet - - - - - {isIOS || (isAndroidGALaunched && isAndroid) ? ( - <> - - openDownloadApp({ - element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON, - isAndroidGALaunched, - }) - } - > - {isAndroidGALaunched ? : } - - {!screenSize['xs'] ? Download : Download app} - - - - - openWalletMicrosite({ element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON }) - } - > - - Learn more - - - - ) : ( - openWalletMicrosite({ element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON })} - > - - Learn more - - - )} - - - ) -} diff --git a/src/components/Banner/BaseAnnouncementBanner/styled.tsx b/src/components/Banner/BaseAnnouncementBanner/styled.tsx deleted file mode 100644 index b96cf72bed..0000000000 --- a/src/components/Banner/BaseAnnouncementBanner/styled.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import walletBannerPhoneImageSrc from 'assets/images/wallet_banner_phone_image.png' -import { BaseButton } from 'components/Button' -import { OpacityHoverState } from 'components/Common' -import Row from 'components/Row' -import { X } from 'react-feather' -import styled from 'styled-components' -import { Z_INDEX } from 'theme/zIndex' - -export const PopupContainer = styled.div<{ show: boolean }>` - display: flex; - flex-direction: column; - justify-content: space-between; - - ${({ show }) => !show && 'display: none'}; - - background: url(${walletBannerPhoneImageSrc}); - background-repeat: no-repeat; - background-position: top 18px right 15px; - background-size: 166px; - - :hover { - background-size: 170px; - } - transition: background-size ${({ theme }) => theme.transition.duration.medium} - ${({ theme }) => theme.transition.timing.inOut}; - - background-color: ${({ theme }) => theme.chain_84531}; - color: ${({ theme }) => theme.neutral1}; - position: fixed; - z-index: ${Z_INDEX.sticky}; - - padding: 24px 16px 16px; - - border-radius: 20px; - bottom: 20px; - right: 20px; - width: 390px; - height: 164px; - - border: 1px solid ${({ theme }) => theme.surface3}; - - box-shadow: ${({ theme }) => theme.deprecated_deepShadow}; - - @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { - bottom: 62px; - } - - @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { - background-position: top 32px right -10px; - width: unset; - right: 10px; - left: 10px; - height: 144px; - } - - user-select: none; -` - -export const BaseBackgroundImage = styled.img` - position: absolute; - top: 0; - left: 0; - height: 138px; - width: 138px; -` -export const ButtonRow = styled(Row)` - gap: 16px; -` -export const StyledXButton = styled(X)` - cursor: pointer; - position: absolute; - top: 21px; - right: 17px; - - color: ${({ theme }) => theme.white}; - ${OpacityHoverState}; -` - -export const BannerButton = styled(BaseButton)` - height: 40px; - border-radius: 16px; - padding: 10px; - ${OpacityHoverState}; -` diff --git a/src/components/TopLevelModals/index.tsx b/src/components/TopLevelModals/index.tsx index cdf4540254..a62f3cce0e 100644 --- a/src/components/TopLevelModals/index.tsx +++ b/src/components/TopLevelModals/index.tsx @@ -2,7 +2,7 @@ import { useWeb3React } from '@web3-react/core' import { OffchainActivityModal } from 'components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal' import UniwalletModal from 'components/AccountDrawer/UniwalletModal' import AirdropModal from 'components/AirdropModal' -import BaseAnnouncementBanner from 'components/Banner/BaseAnnouncementBanner' +import AndroidAnnouncementBanner from 'components/Banner/AndroidAnnouncementBanner' import AddressClaimModal from 'components/claim/AddressClaimModal' import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked' import FiatOnrampModal from 'components/FiatOnrampModal' @@ -30,7 +30,7 @@ export default function TopLevelModals() { - + diff --git a/src/state/migrations/1.test.ts b/src/state/migrations/1.test.ts index 30c77054f4..32ed481111 100644 --- a/src/state/migrations/1.test.ts +++ b/src/state/migrations/1.test.ts @@ -15,7 +15,7 @@ const previousState: PersistAppStateV1 = { tokens: {}, pairs: {}, timestamp: Date.now(), - hideBaseWalletBanner: false, + hideAndroidAnnouncementBanner: false, }, _persist: { version: 0, @@ -43,7 +43,6 @@ describe('migration to v1', () => { expect(result?.user?.tokens).toEqual({}) expect(result?.user?.pairs).toEqual({}) expect(result?.user?.timestamp).toEqual(previousState.user?.timestamp) - expect(result?.user?.hideBaseWalletBanner).toEqual(false) }) it('should not migrate a non-default value', async () => { diff --git a/src/state/migrations/2.test.ts b/src/state/migrations/2.test.ts index 4945c82402..9560ac3687 100644 --- a/src/state/migrations/2.test.ts +++ b/src/state/migrations/2.test.ts @@ -17,7 +17,7 @@ const previousState: PersistAppStateV2 = { tokens: {}, pairs: {}, timestamp: Date.now(), - hideBaseWalletBanner: false, + hideAndroidAnnouncementBanner: false, }, _persist: { version: 1, diff --git a/src/state/migrations/3.test.ts b/src/state/migrations/3.test.ts index 9d8437653d..1317ac2445 100644 --- a/src/state/migrations/3.test.ts +++ b/src/state/migrations/3.test.ts @@ -38,7 +38,7 @@ const previousState: PersistAppStateV3 = { }, pairs: {}, timestamp: Date.now(), - hideBaseWalletBanner: false, + hideAndroidAnnouncementBanner: false, }, _persist: { version: 2, diff --git a/src/state/reducerTypeTest.ts b/src/state/reducerTypeTest.ts index 5aadb43231..fd32c3bbe7 100644 --- a/src/state/reducerTypeTest.ts +++ b/src/state/reducerTypeTest.ts @@ -87,7 +87,7 @@ interface ExpectedUserState { } } timestamp: number - hideBaseWalletBanner: boolean + hideAndroidAnnouncementBanner: boolean showSurveyPopup?: boolean disabledUniswapX?: boolean optedOutOfUniswapX?: boolean diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx index 793e0f1bd7..5072b4da4f 100644 --- a/src/state/user/hooks.tsx +++ b/src/state/user/hooks.tsx @@ -15,7 +15,7 @@ import { useDefaultActiveTokens } from '../../hooks/Tokens' import { addSerializedPair, addSerializedToken, - updateHideBaseWalletBanner, + updateHideAndroidAnnouncementBanner, updateHideClosedPositions, updateUserDeadline, updateUserLocale, @@ -206,15 +206,15 @@ export function usePairAdder(): (pair: Pair) => void { ) } -export function useHideBaseWalletBanner(): [boolean, () => void] { +export function useHideAndroidAnnouncementBanner(): [boolean, () => void] { const dispatch = useAppDispatch() - const hideBaseWalletBanner = useAppSelector((state) => state.user.hideBaseWalletBanner) + const hideAndroidAnnouncementBanner = useAppSelector((state) => state.user.hideAndroidAnnouncementBanner) - const toggleHideBaseWalletBanner = useCallback(() => { - dispatch(updateHideBaseWalletBanner({ hideBaseWalletBanner: true })) + const toggleHideAndroidAnnouncementBanner = useCallback(() => { + dispatch(updateHideAndroidAnnouncementBanner({ hideAndroidAnnouncementBanner: true })) }, [dispatch]) - return [hideBaseWalletBanner, toggleHideBaseWalletBanner] + return [hideAndroidAnnouncementBanner, toggleHideAndroidAnnouncementBanner] } export function useUserDisabledUniswapX(): boolean { diff --git a/src/state/user/reducer.test.ts b/src/state/user/reducer.test.ts index 893dfa719d..67731cd385 100644 --- a/src/state/user/reducer.test.ts +++ b/src/state/user/reducer.test.ts @@ -5,7 +5,7 @@ import reducer, { addSerializedPair, addSerializedToken, initialState, - updateHideBaseWalletBanner, + updateHideAndroidAnnouncementBanner, updateHideClosedPositions, updateSelectedWallet, updateUserDeadline, @@ -77,10 +77,10 @@ describe('swap reducer', () => { }) }) - describe('updateHideBaseWalletBanner', () => { - it('updates the updateHideBaseWalletBanner', () => { - store.dispatch(updateHideBaseWalletBanner({ hideBaseWalletBanner: true })) - expect(store.getState().hideBaseWalletBanner).toEqual(true) + describe('updateHideAndroidAnnouncementBanner', () => { + it('updates the updateHideAndroidAnnouncementBanner', () => { + store.dispatch(updateHideAndroidAnnouncementBanner({ hideAndroidAnnouncementBanner: true })) + expect(store.getState().hideAndroidAnnouncementBanner).toEqual(true) }) }) diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts index 5e49d56723..4c32ebade7 100644 --- a/src/state/user/reducer.ts +++ b/src/state/user/reducer.ts @@ -47,7 +47,7 @@ export interface UserState { } timestamp: number - hideBaseWalletBanner: boolean + hideAndroidAnnouncementBanner: boolean // legacy field indicating the user disabled UniswapX during the opt-in period, or dismissed the UniswapX opt-in modal. disabledUniswapX?: boolean // temporary field indicating the user disabled UniswapX during the transition to the opt-out model @@ -73,7 +73,7 @@ export const initialState: UserState = { tokens: {}, pairs: {}, timestamp: currentTimestamp(), - hideBaseWalletBanner: false, + hideAndroidAnnouncementBanner: false, showSurveyPopup: undefined, originCountry: undefined, } @@ -108,8 +108,8 @@ const userSlice = createSlice({ updateHideClosedPositions(state, action) { state.userHideClosedPositions = action.payload.userHideClosedPositions }, - updateHideBaseWalletBanner(state, action) { - state.hideBaseWalletBanner = action.payload.hideBaseWalletBanner + updateHideAndroidAnnouncementBanner(state, action) { + state.hideAndroidAnnouncementBanner = action.payload.hideAndroidAnnouncementBanner }, updateDisabledUniswapX(state, action) { state.disabledUniswapX = action.payload.disabledUniswapX @@ -152,7 +152,7 @@ export const { updateUserDeadline, updateUserLocale, updateUserSlippageTolerance, - updateHideBaseWalletBanner, + updateHideAndroidAnnouncementBanner, updateDisabledUniswapX, updateOptedOutOfUniswapX, } = userSlice.actions diff --git a/src/utils/openDownloadApp.ts b/src/utils/openDownloadApp.ts index 4da49ae737..b1d35b0ad6 100644 --- a/src/utils/openDownloadApp.ts +++ b/src/utils/openDownloadApp.ts @@ -34,7 +34,8 @@ export function openDownloadApp({ element, isAndroidGALaunched }: OpenDownloadAp } else if (isAndroidGALaunched && isAndroid) { openDownloadStore({ element, appPlatform: AppDownloadPlatform.ANDROID, linkTarget: 'uniswap_wallet_playstore' }) } else { - openWalletMicrosite({ element }) + sendAnalyticsEvent(InterfaceEventName.UNISWAP_WALLET_MICROSITE_OPENED, { element }) + window.open(APP_DOWNLOAD_LINKS[element], /* target = */ 'uniswap_wallet_microsite') } } @@ -61,8 +62,3 @@ const openDownloadStore = (options: AnalyticsLinkOptions) => { }) window.open(APP_DOWNLOAD_LINKS[options.element], /* target = */ options.linkTarget) } - -export const openWalletMicrosite = (options: AnalyticsLinkOptions) => { - sendAnalyticsEvent(InterfaceEventName.UNISWAP_WALLET_MICROSITE_OPENED, { element: options.element }) - window.open(APP_DOWNLOAD_LINKS[options.element], /* target = */ 'uniswap_wallet_microsite') -}