feat: update app download tracking for Android launch (#7542)

* feat: change UniwalletModal android text

* very wip android WC

* adding android/ios disambiguated event names

* put analytics events in todos

* use analytics package

* use isAndroidGALaunched

* fix ternary

* add navbar menu element

* broken onelink changes

* replace utm with onelinks

* use microsite link in address redirect

* fix unit tests, no longer discriminate between platforms expected behavior

* nit lint
This commit is contained in:
Kristie Huang 2023-11-07 17:24:44 -05:00 committed by GitHub
parent aa056adaf9
commit 46c8caa09c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 100 additions and 97 deletions

@ -197,7 +197,7 @@
"@types/react-helmet": "^6.1.7",
"@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "1.5.0",
"@uniswap/analytics-events": "^2.25.0",
"@uniswap/analytics-events": "^2.28.0",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "^1.0.1",

@ -1,4 +1,5 @@
import { InterfaceElementName } from '@uniswap/analytics-events'
import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch'
import { PropsWithChildren, useCallback } from 'react'
import styled from 'styled-components'
import { ClickableStyle } from 'theme/components'
@ -31,7 +32,7 @@ function BaseButton({ onClick, branded, children }: PropsWithChildren<{ onClick?
)
}
// Launches App Store if on an iOS device, else navigates to Uniswap Wallet microsite
// Launches App/Play Store if on an iOS/Android device, else navigates to Uniswap Wallet microsite
export function DownloadButton({
onClick,
text = 'Download',
@ -41,11 +42,12 @@ export function DownloadButton({
text?: string
element: InterfaceElementName
}) {
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
const onButtonClick = useCallback(() => {
// handles any actions required by the parent, i.e. cancelling wallet connection attempt or dismissing an ad
onClick?.()
openDownloadApp({ element })
}, [element, onClick])
openDownloadApp({ element, isAndroidGALaunched })
}, [element, isAndroidGALaunched, onClick])
return (
<BaseButton branded onClick={onButtonClick}>

@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
import { InterfaceElementName } from '@uniswap/analytics-events'
import { InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { WalletConnect as WalletConnectv2 } from '@web3-react/walletconnect-v2'
import { sendAnalyticsEvent } from 'analytics'
import Column, { AutoColumn } from 'components/Column'
@ -9,11 +9,12 @@ import { uniwalletWCV2ConnectConnection } from 'connection'
import { ActivationStatus, useActivationState } from 'connection/activate'
import { ConnectionType } from 'connection/types'
import { UniwalletConnect as UniwalletConnectV2 } from 'connection/WalletConnectV2'
import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch'
import { QRCodeSVG } from 'qrcode.react'
import { useEffect, useState } from 'react'
import styled, { useTheme } from 'styled-components'
import { CloseIcon, ThemedText } from 'theme/components'
import { isIOS } from 'utils/userAgent'
import { isAndroid, isIOS } from 'utils/userAgent'
import uniPng from '../../assets/images/uniwallet_modal_icon.png'
import { DownloadButton } from './DownloadButton'
@ -42,9 +43,11 @@ export default function UniwalletModal() {
const { activationState, cancelActivation } = useActivationState()
const [uri, setUri] = useState<string>()
// Displays the modal if not on iOS, a Uniswap Wallet Connection is pending, & qrcode URI is available
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
// Displays the modal if not on iOS/Android, a Uniswap Wallet Connection is pending, & qrcode URI is available
const onLaunchedMobilePlatform = isIOS || (isAndroidGALaunched && isAndroid)
const open =
!isIOS &&
!onLaunchedMobilePlatform &&
activationState.status === ActivationStatus.PENDING &&
activationState.connection.type === ConnectionType.UNISWAP_WALLET_V2 &&
!!uri
@ -57,7 +60,7 @@ export default function UniwalletModal() {
}, [])
useEffect(() => {
if (open) sendAnalyticsEvent('Uniswap wallet modal opened')
if (open) sendAnalyticsEvent(InterfaceEventName.UNIWALLET_CONNECT_MODAL_OPENED)
}, [open])
const theme = useTheme()
@ -102,6 +105,8 @@ const InfoSectionWrapper = styled(RowBetween)`
`
function InfoSection() {
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
return (
<InfoSectionWrapper>
<AutoColumn gap="4px">
@ -109,9 +114,13 @@ function InfoSection() {
<Trans>Don&apos;t have Uniswap Wallet?</Trans>
</ThemedText.SubHeaderSmall>
<ThemedText.BodySmall color="neutral2">
{isAndroidGALaunched ? (
<Trans>Get the Uniswap app on iOS and Android to safely store and swap tokens.</Trans>
) : (
<Trans>
Download in the App Store to safely store your tokens and NFTs, swap tokens, and connect to crypto apps.
</Trans>
)}
</ThemedText.BodySmall>
</AutoColumn>
<Column>

@ -11,7 +11,7 @@ import { useLocation } from 'react-router-dom'
import { useHideBaseWalletBanner } from 'state/user/hooks'
import { ThemedText } from 'theme/components'
import { openDownloadApp, openWalletMicrosite } from 'utils/openDownloadApp'
import { isIOS, isMobileSafari } from 'utils/userAgent'
import { isAndroid, isIOS, isMobileSafari } from 'utils/userAgent'
import { BannerButton, BaseBackgroundImage, ButtonRow, PopupContainer, StyledXButton } from './styled'
@ -60,14 +60,14 @@ export default function BaseWalletBanner() {
</ThemedText.HeadlineMedium>
<ButtonRow>
{isIOS ? (
{isIOS || (isAndroidGALaunched && isAndroid) ? (
<>
<BannerButton
backgroundColor="white"
onClick={() =>
openDownloadApp({
element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON,
appStoreParams: 'pt=123625782&ct=base-app-banner&mt=8',
isAndroidGALaunched,
})
}
>
@ -77,14 +77,23 @@ export default function BaseWalletBanner() {
</ThemedText.LabelSmall>
</BannerButton>
<BannerButton backgroundColor="black" onClick={() => openWalletMicrosite()}>
<BannerButton
backgroundColor="black"
onClick={() =>
openWalletMicrosite({ element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON })
}
>
<ThemedText.LabelSmall color="white">
<Trans>Learn more</Trans>
</ThemedText.LabelSmall>
</BannerButton>
</>
) : (
<BannerButton backgroundColor="white" width="125px" onClick={() => openWalletMicrosite()}>
<BannerButton
backgroundColor="white"
width="125px"
onClick={() => openWalletMicrosite({ element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON })}
>
<ThemedText.LabelSmall color="black">
<Trans>Learn more</Trans>
</ThemedText.LabelSmall>

@ -174,7 +174,8 @@ export const MenuDropdown = () => {
<Box
onClick={() =>
openDownloadApp({
element: InterfaceElementName.UNISWAP_WALLET_MODAL_DOWNLOAD_BUTTON,
element: InterfaceElementName.UNISWAP_WALLET_NAVBAR_MENU_DOWNLOAD_BUTTON,
isAndroidGALaunched,
})
}
>

@ -6,6 +6,7 @@ import { AutoRow } from 'components/Row'
import { connections, deprecatedNetworkConnection, networkConnection } from 'connection'
import { ActivationStatus, useActivationState } from 'connection/activate'
import { isSupportedChain } from 'constants/chains'
import { useAndroidGALaunchFlagEnabled } from 'featureFlags/flags/androidGALaunch'
import { useFallbackProviderEnabled } from 'featureFlags/flags/fallbackProvider'
import { useEffect } from 'react'
import styled from 'styled-components'
@ -40,6 +41,7 @@ const PrivacyPolicyWrapper = styled.div`
export default function WalletModal({ openSettings }: { openSettings: () => void }) {
const { connector, chainId } = useWeb3React()
const isAndroidGALaunched = useAndroidGALaunchFlagEnabled()
const { activationState } = useActivationState()
const fallbackProviderEnabled = useFallbackProviderEnabled()
@ -66,7 +68,7 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
<AutoColumn gap="16px">
<OptionGrid data-testid="option-grid">
{connections
.filter((connection) => connection.shouldDisplay())
.filter((connection) => connection.shouldDisplay(isAndroidGALaunched))
.map((connection) => (
<Option key={connection.getName()} connection={connection} />
))}

@ -11,7 +11,7 @@ import COINBASE_ICON from 'assets/wallets/coinbase-icon.svg'
import UNIWALLET_ICON from 'assets/wallets/uniswap-wallet-icon.png'
import WALLET_CONNECT_ICON from 'assets/wallets/walletconnect-icon.svg'
import { useSyncExternalStore } from 'react'
import { isMobile, isNonIOSPhone } from 'utils/userAgent'
import { isMobile, isNonIOSPhone, isNonSupportedPhone } from 'utils/userAgent'
import { RPC_URLS } from '../constants/networks'
import { DEPRECATED_RPC_PROVIDERS, RPC_PROVIDERS } from '../constants/providers'
@ -149,7 +149,8 @@ export const uniwalletWCV2ConnectConnection: Connection = {
hooks: web3WCV2UniwalletConnectHooks,
type: ConnectionType.UNISWAP_WALLET_V2,
getIcon: () => UNIWALLET_ICON,
shouldDisplay: () => Boolean(!getIsInjectedMobileBrowser() && !isNonIOSPhone),
shouldDisplay: (isAndroidGALaunched) =>
Boolean(!getIsInjectedMobileBrowser() && (isAndroidGALaunched ? !isNonSupportedPhone : !isNonIOSPhone)),
}
const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(

@ -26,6 +26,6 @@ export interface Connection {
hooks: Web3ReactHooks
type: ConnectionType
getIcon?(isDarkMode: boolean): string
shouldDisplay(): boolean
shouldDisplay(isAndroidGALaunched?: boolean): boolean
overrideActivate?: (chainId?: ChainId) => boolean
}

@ -25,7 +25,7 @@ import { Z_INDEX } from 'theme/zIndex'
import { STATSIG_DUMMY_KEY } from 'tracing'
import { isPathBlocked } from 'utils/blockedPaths'
import { getEnvName } from 'utils/env'
import { getDownloadAppLink } from 'utils/openDownloadApp'
import { MICROSITE_LINK } from 'utils/openDownloadApp'
import { getCurrentPageFromLocation } from 'utils/urlRoutes'
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
@ -145,7 +145,7 @@ export default function App() {
const shouldRedirectToAppInstall = pathname?.startsWith('/address/')
useLayoutEffect(() => {
if (shouldRedirectToAppInstall) {
window.location.href = getDownloadAppLink()
window.location.href = MICROSITE_LINK
}
}, [shouldRedirectToAppInstall])

@ -2114,7 +2114,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
</div>
<a
class="c63"
href="https://wallet.uniswap.org/?utm_source=home_page&utm_medium=webapp&utm_campaign=wallet_microsite&utm_id=1"
href="https://uniswapwallet.onelink.me/8q3y/79gveilz"
>
<svg
height="20"
@ -4727,7 +4727,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
</div>
<a
class="c63"
href="https://wallet.uniswap.org/?utm_source=home_page&utm_medium=webapp&utm_campaign=wallet_microsite&utm_id=1"
href="https://uniswapwallet.onelink.me/8q3y/79gveilz"
>
<svg
height="20"

@ -1,14 +0,0 @@
import { render } from 'test-utils/render'
import Landing from '.'
jest.mock('utils/userAgent', () => {
return {
isIOS: true,
}
})
it('renders ios microsite link', () => {
const { container } = render(<Landing />)
expect(container.innerHTML.includes(`https://apps.apple.com/app/apple-store/id6443944476`)).toBeTruthy()
})

@ -1,14 +0,0 @@
import { render } from 'test-utils/render'
import Landing from '.'
jest.mock('utils/userAgent', () => {
return {
isIOS: false,
}
})
it('renders non-ios microsite link', () => {
const { container } = render(<Landing />)
expect(container.innerHTML.includes(`https://wallet.uniswap.org/?utm_source=home_page`)).toBeTruthy()
})

@ -34,3 +34,10 @@ describe('disable nft on landing page', () => {
)
})
})
describe('Uniswap wallet app download link', () => {
it('renders onelink app download', () => {
const { container } = render(<Landing />)
expect(container.innerHTML.includes('https://uniswapwallet.onelink.me/8q3y/79gveilz')).toBeTruthy()
})
})

@ -399,9 +399,8 @@ export default function Landing() {
<DownloadWalletLink
{...getDownloadAppLinkProps({
// landing page specific tracking params
microSiteParams: `utm_source=home_page&utm_medium=webapp&utm_campaign=wallet_microsite&utm_id=1`,
appStoreParams: `ct=Uniswap-Home-Page&mt=8`,
element: InterfaceElementName.UNISWAP_WALLET_LANDING_PAGE_DOWNLOAD_BUTTON,
isAndroidGALaunched,
})}
>
{isAndroidGALaunched ? (

@ -1,69 +1,68 @@
import { InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { AppDownloadPlatform, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { sendAnalyticsEvent } from 'analytics'
import { isIOS } from 'utils/userAgent'
import { isAndroid, isIOS } from 'utils/userAgent'
const APP_STORE_LINK = 'https://apps.apple.com/app/apple-store/id6443944476'
const MICROSITE_LINK = 'https://wallet.uniswap.org/'
type OpenDownloadAppOptions = {
element?: InterfaceElementName
appStoreParams?: string
microSiteParams?: string
// OneLink will direct to App/Play Store or microsite depending on user agent
const APP_DOWNLOAD_LINKS: Partial<{ [key in InterfaceElementName]: string }> = {
[InterfaceElementName.UNISWAP_WALLET_MODAL_DOWNLOAD_BUTTON]: 'https://uniswapwallet.onelink.me/8q3y/qfwlncf9',
[InterfaceElementName.UNISWAP_WALLET_NAVBAR_MENU_DOWNLOAD_BUTTON]: 'https://uniswapwallet.onelink.me/8q3y/46tvu6pb',
[InterfaceElementName.UNISWAP_WALLET_LANDING_PAGE_DOWNLOAD_BUTTON]: 'https://uniswapwallet.onelink.me/8q3y/79gveilz',
[InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON]: 'https://uniswapwallet.onelink.me/8q3y/jh9orof3',
}
const defaultDownloadAppOptions = {
appStoreParams: `pt=123625782&ct=In-App-Banners&mt=8`,
export const MICROSITE_LINK = 'https://wallet.uniswap.org/'
type OpenDownloadAppOptions = {
element: InterfaceElementName
isAndroidGALaunched: boolean
}
/**
* Note: openDownloadApp and getDownloadAppLink are equivalent functions, the first just runs imperatively
* Note: openDownloadApp is equivalent to APP_DOWNLOAD_LINKS[element], the first just runs imperatively
* and adds an analytics event, where the other only returns a link. Typically you'll use both:
*
* <a href={getDownloadAppLink(options)} onClick={() => openDownloadApp(options)} />
* <a href={APP_DOWNLOAD_LINKS[element]} onClick={() => openDownloadApp(element)} />
*
* This way with JS disabled and when hovering the <a /> you see and nav to the full href properly,
* but with JS on it will send the analytics event before navigating to the href.
*
* I've added a helper `getDownloadAppLinkProps` that unifies this behavior into one thing.
*/
export function openDownloadApp(options: OpenDownloadAppOptions = defaultDownloadAppOptions) {
export function openDownloadApp({ element, isAndroidGALaunched }: OpenDownloadAppOptions) {
if (isIOS) {
openAppStore({ element: options?.element, urlParamString: options?.appStoreParams })
openDownloadStore({ element, appPlatform: AppDownloadPlatform.IOS, linkTarget: 'uniswap_wallet_appstore' })
} else if (isAndroidGALaunched && isAndroid) {
openDownloadStore({ element, appPlatform: AppDownloadPlatform.ANDROID, linkTarget: 'uniswap_wallet_playstore' })
} else {
openWalletMicrosite({ element: options?.element, urlParamString: options?.microSiteParams })
openWalletMicrosite({ element })
}
}
// if you need this by itself can add export, not used externally for now
export const getDownloadAppLink = (options: OpenDownloadAppOptions = defaultDownloadAppOptions) =>
isIOS
? linkWithParams(APP_STORE_LINK, options?.appStoreParams)
: linkWithParams(MICROSITE_LINK, options?.microSiteParams)
export const getDownloadAppLinkProps = (options: OpenDownloadAppOptions = defaultDownloadAppOptions) => {
export const getDownloadAppLinkProps = ({ element, isAndroidGALaunched }: OpenDownloadAppOptions) => {
return {
href: getDownloadAppLink(options),
href: APP_DOWNLOAD_LINKS[element],
onClick(e: { preventDefault: () => void }) {
e.preventDefault()
openDownloadApp(options)
openDownloadApp({ element, isAndroidGALaunched })
},
}
}
type AnalyticsLinkOptions = {
element?: InterfaceElementName
urlParamString?: string
element: InterfaceElementName
appPlatform?: AppDownloadPlatform
linkTarget?: string
}
const openAppStore = (options?: AnalyticsLinkOptions) => {
sendAnalyticsEvent(InterfaceEventName.UNISWAP_WALLET_APP_DOWNLOAD_OPENED, { element: options?.element })
window.open(linkWithParams(APP_STORE_LINK, options?.urlParamString), /* target = */ 'uniswap_wallet_appstore')
const openDownloadStore = (options: AnalyticsLinkOptions) => {
sendAnalyticsEvent(InterfaceEventName.UNISWAP_WALLET_APP_DOWNLOAD_OPENED, {
element: options.element,
appPlatform: options?.appPlatform,
})
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(linkWithParams(MICROSITE_LINK, options?.urlParamString), /* target = */ 'uniswap_wallet_microsite')
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')
}
const linkWithParams = (link: string, params?: string) => link + (params ? `?${params}` : '')

@ -7,6 +7,8 @@ const { name } = parser.getBrowser()
export const isMobile = type === 'mobile' || type === 'tablet'
const platform = parser.getOS().name
export const isIOS = platform === 'iOS'
export const isAndroid = platform === 'Android'
export const isNonIOSPhone = !isIOS && type === 'mobile'
export const isNonSupportedPhone = !isIOS && !isAndroid && type === 'mobile'
export const isMobileSafari = isMobile && isIOS && name?.toLowerCase().includes('safari')

@ -6059,10 +6059,10 @@
"@typescript-eslint/types" "5.59.1"
eslint-visitor-keys "^3.3.0"
"@uniswap/analytics-events@^2.25.0":
version "2.25.0"
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.25.0.tgz#06f2d81342b2e4dc516bdfa1222ddaa7c274ac04"
integrity sha512-0syw7gZtoHXSCVb+zV464L+Zgy1ICnGDOrbK2xoVtpiQ8rBjUXPWvcKuaiNPfTsS9tIZNtqOmEyZEjWwvFSLUw==
"@uniswap/analytics-events@^2.28.0":
version "2.28.0"
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.28.0.tgz#344fbbe3e120b7f8100e4d540004847f7147cf6e"
integrity sha512-j650l315p9W5dbVPzHRu485omyZw+I6jU/ZGpzuz4OdrJIp8yCl57LTLFfmMsZGwmJYDlOxY2fB6Jrbc2lUvUA==
"@uniswap/analytics@1.5.0":
version "1.5.0"