ci(release): publish latest release

parent e8cdfff8
...@@ -21,6 +21,7 @@ An open source repository for all Uniswap front end interfaces maintained by Uni ...@@ -21,6 +21,7 @@ An open source repository for all Uniswap front end interfaces maintained by Uni
## Whitepapers ## Whitepapers
- [V4](https://uniswap.org/whitepaper-v4.pdf)
- [V3](https://uniswap.org/whitepaper-v3.pdf) - [V3](https://uniswap.org/whitepaper-v3.pdf)
- [V2](https://uniswap.org/whitepaper.pdf) - [V2](https://uniswap.org/whitepaper.pdf)
- [V1](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig) - [V1](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
......
IPFS hash of the deployment: IPFS hash of the deployment:
- CIDv0: `QmRHsud7WNRFUg4cTcXRxuwZYZRAhYMkYAcA97mtqwq9re` - CIDv0: `QmXkPV7sagZDwbUR5PRBNWAvFrJSuTUh5Fu1aJpFA1whLV`
- CIDv1: `bafybeibl3u67jwwr3vxssx7z6bbz35bk333qe6xdnkruce7pmma5uf2z74` - CIDv1: `bafybeielzxuhhhlrf7rtyfrqtzqawka7oyjb3qklvfvcexvwdumpsnujdi`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
...@@ -10,15 +10,61 @@ You can also access the Uniswap Interface from an IPFS gateway. ...@@ -10,15 +10,61 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs. Your Uniswap settings are never remembered across different URLs.
IPFS gateways: IPFS gateways:
- https://bafybeibl3u67jwwr3vxssx7z6bbz35bk333qe6xdnkruce7pmma5uf2z74.ipfs.dweb.link/ - https://bafybeielzxuhhhlrf7rtyfrqtzqawka7oyjb3qklvfvcexvwdumpsnujdi.ipfs.dweb.link/
- https://bafybeibl3u67jwwr3vxssx7z6bbz35bk333qe6xdnkruce7pmma5uf2z74.ipfs.cf-ipfs.com/ - https://bafybeielzxuhhhlrf7rtyfrqtzqawka7oyjb3qklvfvcexvwdumpsnujdi.ipfs.cf-ipfs.com/
- [ipfs://QmRHsud7WNRFUg4cTcXRxuwZYZRAhYMkYAcA97mtqwq9re/](ipfs://QmRHsud7WNRFUg4cTcXRxuwZYZRAhYMkYAcA97mtqwq9re/) - [ipfs://QmXkPV7sagZDwbUR5PRBNWAvFrJSuTUh5Fu1aJpFA1whLV/](ipfs://QmXkPV7sagZDwbUR5PRBNWAvFrJSuTUh5Fu1aJpFA1whLV/)
### 5.64.2 (2025-01-13) ## 5.65.0 (2025-01-15)
### Features
* **web:** [EW] Add passkey sign in (#13883) 01fd45c
* **web:** [EW] Add show recovery phrase modal (#13894) 05748a7
* **web:** [EW] Add sign up (#13862) aa9771e
* **web:** [EW] Add signing txs (#13890) c779f0c
* **web:** add the v4 whitepaper (#14867) e87e686
* **web:** delete unused background updating code (#14612) aa26058
* **web:** replace usage of DoubleLogoWithChain with DoubleLogo (#15011) e723da8
* **web:** update uniswapx-sdk (#14654) ab645c7
* **web:** use dynamic config for blocked nft collections (#14643) a748576
### Bug Fixes ### Bug Fixes
* **web:** fix infinite loop on analytics toggle 3a2b4dc * **web:** 5859 get position owner from get position call (#14729) b412b95
* **web:** align chart headers in explore (#14830) 4a2e838
* **web:** consistent lp buttons (#14664) 4c3c0d9
* **web:** eth logo flash (#15013) 486ab05
* **web:** filter toggle behavior on TDP (#14898) dbebd9a
* **web:** fix crash on v4 create (#14891) 53b84d8
* **web:** fix double logo on DoubleCurrencyAndChainLogo (#14946) 0579dcf
* **web:** fix infinite loop on analytics toggle f4b5fde
* **web:** fix infinite loop on analytics toggle (#15068) ad1a014
* **web:** fix pdp price display (#14902) 2f00216
* **web:** fix range display for v4 positions in custom fee tiers (#14904) 3be2db0
* **web:** hero title bug when switching languages (#15022) d6511f5
* **web:** inverted initial price bug (#14994) 017e47e
* **web:** limit orders do not work with uniswapx v2 (#14995) b492069
* **web:** make the swap primary colors on the tdp use token extraction colors (#14696) ea7f43b
* **web:** move both price toggles together in the create flow (#14971) 8d8f595
* **web:** re-add images prop to PortfolioLogo and use split logo (#14864) 840edff
* **web:** reduce size impact of ui icons (#14613) f89a87e
* **web:** remove direct t imports and fix translations outside of React context on web (#14829) e040834
* **web:** remove worldchain minikit provider (#14544) 6794856
* **web:** scroll on nav dropdown and adjust max height (#15094) 3bbd575
* **web:** send speed bump font size fix (#15004) 0bd82df
* **web:** update google conversion datetime format (#15131) 6a7ca9a
* **web:** update input behavior on send tab (#14843) 8c4eab7
### Continuous Integration
* **web:** update sitemaps 8e5f49e
### Styles
* **web:** round ends of loading spinner component (#14779) 26b3a8b
web/5.64.2 web/5.65.0
\ No newline at end of file \ No newline at end of file
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
"@tamagui/core": "1.114.4", "@tamagui/core": "1.114.4",
"@types/uuid": "9.0.1", "@types/uuid": "9.0.1",
"@uniswap/analytics-events": "2.40.0", "@uniswap/analytics-events": "2.40.0",
"@uniswap/uniswapx-sdk": "2.1.0-beta.18", "@uniswap/uniswapx-sdk": "3.0.0-beta.1",
"@uniswap/universal-router-sdk": "4.7.0", "@uniswap/universal-router-sdk": "4.7.0",
"@uniswap/v3-sdk": "3.19.0", "@uniswap/v3-sdk": "3.19.0",
"@uniswap/v4-sdk": "1.12.0", "@uniswap/v4-sdk": "1.12.0",
...@@ -91,8 +91,8 @@ ...@@ -91,8 +91,8 @@
"build:production": "webpack --node-env=production --env BUILD_ENV=prod BUILD_NUM=${BUILD_NUM:-0}", "build:production": "webpack --node-env=production --env BUILD_ENV=prod BUILD_NUM=${BUILD_NUM:-0}",
"check:circular": "concurrently \"../../scripts/check-circular-imports.sh ./src/entry/sidebar.tsx 1\" \"../../scripts/check-circular-imports.sh ./src/entry/onboarding.tsx 1\" \"../../scripts/check-circular-imports.sh ./src/entry/unitagClaim.tsx 1\"", "check:circular": "concurrently \"../../scripts/check-circular-imports.sh ./src/entry/sidebar.tsx 1\" \"../../scripts/check-circular-imports.sh ./src/entry/onboarding.tsx 1\" \"../../scripts/check-circular-imports.sh ./src/entry/unitagClaim.tsx 1\"",
"check:deps:usage": "depcheck", "check:deps:usage": "depcheck",
"env:local:download": "bash ../../scripts/downloadEnvLocal.sh web-local-envs ../../.env", "env:local:download": "bash ../../scripts/downloadEnvLocal.sh m4dhqfltt3dokkqi3hqwigmf2a ../../.env",
"env:local:upload": "bash ../../scripts/uploadEnvLocal.sh web-local-envs ../../.env", "env:local:upload": "bash ../../scripts/uploadEnvLocal.sh m4dhqfltt3dokkqi3hqwigmf2a ../../.env",
"format": "../../scripts/prettier.sh", "format": "../../scripts/prettier.sh",
"lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --ext ts,tsx --max-warnings=0", "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --ext ts,tsx --max-warnings=0",
"lint:fix": "NODE_OPTIONS=--max-old-space-size=8192 eslint . --ext ts,tsx --fix", "lint:fix": "NODE_OPTIONS=--max-old-space-size=8192 eslint . --ext ts,tsx --fix",
......
...@@ -14,7 +14,7 @@ import { DappContextProvider } from 'src/app/features/dapp/DappContext' ...@@ -14,7 +14,7 @@ import { DappContextProvider } from 'src/app/features/dapp/DappContext'
import { SentryAppNameTag, initializeSentry, sentryCreateHashRouter } from 'src/app/sentry' import { SentryAppNameTag, initializeSentry, sentryCreateHashRouter } from 'src/app/sentry'
import { initExtensionAnalytics } from 'src/app/utils/analytics' import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { getReduxPersistor, getReduxStore } from 'src/store/store' import { getReduxPersistor, getReduxStore } from 'src/store/store'
import { Button, Flex, Image, Text } from 'ui/src' import { DeprecatedButton, Flex, Image, Text } from 'ui/src'
import { CHROME_LOGO, UNISWAP_LOGO } from 'ui/src/assets' import { CHROME_LOGO, UNISWAP_LOGO } from 'ui/src/assets'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext' import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext'
...@@ -98,7 +98,7 @@ function PopupContent(): JSX.Element { ...@@ -98,7 +98,7 @@ function PopupContent(): JSX.Element {
<Flex fill /> <Flex fill />
<Trace logPress element={ElementName.ExtensionPopupOpenButton}> <Trace logPress element={ElementName.ExtensionPopupOpenButton}>
<Button <DeprecatedButton
theme="primary" theme="primary"
width="100%" width="100%"
onPress={async () => { onPress={async () => {
...@@ -110,7 +110,7 @@ function PopupContent(): JSX.Element { ...@@ -110,7 +110,7 @@ function PopupContent(): JSX.Element {
}} }}
> >
{t('extension.popup.chrome.button')} {t('extension.popup.chrome.button')}
</Button> </DeprecatedButton>
</Trace> </Trace>
</Flex> </Flex>
</Trace> </Trace>
......
...@@ -2,7 +2,7 @@ import { forwardRef } from 'react' ...@@ -2,7 +2,7 @@ import { forwardRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { TextInput } from 'react-native' import { TextInput } from 'react-native'
import { Input, InputProps } from 'src/app/components/Input' import { Input, InputProps } from 'src/app/components/Input'
import { Button, Flex, FlexProps, IconProps, Text } from 'ui/src' import { DeprecatedButton, Flex, FlexProps, IconProps, Text } from 'ui/src'
import { Eye, EyeOff } from 'ui/src/components/icons' import { Eye, EyeOff } from 'ui/src/components/icons'
import { PasswordStrength, getPasswordStrengthTextAndColor } from 'wallet/src/utils/password' import { PasswordStrength, getPasswordStrengthTextAndColor } from 'wallet/src/utils/password'
...@@ -34,7 +34,7 @@ export const PasswordInput = forwardRef<TextInput, PasswordInputProps>(function ...@@ -34,7 +34,7 @@ export const PasswordInput = forwardRef<TextInput, PasswordInputProps>(function
<StrengthIndicator strength={passwordStrength} /> <StrengthIndicator strength={passwordStrength} />
) : ( ) : (
onToggleHideInput && ( onToggleHideInput && (
<Button <DeprecatedButton
backgroundColor="$transparent" backgroundColor="$transparent"
hoverStyle={hoverStyle} hoverStyle={hoverStyle}
position="absolute" position="absolute"
...@@ -43,7 +43,7 @@ export const PasswordInput = forwardRef<TextInput, PasswordInputProps>(function ...@@ -43,7 +43,7 @@ export const PasswordInput = forwardRef<TextInput, PasswordInputProps>(function
onPress={(): void => onToggleHideInput(!hideInput)} onPress={(): void => onToggleHideInput(!hideInput)}
> >
{hideInput ? <Eye {...iconProps} /> : <EyeOff {...iconProps} />} {hideInput ? <Eye {...iconProps} /> : <EyeOff {...iconProps} />}
</Button> </DeprecatedButton>
) )
)} )}
</Flex> </Flex>
......
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { Anchor, Button, Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Anchor, DeprecatedButton, Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { X } from 'ui/src/components/icons' import { X } from 'ui/src/components/icons'
import { zIndices } from 'ui/src/theme' import { zIndices } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -60,9 +60,9 @@ export function InfoModal({ ...@@ -60,9 +60,9 @@ export function InfoModal({
{description} {description}
</Text> </Text>
</Flex> </Flex>
<Button size="medium" theme={buttonTheme} width="100%" onPress={onButtonPress}> <DeprecatedButton size="medium" theme={buttonTheme} width="100%" onPress={onButtonPress}>
{buttonText} {buttonText}
</Button> </DeprecatedButton>
{linkText && linkUrl && ( {linkText && linkUrl && (
<Anchor href={linkUrl} lineHeight={16} p="$spacing12" target="_blank" textDecorationLine="none"> <Anchor href={linkUrl} lineHeight={16} p="$spacing12" target="_blank" textDecorationLine="none">
<Text color="$neutral2" textAlign="center" variant="buttonLabel3"> <Text color="$neutral2" textAlign="center" variant="buttonLabel3">
......
...@@ -15,7 +15,7 @@ import { PopupName, openPopup } from 'src/app/features/popups/slice' ...@@ -15,7 +15,7 @@ import { PopupName, openPopup } from 'src/app/features/popups/slice'
import { AppRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes, UnitagClaimRoutes } from 'src/app/navigation/constants' import { AppRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes, UnitagClaimRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { focusOrCreateUnitagTab } from 'src/app/navigation/utils' import { focusOrCreateUnitagTab } from 'src/app/navigation/utils'
import { Button, Flex, Popover, ScrollView, Text, useSporeColors } from 'ui/src' import { DeprecatedButton, Flex, Popover, ScrollView, Text, useSporeColors } from 'ui/src'
import { WalletFilled, X } from 'ui/src/components/icons' import { WalletFilled, X } from 'ui/src/components/icons'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal'
...@@ -201,14 +201,14 @@ export function AccountSwitcherScreen(): JSX.Element { ...@@ -201,14 +201,14 @@ export function AccountSwitcherScreen(): JSX.Element {
{activeAccountHasUnitag ? ( {activeAccountHasUnitag ? (
<UnitagActionButton /> <UnitagActionButton />
) : ( ) : (
<Button <DeprecatedButton
size="small" size="small"
testID={TestID.AccountCard} testID={TestID.AccountCard}
theme="secondary" theme="secondary"
onPress={() => setShowEditLabelModal(true)} onPress={() => setShowEditLabelModal(true)}
> >
{t('account.wallet.header.button.title')} {t('account.wallet.header.button.title')}
</Button> </DeprecatedButton>
)} )}
</Flex> </Flex>
<ScrollView backgroundColor="$surface1" height="auto"> <ScrollView backgroundColor="$surface1" height="auto">
...@@ -291,17 +291,23 @@ const UnitagActionButton = (): JSX.Element => { ...@@ -291,17 +291,23 @@ const UnitagActionButton = (): JSX.Element => {
if (isClaimUnitagEnabled) { if (isClaimUnitagEnabled) {
return ( return (
<Button color="$neutral1" size="small" testID={TestID.AccountCard} theme="tertiary" onPress={onPressEditProfile}> <DeprecatedButton
color="$neutral1"
size="small"
testID={TestID.AccountCard}
theme="tertiary"
onPress={onPressEditProfile}
>
{t('account.wallet.header.button.disabled.title')} {t('account.wallet.header.button.disabled.title')}
</Button> </DeprecatedButton>
) )
} }
return ( return (
<ComingSoon placement="top"> <ComingSoon placement="top">
<Button color="$neutral2" disabled={true} size="small" testID={TestID.AccountCard} theme="secondary"> <DeprecatedButton color="$neutral2" disabled={true} size="small" testID={TestID.AccountCard} theme="secondary">
{t('account.wallet.header.button.disabled.title')} {t('account.wallet.header.button.disabled.title')}
</Button> </DeprecatedButton>
</ComingSoon> </ComingSoon>
) )
} }
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { OpaqueColorValue } from 'react-native' import { OpaqueColorValue } from 'react-native'
import { Button, Flex, Text, getUniconColors, useIsDarkMode } from 'ui/src' import { DeprecatedButton, Flex, Text, getUniconColors, useIsDarkMode } from 'ui/src'
import { iconSizes, opacify } from 'ui/src/theme' import { iconSizes, opacify } from 'ui/src/theme'
import { TextInput } from 'uniswap/src/components/input/TextInput' import { TextInput } from 'uniswap/src/components/input/TextInput'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -44,7 +44,7 @@ export function CreateWalletModal({ ...@@ -44,7 +44,7 @@ export function CreateWalletModal({
? getUniconColors(onboardingAccountAddress, isDark) ? getUniconColors(onboardingAccountAddress, isDark)
: { color: '' } : { color: '' }
// Cast because Button component doesnt acccept sytling outside of theme color values for hover and press states // Cast because DeprecatedButton component doesnt acccept sytling outside of theme color values for hover and press states
const hoverAndPressButtonStyle = useMemo(() => { const hoverAndPressButtonStyle = useMemo(() => {
return { return {
backgroundColor: opacify(15, uniconColor) as unknown as OpaqueColorValue, backgroundColor: opacify(15, uniconColor) as unknown as OpaqueColorValue,
...@@ -76,10 +76,10 @@ export function CreateWalletModal({ ...@@ -76,10 +76,10 @@ export function CreateWalletModal({
</Flex> </Flex>
<Flex centered fill row gap="$spacing12" justifyContent="space-between" width="100%"> <Flex centered fill row gap="$spacing12" justifyContent="space-between" width="100%">
<Button color="$neutral1" flex={1} flexBasis={1} size="small" theme="secondary" onPress={onCancel}> <DeprecatedButton color="$neutral1" flex={1} flexBasis={1} size="small" theme="secondary" onPress={onCancel}>
{t('common.button.cancel')} {t('common.button.cancel')}
</Button> </DeprecatedButton>
<Button <DeprecatedButton
flex={1} flex={1}
flexBasis={1} flexBasis={1}
hoverStyle={hoverAndPressButtonStyle} hoverStyle={hoverAndPressButtonStyle}
...@@ -89,7 +89,7 @@ export function CreateWalletModal({ ...@@ -89,7 +89,7 @@ export function CreateWalletModal({
onPress={onPressConfirm} onPress={onPressConfirm}
> >
{t('common.button.create')} {t('common.button.create')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</Modal> </Modal>
......
...@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' ...@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { UnitagClaimRoutes } from 'src/app/navigation/constants' import { UnitagClaimRoutes } from 'src/app/navigation/constants'
import { focusOrCreateUnitagTab } from 'src/app/navigation/utils' import { focusOrCreateUnitagTab } from 'src/app/navigation/utils'
import { Button, Flex, Text } from 'ui/src' import { DeprecatedButton, Flex, Text } from 'ui/src'
import { Person } from 'ui/src/components/icons' import { Person } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { TextInput } from 'uniswap/src/components/input/TextInput' import { TextInput } from 'uniswap/src/components/input/TextInput'
...@@ -100,12 +100,12 @@ export function EditLabelModal({ isOpen, address, onClose }: EditLabelModalProps ...@@ -100,12 +100,12 @@ export function EditLabelModal({ isOpen, address, onClose }: EditLabelModalProps
</Text> </Text>
</Flex> </Flex>
<Flex centered fill row gap="$spacing12" justifyContent="space-between" width="100%"> <Flex centered fill row gap="$spacing12" justifyContent="space-between" width="100%">
<Button color="$neutral1" flex={1} flexBasis={1} size="small" theme="secondary" onPress={onClose}> <DeprecatedButton color="$neutral1" flex={1} flexBasis={1} size="small" theme="secondary" onPress={onClose}>
{t('common.button.cancel')} {t('common.button.cancel')}
</Button> </DeprecatedButton>
<Button flex={1} flexBasis={1} size="small" theme="accentSecondary" onPress={onConfirm}> <DeprecatedButton flex={1} flexBasis={1} size="small" theme="accentSecondary" onPress={onConfirm}>
{t('common.button.save')} {t('common.button.save')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</Modal> </Modal>
......
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { Button, Flex, Text, TouchableArea } from 'ui/src' import { DeprecatedButton, Flex, Text, TouchableArea } from 'ui/src'
import { Feedback, LikeSquare, MessageText, X } from 'ui/src/components/icons' import { Feedback, LikeSquare, MessageText, X } from 'ui/src/components/icons'
import { IconSizeTokens, zIndices } from 'ui/src/theme' import { IconSizeTokens, zIndices } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -133,12 +133,12 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El ...@@ -133,12 +133,12 @@ export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.El
</Text> </Text>
</Flex> </Flex>
<Flex row width="100%" gap="$spacing12"> <Flex row width="100%" gap="$spacing12">
<Button flex={1} flexBasis={1} size="small" theme="secondary" onPress={onSecondaryButtonPress}> <DeprecatedButton flex={1} flexBasis={1} size="small" theme="secondary" onPress={onSecondaryButtonPress}>
{secondaryButtonText} {secondaryButtonText}
</Button> </DeprecatedButton>
<Button flex={1} flexBasis={1} size="small" theme="primary" onPress={onPrimaryButtonPress}> <DeprecatedButton flex={1} flexBasis={1} size="small" theme="primary" onPress={onPrimaryButtonPress}>
{primaryButtonText} {primaryButtonText}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</Modal> </Modal>
......
...@@ -4,7 +4,16 @@ import { useDappLastChainId } from 'src/app/features/dapp/hooks' ...@@ -4,7 +4,16 @@ import { useDappLastChainId } from 'src/app/features/dapp/hooks'
import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRequestQueueContext' import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRequestQueueContext'
import { DappRequestStoreItem } from 'src/app/features/dappRequests/slice' import { DappRequestStoreItem } from 'src/app/features/dappRequests/slice'
import { DappRequestType } from 'src/app/features/dappRequests/types/DappRequestTypes' import { DappRequestType } from 'src/app/features/dappRequests/types/DappRequestTypes'
import { Anchor, AnimatePresence, Button, Flex, Text, UniversalImage, UniversalImageResizeMode, styled } from 'ui/src' import {
Anchor,
AnimatePresence,
DeprecatedButton,
Flex,
Text,
UniversalImage,
UniversalImageResizeMode,
styled,
} from 'ui/src'
import { borderRadii, iconSizes } from 'ui/src/theme' import { borderRadii, iconSizes } from 'ui/src/theme'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
...@@ -240,10 +249,10 @@ export function DappRequestFooter({ ...@@ -240,10 +249,10 @@ export function DappRequestFooter({
px="$spacing8" px="$spacing8"
/> />
<Flex row gap="$spacing12" pt="$spacing8"> <Flex row gap="$spacing12" pt="$spacing8">
<Button flex={1} flexBasis={1} size="medium" theme="secondary" onPress={handleOnCancel}> <DeprecatedButton flex={1} flexBasis={1} size="medium" theme="secondary" onPress={handleOnCancel}>
{t('common.button.cancel')} {t('common.button.cancel')}
</Button> </DeprecatedButton>
<Button <DeprecatedButton
disabled={!isConfirmEnabled} disabled={!isConfirmEnabled}
flex={1} flex={1}
flexBasis={1} flexBasis={1}
...@@ -252,7 +261,7 @@ export function DappRequestFooter({ ...@@ -252,7 +261,7 @@ export function DappRequestFooter({
onPress={handleOnConfirm} onPress={handleOnConfirm}
> >
{confirmText} {confirmText}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</> </>
......
...@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react' ...@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { DappRequestContent } from 'src/app/features/dappRequests/DappRequestContent' import { DappRequestContent } from 'src/app/features/dappRequests/DappRequestContent'
import { SignMessageRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' import { SignMessageRequest } from 'src/app/features/dappRequests/types/DappRequestTypes'
import { Button, Flex, Text, Tooltip } from 'ui/src' import { DeprecatedButton, Flex, Text, Tooltip } from 'ui/src'
import { AlertTriangleFilled, Code, StickyNoteTextSquare } from 'ui/src/components/icons' import { AlertTriangleFilled, Code, StickyNoteTextSquare } from 'ui/src/components/icons'
import { containsNonPrintableChars } from 'utilities/src/primitives/string' import { containsNonPrintableChars } from 'utilities/src/primitives/string'
...@@ -86,7 +86,7 @@ export function PersonalSignRequestContent({ dappRequest }: PersonalSignRequestP ...@@ -86,7 +86,7 @@ export function PersonalSignRequestContent({ dappRequest }: PersonalSignRequestP
right={isScrollable ? '$spacing24' : '$spacing12'} right={isScrollable ? '$spacing24' : '$spacing12'}
> >
<Tooltip.Trigger> <Tooltip.Trigger>
<Button <DeprecatedButton
borderColor="$surface3" borderColor="$surface3"
borderRadius="$rounded4" borderRadius="$rounded4"
icon={ icon={
......
import { useTranslation } from 'react-i18next'
import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { SeedPhraseDisplay } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/SeedPhraseDisplay'
import { Flex, Text } from 'ui/src'
import { GraduationCap } from 'ui/src/components/icons/GraduationCap'
import { ForceUpgrade } from 'wallet/src/features/forceUpgrade/ForceUpgrade'
function SeedPhraseModalContent({ mnemonicId, onDismiss }: { mnemonicId: string; onDismiss: () => void }): JSX.Element {
const { t } = useTranslation()
return (
<Flex fill gap="$spacing24" py="$spacing8">
<ScreenHeader title={t('forceUpgrade.label.recoveryPhrase')} onBackClick={onDismiss} />
<Flex gap="$spacing16">
<SeedPhraseDisplay mnemonicId={mnemonicId} />
<Flex
row
alignItems="center"
backgroundColor="$surface2"
borderRadius="$rounded16"
gap="$spacing8"
p="$spacing12"
>
<Flex>
<GraduationCap color="$neutral2" size="$icon.20" />
</Flex>
<Flex shrink>
<Text color="$neutral2" variant="body4">
{t('onboarding.backup.manual.banner')}
</Text>
</Flex>
</Flex>
</Flex>
</Flex>
)
}
export function ForceUpgradeModal(): JSX.Element {
return <ForceUpgrade SeedPhraseModalContent={SeedPhraseModalContent} />
}
...@@ -7,6 +7,7 @@ import { ActivityTab } from 'src/app/components/tabs/ActivityTab' ...@@ -7,6 +7,7 @@ import { ActivityTab } from 'src/app/components/tabs/ActivityTab'
import { NftsTab } from 'src/app/components/tabs/NftsTab' import { NftsTab } from 'src/app/components/tabs/NftsTab'
import AppRatingModal from 'src/app/features/appRating/AppRatingModal' import AppRatingModal from 'src/app/features/appRating/AppRatingModal'
import { useAppRating } from 'src/app/features/appRating/hooks/useAppRating' import { useAppRating } from 'src/app/features/appRating/hooks/useAppRating'
import { ForceUpgradeModal } from 'src/app/features/forceUpgrade/ForceUpgradeModal'
import { PortfolioActionButtons } from 'src/app/features/home/PortfolioActionButtons' import { PortfolioActionButtons } from 'src/app/features/home/PortfolioActionButtons'
import { PortfolioHeader } from 'src/app/features/home/PortfolioHeader' import { PortfolioHeader } from 'src/app/features/home/PortfolioHeader'
import { TokenBalanceList } from 'src/app/features/home/TokenBalanceList' import { TokenBalanceList } from 'src/app/features/home/TokenBalanceList'
...@@ -198,6 +199,7 @@ export const HomeScreen = memo(function _HomeScreen(): JSX.Element { ...@@ -198,6 +199,7 @@ export const HomeScreen = memo(function _HomeScreen(): JSX.Element {
</Text> </Text>
)} )}
{appRatingModalVisible && <AppRatingModal onClose={onAppRatingModalClose} />} {appRatingModalVisible && <AppRatingModal onClose={onAppRatingModalClose} />}
<ForceUpgradeModal />
</Flex> </Flex>
) )
}) })
......
...@@ -4,7 +4,7 @@ import { useDappContext } from 'src/app/features/dapp/DappContext' ...@@ -4,7 +4,7 @@ import { useDappContext } from 'src/app/features/dapp/DappContext'
import { removeDappConnection, saveDappChain } from 'src/app/features/dapp/actions' import { removeDappConnection, saveDappChain } from 'src/app/features/dapp/actions'
import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { useDappLastChainId } from 'src/app/features/dapp/hooks'
import { PopupName, closePopup } from 'src/app/features/popups/slice' import { PopupName, closePopup } from 'src/app/features/popups/slice'
import { Anchor, Button, Flex, Popover, Separator, Text, getTokenValue } from 'ui/src' import { Anchor, DeprecatedButton, Flex, Popover, Separator, Text, getTokenValue } from 'ui/src'
import { Check, Power } from 'ui/src/components/icons' import { Check, Power } from 'ui/src/components/icons'
import { usePreventOverflowBelowFold } from 'ui/src/hooks/usePreventOverflowBelowFold' import { usePreventOverflowBelowFold } from 'ui/src/hooks/usePreventOverflowBelowFold'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
...@@ -76,7 +76,7 @@ export function SwitchNetworksModal(): JSX.Element { ...@@ -76,7 +76,7 @@ export function SwitchNetworksModal(): JSX.Element {
{enabledChains.map((chain: UniverseChainId) => { {enabledChains.map((chain: UniverseChainId) => {
return ( return (
<Popover.Close asChild> <Popover.Close asChild>
<Button <DeprecatedButton
key={chain} key={chain}
borderRadius="$rounded12" borderRadius="$rounded12"
justifyContent="space-between" justifyContent="space-between"
...@@ -98,14 +98,14 @@ export function SwitchNetworksModal(): JSX.Element { ...@@ -98,14 +98,14 @@ export function SwitchNetworksModal(): JSX.Element {
</Flex> </Flex>
) : null} ) : null}
</Flex> </Flex>
</Button> </DeprecatedButton>
</Popover.Close> </Popover.Close>
) )
})} })}
</Flex> </Flex>
<Popover.Close asChild> <Popover.Close asChild>
<Button mt="$spacing8" size="small" theme="tertiary" onPress={onDisconnect}> <DeprecatedButton mt="$spacing8" size="small" theme="tertiary" onPress={onDisconnect}>
<Flex centered row gap="$spacing8"> <Flex centered row gap="$spacing8">
<Power color="$neutral1" size={getTokenValue('$icon.16')} /> <Power color="$neutral1" size={getTokenValue('$icon.16')} />
{/* TODO(EXT-207 / EXT-208): fix button component styling and derive text color from theme */}{' '} {/* TODO(EXT-207 / EXT-208): fix button component styling and derive text color from theme */}{' '}
...@@ -113,7 +113,7 @@ export function SwitchNetworksModal(): JSX.Element { ...@@ -113,7 +113,7 @@ export function SwitchNetworksModal(): JSX.Element {
{t('common.button.disconnect')} {t('common.button.disconnect')}
</Text> </Text>
</Flex> </Flex>
</Button> </DeprecatedButton>
</Popover.Close> </Popover.Close>
</Flex> </Flex>
) )
......
import { SharedEventName } from '@uniswap/analytics-events'
import { PropsWithChildren, memo, useState } from 'react' import { PropsWithChildren, memo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useInterfaceBuyNavigator } from 'src/app/features/for/utils' import { useInterfaceBuyNavigator } from 'src/app/features/for/utils'
...@@ -10,13 +9,11 @@ import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' ...@@ -10,13 +9,11 @@ import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { InfoLinkModal } from 'uniswap/src/components/modals/InfoLinkModal' import { InfoLinkModal } from 'uniswap/src/components/modals/InfoLinkModal'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { PortfolioBalance } from 'uniswap/src/features/dataApi/types'
import { ElementName, ModalName, SectionName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { InformationBanner } from 'wallet/src/components/banners/InformationBanner' import { InformationBanner } from 'wallet/src/components/banners/InformationBanner'
import { ContextMenu } from 'wallet/src/components/menu/ContextMenu' import { ContextMenu } from 'wallet/src/components/menu/ContextMenu'
import { useWalletNavigation } from 'wallet/src/contexts/WalletNavigationContext'
import { isNonPollingRequestInFlight } from 'wallet/src/data/utils' import { isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { HiddenTokensRow } from 'wallet/src/features/portfolio/HiddenTokensRow' import { HiddenTokensRow } from 'wallet/src/features/portfolio/HiddenTokensRow'
import { PortfolioEmptyState } from 'wallet/src/features/portfolio/PortfolioEmptyState' import { PortfolioEmptyState } from 'wallet/src/features/portfolio/PortfolioEmptyState'
...@@ -36,25 +33,9 @@ type TokenBalanceListProps = { ...@@ -36,25 +33,9 @@ type TokenBalanceListProps = {
} }
export const TokenBalanceList = memo(function _TokenBalanceList({ owner }: TokenBalanceListProps): JSX.Element { export const TokenBalanceList = memo(function _TokenBalanceList({ owner }: TokenBalanceListProps): JSX.Element {
const { navigateToTokenDetails } = useWalletNavigation()
const { isTestnetModeEnabled } = useEnabledChains()
const onPressToken = (currencyId: string): void => {
if (isTestnetModeEnabled) {
return
}
sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, {
element: ElementName.TokenItem,
section: SectionName.HomeTokensTab,
})
navigateToTokenDetails(currencyId)
}
return ( return (
<Flex grow> <Flex grow>
<TokenBalanceListContextProvider isExternalProfile={false} owner={owner} onPressToken={onPressToken}> <TokenBalanceListContextProvider isExternalProfile={false} owner={owner} onPressToken={() => {}}>
<TokenBalanceListInner /> <TokenBalanceListInner />
</TokenBalanceListContextProvider> </TokenBalanceListContextProvider>
</Flex> </Flex>
...@@ -246,7 +227,13 @@ function TokenContextMenu({ ...@@ -246,7 +227,13 @@ function TokenContextMenu({
const itemId = `${portfolioBalance.currencyInfo.currencyId}-${portfolioBalance.isHidden}` const itemId = `${portfolioBalance.currencyInfo.currencyId}-${portfolioBalance.isHidden}`
return ( return (
<ContextMenu itemId={itemId} menuOptions={menuOptions} menuStyleProps={{ minWidth: MIN_CONTEXT_MENU_WIDTH }}> <ContextMenu
closeOnClick
itemId={itemId}
menuOptions={menuOptions}
menuStyleProps={{ minWidth: MIN_CONTEXT_MENU_WIDTH }}
onLeftClick
>
{children} {children}
</ContextMenu> </ContextMenu>
) )
......
...@@ -7,7 +7,7 @@ import { InfoModal, ModalProps } from 'src/app/components/modal/InfoModal' ...@@ -7,7 +7,7 @@ import { InfoModal, ModalProps } from 'src/app/components/modal/InfoModal'
import { useSagaStatus } from 'src/app/hooks/useSagaStatus' import { useSagaStatus } from 'src/app/hooks/useSagaStatus'
import { OnboardingRoutes, TopLevelRoutes } from 'src/app/navigation/constants' import { OnboardingRoutes, TopLevelRoutes } from 'src/app/navigation/constants'
import { focusOrCreateOnboardingTab } from 'src/app/navigation/utils' import { focusOrCreateOnboardingTab } from 'src/app/navigation/utils'
import { Button, Flex, InputProps, Text, TouchableArea } from 'ui/src' import { DeprecatedButton, Flex, InputProps, Text, TouchableArea } from 'ui/src'
import { AlertTriangleFilled, Lock } from 'ui/src/components/icons' import { AlertTriangleFilled, Lock } from 'ui/src/components/icons'
import { spacing, zIndices } from 'ui/src/theme' import { spacing, zIndices } from 'ui/src/theme'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
...@@ -211,9 +211,9 @@ export function Locked(): JSX.Element { ...@@ -211,9 +211,9 @@ export function Locked(): JSX.Element {
</Flex> </Flex>
<Flex gap="$spacing12" justifyContent="flex-end" zIndex={zIndices.sticky}> <Flex gap="$spacing12" justifyContent="flex-end" zIndex={zIndices.sticky}>
<Button size="large" theme="primary" onPress={onPress}> <DeprecatedButton size="large" theme="primary" onPress={onPress}>
{t('extension.lock.button.submit')} {t('extension.lock.button.submit')}
</Button> </DeprecatedButton>
<TouchableArea> <TouchableArea>
<Text <Text
......
...@@ -8,7 +8,7 @@ import { onboardingMessageChannel } from 'src/background/messagePassing/messageC ...@@ -8,7 +8,7 @@ import { onboardingMessageChannel } from 'src/background/messagePassing/messageC
import { OnboardingMessageType } from 'src/background/messagePassing/types/ExtensionMessages' import { OnboardingMessageType } from 'src/background/messagePassing/types/ExtensionMessages'
import { openSidePanel } from 'src/background/utils/chromeSidePanelUtils' import { openSidePanel } from 'src/background/utils/chromeSidePanelUtils'
import { terminateStoreSynchronization } from 'src/store/storeSynchronization' import { terminateStoreSynchronization } from 'src/store/storeSynchronization'
import { Button, Flex, Image, Text } from 'ui/src' import { DeprecatedButton, Flex, Image, Text } from 'ui/src'
import { UNISWAP_LOGO } from 'ui/src/assets' import { UNISWAP_LOGO } from 'ui/src/assets'
import { RightArrow } from 'ui/src/components/icons' import { RightArrow } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
...@@ -92,7 +92,7 @@ export function Complete({ ...@@ -92,7 +92,7 @@ export function Complete({
<KeyboardKey key={key.title} fontSize={key.fontSize} px={key.px} state={key.state} title={key.title} /> <KeyboardKey key={key.title} fontSize={key.fontSize} px={key.px} state={key.state} title={key.title} />
))} ))}
</Flex> </Flex>
<Button <DeprecatedButton
iconAfter={openedSideBar ? <RightArrow /> : undefined} iconAfter={openedSideBar ? <RightArrow /> : undefined}
size="large" size="large"
theme={openedSideBar ? 'primary' : 'secondary'} theme={openedSideBar ? 'primary' : 'secondary'}
...@@ -100,7 +100,7 @@ export function Complete({ ...@@ -100,7 +100,7 @@ export function Complete({
onPress={openedSideBar ? handleOpenWebApp : handleOpenSidebar} onPress={openedSideBar ? handleOpenWebApp : handleOpenSidebar}
> >
{openedSideBar ? t('onboarding.complete.go_to_uniswap') : t('onboarding.complete.button')} {openedSideBar ? t('onboarding.complete.go_to_uniswap') : t('onboarding.complete.button')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</MainContentWrapper> </MainContentWrapper>
......
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { OnboardingScreenProps } from 'src/app/features/onboarding/OnboardingScreenProps' import { OnboardingScreenProps } from 'src/app/features/onboarding/OnboardingScreenProps'
import { Button, Flex, Text, TouchableArea } from 'ui/src' import { DeprecatedButton, Flex, Text, TouchableArea } from 'ui/src'
import { BackArrow } from 'ui/src/components/icons' import { BackArrow } from 'ui/src/components/icons'
import i18n from 'uniswap/src/i18n' import i18n from 'uniswap/src/i18n'
...@@ -85,7 +85,7 @@ export function OnboardingScreenFrame({ ...@@ -85,7 +85,7 @@ export function OnboardingScreenFrame({
</Flex> </Flex>
<Flex row gap="$spacing12" width="100%"> <Flex row gap="$spacing12" width="100%">
{Boolean(onSubmit) && nextButtonText && ( {Boolean(onSubmit) && nextButtonText && (
<Button <DeprecatedButton
disabled={!nextButtonEnabled} disabled={!nextButtonEnabled}
flexGrow={1} flexGrow={1}
icon={nextButtonIcon} icon={nextButtonIcon}
...@@ -93,7 +93,7 @@ export function OnboardingScreenFrame({ ...@@ -93,7 +93,7 @@ export function OnboardingScreenFrame({
onPress={onSubmit} onPress={onSubmit}
> >
{nextButtonText} {nextButtonText}
</Button> </DeprecatedButton>
)} )}
</Flex> </Flex>
</> </>
......
...@@ -13,7 +13,7 @@ import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingSteps' ...@@ -13,7 +13,7 @@ import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingSteps'
import { SyncFromPhoneButton } from 'src/app/features/onboarding/SyncFromPhoneButton' import { SyncFromPhoneButton } from 'src/app/features/onboarding/SyncFromPhoneButton'
import { TopLevelRoutes } from 'src/app/navigation/constants' import { TopLevelRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { Button, Flex, FlexProps, Input, Square, Text, inputStyles } from 'ui/src' import { DeprecatedButton, Flex, FlexProps, Input, Square, Text, inputStyles } from 'ui/src'
import { FileListLock, RotatableChevron } from 'ui/src/components/icons' import { FileListLock, RotatableChevron } from 'ui/src/components/icons'
import { fonts, iconSizes } from 'ui/src/theme' import { fonts, iconSizes } from 'ui/src/theme'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
...@@ -247,7 +247,7 @@ export function ImportMnemonic(): JSX.Element { ...@@ -247,7 +247,7 @@ export function ImportMnemonic(): JSX.Element {
), ),
)} )}
</Flex> </Flex>
<Button <DeprecatedButton
backgroundColor="$transparent" backgroundColor="$transparent"
gap="$spacing4" gap="$spacing4"
hoverStyle={{ backgroundColor: 'transparent' } as FlexProps} hoverStyle={{ backgroundColor: 'transparent' } as FlexProps}
...@@ -262,7 +262,7 @@ export function ImportMnemonic(): JSX.Element { ...@@ -262,7 +262,7 @@ export function ImportMnemonic(): JSX.Element {
: t('onboarding.importMnemonic.button.longPhrase')} : t('onboarding.importMnemonic.button.longPhrase')}
</Text> </Text>
<RotatableChevron color="$neutral3" direction={expanded ? 'up' : 'down'} width={iconSizes.icon20} /> <RotatableChevron color="$neutral3" direction={expanded ? 'up' : 'down'} width={iconSizes.icon20} />
</Button> </DeprecatedButton>
</Flex> </Flex>
</> </>
</OnboardingScreen> </OnboardingScreen>
......
...@@ -8,7 +8,7 @@ import { OnboardingRoutes, TopLevelRoutes } from 'src/app/navigation/constants' ...@@ -8,7 +8,7 @@ import { OnboardingRoutes, TopLevelRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { checksIfSupportsSidePanel } from 'src/app/utils/chrome' import { checksIfSupportsSidePanel } from 'src/app/utils/chrome'
import { isOnboardedSelector } from 'src/app/utils/isOnboardedSelector' import { isOnboardedSelector } from 'src/app/utils/isOnboardedSelector'
import { Button, Flex, Text } from 'ui/src' import { DeprecatedButton, Flex, Text } from 'ui/src'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
...@@ -43,7 +43,7 @@ export function IntroScreen(): JSX.Element { ...@@ -43,7 +43,7 @@ export function IntroScreen(): JSX.Element {
> >
<Flex gap="$spacing12" pb="$spacing16" pt="$spacing32"> <Flex gap="$spacing12" pb="$spacing16" pt="$spacing32">
<Flex backgroundColor="$surface1" borderRadius="$rounded16"> <Flex backgroundColor="$surface1" borderRadius="$rounded16">
<Button <DeprecatedButton
flexGrow={1} flexGrow={1}
theme="primary" theme="primary"
onPress={(): void => onPress={(): void =>
...@@ -53,15 +53,15 @@ export function IntroScreen(): JSX.Element { ...@@ -53,15 +53,15 @@ export function IntroScreen(): JSX.Element {
} }
> >
{t('onboarding.landing.button.create')} {t('onboarding.landing.button.create')}
</Button> </DeprecatedButton>
</Flex> </Flex>
<Button <DeprecatedButton
flexGrow={1} flexGrow={1}
theme="secondary" theme="secondary"
onPress={(): void => navigate(`/${TopLevelRoutes.Onboarding}/${OnboardingRoutes.Import}`)} onPress={(): void => navigate(`/${TopLevelRoutes.Onboarding}/${OnboardingRoutes.Import}`)}
> >
{t('onboarding.intro.button.alreadyHave')} {t('onboarding.intro.button.alreadyHave')}
</Button> </DeprecatedButton>
</Flex> </Flex>
<Flex row alignItems="center" gap="$spacing16" py="$spacing4"> <Flex row alignItems="center" gap="$spacing16" py="$spacing4">
<Flex fill backgroundColor="$surface3" height={1} /> <Flex fill backgroundColor="$surface3" height={1} />
......
...@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next' ...@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useDappContext } from 'src/app/features/dapp/DappContext' import { useDappContext } from 'src/app/features/dapp/DappContext'
import { saveDappConnection } from 'src/app/features/dapp/actions' import { saveDappConnection } from 'src/app/features/dapp/actions'
import { Anchor, Button, Flex, Popover, Separator, Text, TouchableArea } from 'ui/src' import { Anchor, DeprecatedButton, Flex, Popover, Separator, Text, TouchableArea } from 'ui/src'
import { X } from 'ui/src/components/icons' import { X } from 'ui/src/components/icons'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
...@@ -56,14 +56,14 @@ export function ConnectPopupContent({ ...@@ -56,14 +56,14 @@ export function ConnectPopupContent({
{showConnectButton ? ( {showConnectButton ? (
asPopover ? ( asPopover ? (
<Popover.Close onPress={onConnect}> <Popover.Close onPress={onConnect}>
<Button mt="$spacing8" size="small" theme="tertiary"> <DeprecatedButton mt="$spacing8" size="small" theme="tertiary">
{t('common.button.connect')} {t('common.button.connect')}
</Button> </DeprecatedButton>
</Popover.Close> </Popover.Close>
) : ( ) : (
<Button mt="$spacing8" size="small" theme="tertiary" onPress={onConnect}> <DeprecatedButton mt="$spacing8" size="small" theme="tertiary" onPress={onConnect}>
{t('common.button.connect')} {t('common.button.connect')}
</Button> </DeprecatedButton>
) )
) : ( ) : (
<Link <Link
......
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Button, Flex, Text, isWeb } from 'ui/src' import { DeprecatedButton, Flex, Text, isWeb } from 'ui/src'
import { WarningLabel } from 'uniswap/src/components/modals/WarningModal/types' import { WarningLabel } from 'uniswap/src/components/modals/WarningModal/types'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
...@@ -35,7 +35,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem ...@@ -35,7 +35,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem
return ( return (
<Flex gap="$spacing16"> <Flex gap="$spacing16">
<Trace logPress element={ElementName.SendReview}> <Trace logPress element={ElementName.SendReview}>
<Button <DeprecatedButton
backgroundColor="$accent1" backgroundColor="$accent1"
disabled={disableReviewButton} disabled={disableReviewButton}
size={isWeb ? 'medium' : 'large'} size={isWeb ? 'medium' : 'large'}
...@@ -45,7 +45,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem ...@@ -45,7 +45,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem
<Text color="white" variant="buttonLabel1"> <Text color="white" variant="buttonLabel1">
{buttonText} {buttonText}
</Text> </Text>
</Button> </DeprecatedButton>
</Trace> </Trace>
</Flex> </Flex>
) )
......
import { useCallback } from 'react' import { useCallback, useState } from 'react'
import { useSelector } from 'react-redux'
import { RecipientPanel } from 'src/app/features/send/SendFormScreen/RecipientPanel' import { RecipientPanel } from 'src/app/features/send/SendFormScreen/RecipientPanel'
import { ReviewButton } from 'src/app/features/send/SendFormScreen/ReviewButton' import { ReviewButton } from 'src/app/features/send/SendFormScreen/ReviewButton'
import { Flex, Separator, useSporeColors } from 'ui/src' import { Flex, Separator, useSporeColors } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { selectHasDismissedLowNetworkTokenWarning } from 'uniswap/src/features/behaviorHistory/selectors'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ModalName, SectionName } from 'uniswap/src/features/telemetry/constants' import { ModalName, SectionName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { InsufficientNativeTokenWarning } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning' import { InsufficientNativeTokenWarning } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning'
import { import {
TransactionScreen, TransactionScreen,
...@@ -14,6 +17,7 @@ import { ...@@ -14,6 +17,7 @@ import {
import { useUSDTokenUpdater } from 'uniswap/src/features/transactions/hooks/useUSDTokenUpdater' import { useUSDTokenUpdater } from 'uniswap/src/features/transactions/hooks/useUSDTokenUpdater'
import { BlockedAddressWarning } from 'uniswap/src/features/transactions/modals/BlockedAddressWarning' import { BlockedAddressWarning } from 'uniswap/src/features/transactions/modals/BlockedAddressWarning'
import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice'
import { LowNativeBalanceModal } from 'uniswap/src/features/transactions/swap/modals/LowNativeBalanceModal'
import { useIsBlocked } from 'uniswap/src/features/trm/hooks' import { useIsBlocked } from 'uniswap/src/features/trm/hooks'
import { CurrencyField } from 'uniswap/src/types/currency' import { CurrencyField } from 'uniswap/src/types/currency'
import { createTransactionId } from 'uniswap/src/utils/createTransactionId' import { createTransactionId } from 'uniswap/src/utils/createTransactionId'
...@@ -28,6 +32,9 @@ import { useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks' ...@@ -28,6 +32,9 @@ import { useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks'
export function SendFormScreen(): JSX.Element { export function SendFormScreen(): JSX.Element {
const colors = useSporeColors() const colors = useSporeColors()
const hasDismissedLowNetworkTokenWarning = useSelector(selectHasDismissedLowNetworkTokenWarning)
const [showMaxTransferModal, setShowMaxTransferModal] = useState(false)
const { const {
derivedSendInfo, derivedSendInfo,
selectingCurrencyField, selectingCurrencyField,
...@@ -39,6 +46,7 @@ export function SendFormScreen(): JSX.Element { ...@@ -39,6 +46,7 @@ export function SendFormScreen(): JSX.Element {
recipient, recipient,
updateSendForm, updateSendForm,
onSelectCurrency, onSelectCurrency,
isMax,
} = useSendContext() } = useSendContext()
const { screen, setScreen } = useTransactionModalContext() const { screen, setScreen } = useTransactionModalContext()
...@@ -83,12 +91,21 @@ export function SendFormScreen(): JSX.Element { ...@@ -83,12 +91,21 @@ export function SendFormScreen(): JSX.Element {
const isBlocked = isActiveBlocked || isRecipientBlocked const isBlocked = isActiveBlocked || isRecipientBlocked
const isBlockedLoading = isActiveBlockedLoading || isRecipientBlockedLoading const isBlockedLoading = isActiveBlockedLoading || isRecipientBlockedLoading
const onPressReview = useCallback(() => { const goToReview = useCallback(() => {
const txId = createTransactionId() const txId = createTransactionId()
updateSendForm({ txId }) updateSendForm({ txId })
setScreen(TransactionScreen.Review) setScreen(TransactionScreen.Review)
}, [setScreen, updateSendForm]) }, [setScreen, updateSendForm])
const onPressReview = useCallback(() => {
if (!hasDismissedLowNetworkTokenWarning && isMax && currencyInInfo?.currency.isNative) {
sendAnalyticsEvent(WalletEventName.LowNetworkTokenInfoModalOpened, { location: 'send' })
setShowMaxTransferModal(true)
return
}
goToReview()
}, [goToReview, isMax, hasDismissedLowNetworkTokenWarning, setShowMaxTransferModal, currencyInInfo])
const onSetExactAmount = useCallback( const onSetExactAmount = useCallback(
(amount: string) => { (amount: string) => {
updateSendForm(isFiatInput ? { exactAmountFiat: amount } : { exactAmountToken: amount }) updateSendForm(isFiatInput ? { exactAmountFiat: amount } : { exactAmountToken: amount })
...@@ -103,6 +120,16 @@ export function SendFormScreen(): JSX.Element { ...@@ -103,6 +120,16 @@ export function SendFormScreen(): JSX.Element {
[updateSendForm], [updateSendForm],
) )
const hideLowNativeBalanceWarning = useCallback(() => {
setShowMaxTransferModal(false)
}, [setShowMaxTransferModal])
const onAcknowledgeLowNativeBalanceWarning = useCallback(() => {
hideLowNativeBalanceWarning()
goToReview()
}, [hideLowNativeBalanceWarning, goToReview])
const onHideTokenSelector = useCallback(() => { const onHideTokenSelector = useCallback(() => {
updateSendForm({ selectingCurrencyField: undefined }) updateSendForm({ selectingCurrencyField: undefined })
}, [updateSendForm]) }, [updateSendForm])
...@@ -127,6 +154,11 @@ export function SendFormScreen(): JSX.Element { ...@@ -127,6 +154,11 @@ export function SendFormScreen(): JSX.Element {
<Modal alignment="top" isModalOpen={screen === TransactionScreen.Review} name={ModalName.SendReview}> <Modal alignment="top" isModalOpen={screen === TransactionScreen.Review} name={ModalName.SendReview}>
<SendReviewDetails /> <SendReviewDetails />
</Modal> </Modal>
<LowNativeBalanceModal
isOpen={showMaxTransferModal}
onClose={hideLowNativeBalanceWarning}
onAcknowledge={onAcknowledgeLowNativeBalanceWarning}
/>
<Flex fill gap="$spacing12"> <Flex fill gap="$spacing12">
<Flex <Flex
borderColor="$surface3" borderColor="$surface3"
......
import { useCallback, useState } from 'react'
import { LayoutChangeEvent } from 'react-native'
import { CopyButton } from 'src/app/components/buttons/CopyButton'
import { Flex, Separator, Text } from 'ui/src'
import { spacing } from 'ui/src/theme'
import { setClipboard } from 'uniswap/src/utils/clipboard'
import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
function SeedPhraseColumnGroup({ recoveryPhraseArray }: { recoveryPhraseArray: string[] }): JSX.Element {
const [largestIndexWidth, setLargestIndexWidth] = useState(0)
const halfLength = Math.ceil(recoveryPhraseArray.length / 2)
const firstHalfWords = recoveryPhraseArray.slice(0, halfLength)
const secondHalfWords = recoveryPhraseArray.slice(halfLength)
const onIndexLayout = (event: LayoutChangeEvent): void => {
const { width } = event.nativeEvent.layout
if (width > largestIndexWidth) {
setLargestIndexWidth(width)
}
}
return (
<Flex grow row gap="$spacing16" justifyContent="space-between">
<SeedPhraseColumn
indexOffset={1}
largestIndexWidth={largestIndexWidth}
words={firstHalfWords}
onIndexLayout={onIndexLayout}
/>
<Separator vertical />
<SeedPhraseColumn
indexOffset={halfLength + 1}
largestIndexWidth={largestIndexWidth}
words={secondHalfWords}
onIndexLayout={onIndexLayout}
/>
</Flex>
)
}
function SeedPhraseColumn({
words,
indexOffset,
largestIndexWidth,
onIndexLayout,
}: {
words: string[]
indexOffset: number
largestIndexWidth: number
onIndexLayout: (event: LayoutChangeEvent) => void
}): JSX.Element {
return (
<Flex fill gap="$spacing16">
{words.map((word, index) => (
<SeedPhraseWord
key={index}
index={index + indexOffset}
indexMinWidth={largestIndexWidth}
word={word}
onIndexLayout={onIndexLayout}
/>
))}
</Flex>
)
}
function SeedPhraseWord({
index,
word,
indexMinWidth,
onIndexLayout,
}: {
index: number
word: string
indexMinWidth: number
onIndexLayout: (event: LayoutChangeEvent) => void
}): JSX.Element {
return (
<Flex key={index} row gap="$spacing12">
<Text color="$neutral3" minWidth={indexMinWidth} variant="body2" onLayout={onIndexLayout}>
{index}
</Text>
<Text variant="body2" className="notranslate">
{word}
</Text>
</Flex>
)
}
export function SeedPhraseDisplay({ mnemonicId }: { mnemonicId: string }): JSX.Element {
const placeholderWordArrayLength = 12
const recoveryPhraseString = useAsyncData(
useCallback(async () => Keyring.retrieveMnemonicUnlocked(mnemonicId), [mnemonicId]),
).data
const recoveryPhraseArray = recoveryPhraseString?.split(' ') ?? Array(placeholderWordArrayLength).fill('')
const onCopyPress = async (): Promise<void> => {
try {
if (recoveryPhraseString) {
await setClipboard(recoveryPhraseString)
}
} catch (error) {
logger.error(error, {
tags: { file: 'SeedPhraseDisplay', function: 'onCopyPress' },
})
}
}
return (
<Flex
backgroundColor="$surface2"
borderColor="$surface3"
borderRadius="$rounded16"
borderWidth={1}
gap="$spacing12"
width="100%"
>
<Flex fill row pb="$spacing24" pt="$spacing32" px="$spacing24">
<SeedPhraseColumnGroup recoveryPhraseArray={recoveryPhraseArray} />
</Flex>
<Flex alignItems="center" position="absolute" top={-1 * spacing.spacing16} width="100%">
<CopyButton onCopyPress={onCopyPress} />
</Flex>
</Flex>
)
}
import { Button, Flex, Square, Text } from 'ui/src' import { DeprecatedButton, Flex, Square, Text } from 'ui/src'
import { ThemeNames } from 'ui/src/theme' import { ThemeNames } from 'ui/src/theme'
export type SettingsRecoveryPhraseProps = { export type SettingsRecoveryPhraseProps = {
...@@ -38,14 +38,14 @@ export function SettingsRecoveryPhrase({ ...@@ -38,14 +38,14 @@ export function SettingsRecoveryPhrase({
</Flex> </Flex>
<Flex grow>{children}</Flex> <Flex grow>{children}</Flex>
<Flex mt="$spacing12"> <Flex mt="$spacing12">
<Button <DeprecatedButton
disabled={!nextButtonEnabled} disabled={!nextButtonEnabled}
flexGrow={1} flexGrow={1}
theme={nextButtonTheme as ThemeNames} theme={nextButtonTheme as ThemeNames}
onPress={onNextPressed} onPress={onNextPressed}
> >
{nextButtonText} {nextButtonText}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
) )
......
import { useCallback, useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { LayoutChangeEvent } from 'react-native'
import { CopyButton } from 'src/app/components/buttons/CopyButton'
import { ScreenHeader } from 'src/app/components/layout/ScreenHeader' import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { SeedPhraseDisplay } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/SeedPhraseDisplay'
import { SettingsRecoveryPhrase } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/SettingsRecoveryPhrase' import { SettingsRecoveryPhrase } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/SettingsRecoveryPhrase'
import { EnterPasswordModal } from 'src/app/features/settings/password/EnterPasswordModal' import { EnterPasswordModal } from 'src/app/features/settings/password/EnterPasswordModal'
import { AppRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes } from 'src/app/navigation/constants' import { AppRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { Button, Flex, Separator, Text } from 'ui/src' import { DeprecatedButton, Flex, Text } from 'ui/src'
import { AlertTriangleFilled, Eye, Key, Laptop } from 'ui/src/components/icons' import { AlertTriangleFilled, Eye, Key, Laptop } from 'ui/src/components/icons'
import { spacing } from 'ui/src/theme'
import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { setClipboard } from 'uniswap/src/utils/clipboard'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
import { useSignerAccounts } from 'wallet/src/features/wallet/hooks' import { useSignerAccounts } from 'wallet/src/features/wallet/hooks'
const enum ViewStep { const enum ViewStep {
...@@ -35,25 +30,6 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element { ...@@ -35,25 +30,6 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
throw new Error('Screen should not be accessed unless mnemonic account exists') throw new Error('Screen should not be accessed unless mnemonic account exists')
} }
const placeholderWordArrayLength = 12
const recoveryPhraseString = useAsyncData(
useCallback(async () => Keyring.retrieveMnemonicUnlocked(mnemonicAccount.mnemonicId), [mnemonicAccount.mnemonicId]),
).data
const recoveryPhraseArray = recoveryPhraseString?.split(' ') ?? Array(placeholderWordArrayLength).fill('')
const onCopyPress = async (): Promise<void> => {
try {
if (recoveryPhraseString) {
await setClipboard(recoveryPhraseString)
}
} catch (error) {
logger.error(error, {
tags: { file: 'SettingsViewRecoveryPhraseScreen.tsx', function: 'onCopyPress' },
})
}
}
const showPasswordModal = (): void => { const showPasswordModal = (): void => {
setViewStep(ViewStep.Password) setViewStep(ViewStep.Password)
} }
...@@ -125,28 +101,14 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element { ...@@ -125,28 +101,14 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
</SettingsRecoveryPhrase> </SettingsRecoveryPhrase>
) : ( ) : (
<Flex fill gap="$spacing24" pt="$spacing36"> <Flex fill gap="$spacing24" pt="$spacing36">
<Flex <SeedPhraseDisplay mnemonicId={mnemonicAccount.mnemonicId} />
backgroundColor="$surface2"
borderColor="$surface3"
borderRadius="$rounded16"
borderWidth={1}
gap="$spacing12"
width="100%"
>
<Flex fill row pb="$spacing24" pt="$spacing32" px="$spacing24">
<SeedPhraseColumnGroup recoveryPhraseArray={recoveryPhraseArray} />
</Flex>
<Flex alignItems="center" position="absolute" top={-1 * spacing.spacing16} width="100%">
<CopyButton onCopyPress={onCopyPress} />
</Flex>
</Flex>
<Flex alignItems="center" gap="$spacing8"> <Flex alignItems="center" gap="$spacing8">
<Text color="$neutral2" textAlign="center" variant="body3"> <Text color="$neutral2" textAlign="center" variant="body3">
{t('setting.recoveryPhrase.warning.view.message')} {t('setting.recoveryPhrase.warning.view.message')}
</Text> </Text>
</Flex> </Flex>
<Flex fill justifyContent="flex-end"> <Flex fill justifyContent="flex-end">
<Button <DeprecatedButton
theme="detrimental" theme="detrimental"
onPress={(): void => onPress={(): void =>
navigate( navigate(
...@@ -156,92 +118,10 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element { ...@@ -156,92 +118,10 @@ export function SettingsViewRecoveryPhraseScreen(): JSX.Element {
} }
> >
{t('setting.recoveryPhrase.remove')} {t('setting.recoveryPhrase.remove')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
)} )}
</Flex> </Flex>
) )
} }
function SeedPhraseColumnGroup({ recoveryPhraseArray }: { recoveryPhraseArray: string[] }): JSX.Element {
const [largestIndexWidth, setLargestIndexWidth] = useState(0)
const halfLength = Math.ceil(recoveryPhraseArray.length / 2)
const firstHalfWords = recoveryPhraseArray.slice(0, halfLength)
const secondHalfWords = recoveryPhraseArray.slice(halfLength)
const onIndexLayout = (event: LayoutChangeEvent): void => {
const { width } = event.nativeEvent.layout
if (width > largestIndexWidth) {
setLargestIndexWidth(width)
}
}
return (
<Flex grow row gap="$spacing16" justifyContent="space-between">
<SeedPhraseColumn
indexOffset={1}
largestIndexWidth={largestIndexWidth}
words={firstHalfWords}
onIndexLayout={onIndexLayout}
/>
<Separator vertical borderWidth="$spacing1" />
<SeedPhraseColumn
indexOffset={halfLength + 1}
largestIndexWidth={largestIndexWidth}
words={secondHalfWords}
onIndexLayout={onIndexLayout}
/>
</Flex>
)
}
function SeedPhraseColumn({
words,
indexOffset,
largestIndexWidth,
onIndexLayout,
}: {
words: string[]
indexOffset: number
largestIndexWidth: number
onIndexLayout: (event: LayoutChangeEvent) => void
}): JSX.Element {
return (
<Flex fill gap="$spacing16">
{words.map((word, index) => (
<SeedPhraseWord
key={index}
index={index + indexOffset}
indexMinWidth={largestIndexWidth}
word={word}
onIndexLayout={onIndexLayout}
/>
))}
</Flex>
)
}
function SeedPhraseWord({
index,
word,
indexMinWidth,
onIndexLayout,
}: {
index: number
word: string
indexMinWidth: number
onIndexLayout: (event: LayoutChangeEvent) => void
}): JSX.Element {
return (
<Flex key={index} row gap="$spacing12">
<Text color="$neutral3" minWidth={indexMinWidth} variant="body2" onLayout={onIndexLayout}>
{index}
</Text>
<Text variant="body2" className="notranslate">
{word}
</Text>
</Flex>
)
}
...@@ -8,8 +8,8 @@ import { SettingsItemWithDropdown } from 'src/app/features/settings/SettingsItem ...@@ -8,8 +8,8 @@ import { SettingsItemWithDropdown } from 'src/app/features/settings/SettingsItem
import { AppRoutes, SettingsRoutes } from 'src/app/navigation/constants' import { AppRoutes, SettingsRoutes } from 'src/app/navigation/constants'
import { useExtensionNavigation } from 'src/app/navigation/utils' import { useExtensionNavigation } from 'src/app/navigation/utils'
import { import {
Button,
ColorTokens, ColorTokens,
DeprecatedButton,
Flex, Flex,
GeneratedIcon, GeneratedIcon,
ScrollView, ScrollView,
...@@ -222,9 +222,9 @@ export function SettingsScreen(): JSX.Element { ...@@ -222,9 +222,9 @@ export function SettingsScreen(): JSX.Element {
<Text color="$neutral3" px="$spacing12" py="$spacing4" variant="body4">{`Version ${manifestVersion}`}</Text> <Text color="$neutral3" px="$spacing12" py="$spacing4" variant="body4">{`Version ${manifestVersion}`}</Text>
</SettingsSection> </SettingsSection>
</ScrollView> </ScrollView>
<Button icon={<Lock />} theme="secondary" onPress={onPressLockWallet}> <DeprecatedButton icon={<Lock />} theme="secondary" onPress={onPressLockWallet}>
{t('settings.action.lock')} {t('settings.action.lock')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</> </>
) )
......
...@@ -2,7 +2,7 @@ import { useCallback } from 'react' ...@@ -2,7 +2,7 @@ import { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { PADDING_STRENGTH_INDICATOR, PasswordInput } from 'src/app/components/PasswordInput' import { PADDING_STRENGTH_INDICATOR, PasswordInput } from 'src/app/components/PasswordInput'
import { Button, Flex, Text } from 'ui/src' import { DeprecatedButton, Flex, Text } from 'ui/src'
import { pushNotification } from 'uniswap/src/features/notifications/slice' import { pushNotification } from 'uniswap/src/features/notifications/slice'
import { AppNotificationType } from 'uniswap/src/features/notifications/types' import { AppNotificationType } from 'uniswap/src/features/notifications/types'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
...@@ -67,9 +67,9 @@ export function ChangePasswordForm({ onNext }: { onNext: () => void }): JSX.Elem ...@@ -67,9 +67,9 @@ export function ChangePasswordForm({ onNext }: { onNext: () => void }): JSX.Elem
{errorText || 'Placeholder text'} {errorText || 'Placeholder text'}
</Text> </Text>
</Flex> </Flex>
<Button disabled={!enableNext} theme="tertiary" onPress={onSubmit}> <DeprecatedButton disabled={!enableNext} theme="tertiary" onPress={onSubmit}>
{t('common.button.save')} {t('common.button.save')}
</Button> </DeprecatedButton>
</Flex> </Flex>
) )
} }
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { PasswordInput } from 'src/app/components/PasswordInput' import { PasswordInput } from 'src/app/components/PasswordInput'
import { Button, Flex, Text } from 'ui/src' import { DeprecatedButton, Flex, Text } from 'ui/src'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
function useEnterPasswordForm(): { function useEnterPasswordForm(): {
...@@ -72,9 +72,9 @@ export function EnterPasswordForm({ onNext }: { onNext: () => void }): JSX.Eleme ...@@ -72,9 +72,9 @@ export function EnterPasswordForm({ onNext }: { onNext: () => void }): JSX.Eleme
</Text> </Text>
)} )}
</Flex> </Flex>
<Button disabled={!submitEnabled} theme="tertiary" onPress={onContinue}> <DeprecatedButton disabled={!submitEnabled} theme="tertiary" onPress={onContinue}>
{t('common.button.continue')} {t('common.button.continue')}
</Button> </DeprecatedButton>
</Flex> </Flex>
) )
} }
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { PasswordInput } from 'src/app/components/PasswordInput' import { PasswordInput } from 'src/app/components/PasswordInput'
import { Button, Flex, Square, Text, inputStyles, useSporeColors } from 'ui/src' import { DeprecatedButton, Flex, Square, Text, inputStyles, useSporeColors } from 'ui/src'
import { Lock } from 'ui/src/components/icons' import { Lock } from 'ui/src/components/icons'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
...@@ -69,9 +69,9 @@ export function EnterPasswordModal({ ...@@ -69,9 +69,9 @@ export function EnterPasswordModal({
<Text color="$statusCritical" minHeight="$spacing24" textAlign="center" variant="body2"> <Text color="$statusCritical" minHeight="$spacing24" textAlign="center" variant="body2">
{showPasswordError ? t('setting.recoveryPhrase.remove.password.error') : ''} {showPasswordError ? t('setting.recoveryPhrase.remove.password.error') : ''}
</Text> </Text>
<Button disabled={!password.length} theme="secondary" width="100%" onPress={checkPassword}> <DeprecatedButton disabled={!password.length} theme="secondary" width="100%" onPress={checkPassword}>
{t('common.button.continue')} {t('common.button.continue')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Modal> </Modal>
) )
......
...@@ -4,7 +4,7 @@ import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen' ...@@ -4,7 +4,7 @@ import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext' import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext' import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext'
import { closeCurrentTab } from 'src/app/navigation/utils' import { closeCurrentTab } from 'src/app/navigation/utils'
import { Button, Flex, Text } from 'ui/src' import { DeprecatedButton, Flex, Text } from 'ui/src'
import { UNITAG_SUFFIX } from 'uniswap/src/features/unitags/constants' import { UNITAG_SUFFIX } from 'uniswap/src/features/unitags/constants'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { UnitagWithProfilePicture } from 'wallet/src/features/unitags/UnitagWithProfilePicture' import { UnitagWithProfilePicture } from 'wallet/src/features/unitags/UnitagWithProfilePicture'
...@@ -49,12 +49,12 @@ export function UnitagConfirmationScreen(): JSX.Element { ...@@ -49,12 +49,12 @@ export function UnitagConfirmationScreen(): JSX.Element {
</Text> </Text>
</Flex> </Flex>
<Flex gap="$spacing12" pt="$spacing12"> <Flex gap="$spacing12" pt="$spacing12">
<Button size="medium" theme="primary" onPress={closeCurrentTab}> <DeprecatedButton size="medium" theme="primary" onPress={closeCurrentTab}>
{t('common.button.done')} {t('common.button.done')}
</Button> </DeprecatedButton>
<Button size="medium" theme="secondary" onPress={onPressCustomize}> <DeprecatedButton size="medium" theme="secondary" onPress={onPressCustomize}>
{t('unitags.claim.confirmation.customize')} {t('unitags.claim.confirmation.customize')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</OnboardingScreen> </OnboardingScreen>
......
...@@ -4,7 +4,7 @@ import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsC ...@@ -4,7 +4,7 @@ import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsC
import { Terms } from 'src/app/features/onboarding/Terms' import { Terms } from 'src/app/features/onboarding/Terms'
import { UnitagClaimRoutes } from 'src/app/navigation/constants' import { UnitagClaimRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { Button, Flex, GeneratedIcon, Text } from 'ui/src' import { DeprecatedButton, Flex, GeneratedIcon, Text } from 'ui/src'
import { Bolt, Coupon, UserSquare } from 'ui/src/components/icons' import { Bolt, Coupon, UserSquare } from 'ui/src/components/icons'
import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks' import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks'
import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks' import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks'
...@@ -46,9 +46,9 @@ export function UnitagIntroScreen(): JSX.Element { ...@@ -46,9 +46,9 @@ export function UnitagIntroScreen(): JSX.Element {
</Flex> </Flex>
<Flex gap="$spacing24"> <Flex gap="$spacing24">
<Button size="large" onPress={() => goToNextStep()}> <DeprecatedButton size="large" onPress={() => goToNextStep()}>
{t('unitags.extension.intro.buttton')} {t('unitags.extension.intro.buttton')}
</Button> </DeprecatedButton>
<Flex width={TERMS_WIDTH} alignSelf="center"> <Flex width={TERMS_WIDTH} alignSelf="center">
<Terms /> <Terms />
</Flex> </Flex>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Uniswap Extension", "name": "Uniswap Extension",
"description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.", "description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.",
"version": "1.13.0", "version": "1.14.0",
"minimum_chrome_version": "116", "minimum_chrome_version": "116",
"icons": { "icons": {
"16": "assets/icon16.png", "16": "assets/icon16.png",
......
...@@ -12,7 +12,7 @@ import { ...@@ -12,7 +12,7 @@ import {
deleteDeprecatedReduxedChromeStorage, deleteDeprecatedReduxedChromeStorage,
readDeprecatedReduxedChromeStorage, readDeprecatedReduxedChromeStorage,
} from 'src/store/reduxedChromeStorageToReduxPersistMigration' } from 'src/store/reduxedChromeStorageToReduxPersistMigration'
import { getFiatOnRampAggregatorApi } from 'uniswap/src/features/fiatOnRamp/api' import { fiatOnRampAggregatorApi } from 'uniswap/src/features/fiatOnRamp/api'
import { createDatadogReduxEnhancer } from 'utilities/src/logger/Datadog' import { createDatadogReduxEnhancer } from 'utilities/src/logger/Datadog'
import { createStore } from 'wallet/src/state' import { createStore } from 'wallet/src/state'
import { createMigrate } from 'wallet/src/state/createMigrate' import { createMigrate } from 'wallet/src/state/createMigrate'
...@@ -52,7 +52,7 @@ const setupStore = (preloadedState?: PreloadedState<ExtensionState>): ReturnType ...@@ -52,7 +52,7 @@ const setupStore = (preloadedState?: PreloadedState<ExtensionState>): ReturnType
preloadedState, preloadedState,
additionalSagas: [rootExtensionSaga], additionalSagas: [rootExtensionSaga],
middlewareBefore: __DEV__ ? [loggerMiddleware] : [], middlewareBefore: __DEV__ ? [loggerMiddleware] : [],
middlewareAfter: [getFiatOnRampAggregatorApi().middleware], middlewareAfter: [fiatOnRampAggregatorApi.middleware],
enhancers: [sentryReduxEnhancer, dataDogReduxEnhancer], enhancers: [sentryReduxEnhancer, dataDogReduxEnhancer],
}) })
} }
......
...@@ -83,6 +83,9 @@ artifacts/ ...@@ -83,6 +83,9 @@ artifacts/
# CocoaPods # CocoaPods
/ios/Pods/ /ios/Pods/
# Android jsbundle
*.android.bundle
# ccache # ccache
.ccache .ccache
......
appId: com.uniswap.mobile.dev
---
- launchApp
- tapOn:
id: 'search-tokens-and-wallets'
- 'scroll'
- 'scroll'
- 'scroll'
- 'back'
appId: com.uniswap.mobile.dev
---
- launchApp:
appId: 'com.uniswap.mobile.dev'
clearState: true
clearKeychain: true # optional: clear *entire* iOS keychain
- extendedWaitUntil:
visible: 'Create a wallet'
- tapOn: 'Create a wallet'
- waitForAnimationToEnd
- tapOn: 'Skip'
- waitForAnimationToEnd
- tapOn: 'Skip'
- waitForAnimationToEnd
- tapOn: 'Skip'
- extendedWaitUntil:
visible:
id: 'confirm'
- tapOn:
id: 'confirm'
- waitForAnimationToEnd
- tapOn: 'Send'
- waitForAnimationToEnd
- 'back'
...@@ -31,19 +31,6 @@ const normalizedStories = [ ...@@ -31,19 +31,6 @@ const normalizedStories = [
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/ /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/
), ),
}, },
{
titlePrefix: "",
directory: "../../packages/ui/src",
files: "**/*.mdx",
importPathMatcher:
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.mdx)$/,
// @ts-ignore
req: require.context(
"../../../packages/ui/src",
true,
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.mdx)$/
),
},
]; ];
declare global { declare global {
......
...@@ -4,6 +4,32 @@ ...@@ -4,6 +4,32 @@
If you have suggestions on how we can improve the app, or would like to report a bug or a problem, check out the [Uniswap Help Center](https://support.uniswap.org/). If you have suggestions on how we can improve the app, or would like to report a bug or a problem, check out the [Uniswap Help Center](https://support.uniswap.org/).
## Table of contents
- [Setup](#setup)
- [Packages and Software](#packages-and-software)
- [iOS Setup](#ios-setup)
- [Xcode](#xcode)
- [Add Xcode Command Line Tools](#add-xcode-command-line-tools)
- [Android Setup](#android-setup)
- [Deploying to Physical Android Device](#deploying-to-physical-android-device)
- [Development](#development)
- [Environment variables](#environment-variables)
- [Compile contract ABI types](#compile-contract-abi-types)
- [Run the app](#run-the-app)
- [Using Radon IDE](#using-radon-ide-vscodecursor-extension)
- [Running on a Physical iOS Device](#running-on-a-physical-ios-device)
- [Important Libraries and Tools](#important-libraries-and-tools)
- [Migrations](#migrations)
- [Testing & Performance](#testing--performance)
- [Build local app files](./docs/build-app-files.md)
- [E2E testing](./docs/e2e-testing.md)
- [Performance monitoring](./docs/perf-monitoring.md)
- [Troubleshooting](#troubleshooting)
- [Common issues](#common-issues)
- [Common fixes](#common-fixes)
- [Shell profile setup](#shell-profile-setup)
## Setup ## Setup
This guide assumes that: This guide assumes that:
...@@ -124,7 +150,7 @@ Note: If you are indeed using an Apple Silicon Mac, we recommend setting up your ...@@ -124,7 +150,7 @@ Note: If you are indeed using an Apple Silicon Mac, we recommend setting up your
You should start with downloading Xcode if you don't already have it installed, since the file is so large. You can find it here: [developer.apple.com/xcode](https://developer.apple.com/xcode/) You should start with downloading Xcode if you don't already have it installed, since the file is so large. You can find it here: [developer.apple.com/xcode](https://developer.apple.com/xcode/)
You must use **XCode 15** to compile the app. [Older versions of xCode can be found here](https://developer.apple.com/download/all/?q=xcode). You must use the [Required Xcode Version](https://github.com/Uniswap/universe/blob/main/apps/mobile/scripts/podinstall.sh#L5) to compile the app. [Older versions of xCode can be found here](https://developer.apple.com/download/all/?q=xcode).
#### Add Xcode Command Line Tools #### Add Xcode Command Line Tools
...@@ -255,6 +281,13 @@ These are some tools you might want to familiarize yourself with to understand t ...@@ -255,6 +281,13 @@ These are some tools you might want to familiarize yourself with to understand t
We use `redux-persist` to persist the Redux state between user sessions. Most of this state is shared between the mobile app and the extension. Please review the [Wallet Migrations README](../../packages/wallet/src/state//README.md) for details on how to write migrations when you add or remove anything from the Redux state structure. We use `redux-persist` to persist the Redux state between user sessions. Most of this state is shared between the mobile app and the extension. Please review the [Wallet Migrations README](../../packages/wallet/src/state//README.md) for details on how to write migrations when you add or remove anything from the Redux state structure.
## Testing & Performance
- [Build local app files](./docs/build-app-files.md)
- [E2E testing](./docs/e2e-testing.md)
- [Performance monitoring](./docs/perf-monitoring.md)
## Troubleshooting ## Troubleshooting
### Common issues ### Common issues
...@@ -313,3 +346,4 @@ eval "$(/opt/homebrew/bin/brew shellenv)" ...@@ -313,3 +346,4 @@ eval "$(/opt/homebrew/bin/brew shellenv)"
export NVM_DIR="$HOME/.nvm" export NVM_DIR="$HOME/.nvm"
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm [ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion [ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion
...@@ -89,9 +89,9 @@ if (isCI && datadogPropertiesAvailable && !isDetox) { ...@@ -89,9 +89,9 @@ if (isCI && datadogPropertiesAvailable && !isDetox) {
apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle" apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle"
} }
def devVersionName = "1.43" def devVersionName = "1.44"
def betaVersionName = "1.43" def betaVersionName = "1.44"
def prodVersionName = "1.43" def prodVersionName = "1.44"
android { android {
ndkVersion rootProject.ext.ndkVersion ndkVersion rootProject.ext.ndkVersion
...@@ -251,6 +251,9 @@ dependencies { ...@@ -251,6 +251,9 @@ dependencies {
implementation("androidx.core:core-performance:$corePerf") implementation("androidx.core:core-performance:$corePerf")
implementation("androidx.core:core-performance-play-services:$corePerf") implementation("androidx.core:core-performance-play-services:$corePerf")
implementation 'com.onesignal:OneSignal:4.8.9'
implementation 'com.github.statsig-io:android-sdk:4.36.0'
if (hermesEnabled.toBoolean()) { if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android") implementation("com.facebook.react:hermes-android")
} else { } else {
......
...@@ -31,6 +31,9 @@ ...@@ -31,6 +31,9 @@
android:name="com.onesignal.NotificationAccentColor.DEFAULT" android:name="com.onesignal.NotificationAccentColor.DEFAULT"
android:value="@string/notification_accent_color" /> android:value="@string/notification_accent_color" />
<meta-data android:name="com.onesignal.NotificationServiceExtension"
android:value="com.uniswap.notifications.NotificationExtension" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
......
...@@ -11,6 +11,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnable ...@@ -11,6 +11,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnable
import com.facebook.react.defaults.DefaultReactActivityDelegate import com.facebook.react.defaults.DefaultReactActivityDelegate
import com.facebook.react.modules.i18nmanager.I18nUtil import com.facebook.react.modules.i18nmanager.I18nUtil
import expo.modules.ReactActivityDelegateWrapper import expo.modules.ReactActivityDelegateWrapper
import com.zoontek.rnbootsplash.RNBootSplash;
class MainActivity : ReactActivity() { class MainActivity : ReactActivity() {
...@@ -25,6 +26,8 @@ class MainActivity : ReactActivity() { ...@@ -25,6 +26,8 @@ class MainActivity : ReactActivity() {
// Required for react-navigation to work on Android // Required for react-navigation to work on Android
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
RNBootSplash.init(this, R.style.AppTheme)
super.onCreate(null); super.onCreate(null);
window.navigationBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT
......
package com.uniswap.notifications
import android.app.Application
import android.content.Context
import android.provider.Settings.Secure
import com.onesignal.OSNotificationReceivedEvent
import com.onesignal.OneSignal.OSRemoteNotificationReceivedHandler
import com.statsig.androidsdk.Statsig
import com.statsig.androidsdk.StatsigOptions
import com.statsig.androidsdk.StatsigUser
import com.uniswap.BuildConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/**
* OneSignal extension used to intercept notifications, integrating with Statsig to gate and expose
* test groups.
*/
class NotificationExtension : OSRemoteNotificationReceivedHandler {
private val scope = CoroutineScope(Dispatchers.IO)
override fun remoteNotificationReceived(
context: Context?,
notificationReceivedEvent: OSNotificationReceivedEvent
) {
val notification = notificationReceivedEvent.notification
val additionalData = notification.additionalData
val notificationType = if (additionalData.has(FIELD_NOTIFICATION_TYPE)) additionalData.getString(FIELD_NOTIFICATION_TYPE) else null
val isGatedNotification = notificationType == TYPE_UNFUNDED_WALLET_REMINDER ||
notificationType == TYPE_PRICE_ALERT
if (isGatedNotification) {
scope.launch(Dispatchers.IO) {
if (!Statsig.isInitialized()) {
val options = StatsigOptions(api = STATSIG_PROXY_URL, eventLoggingAPI = STATSIG_PROXY_URL).apply {
setEnvironmentParameter(STATSIG_ENVIRONMENT_KEY_TIER, getStatsigTier())
}
val deviceId = Secure.getString(context!!.contentResolver, Secure.ANDROID_ID)
val user = StatsigUser(userID = deviceId)
user.custom = mapOf("app" to "mobile")
Statsig.initialize(
context!!.applicationContext as Application,
STATSIG_SDK_KEY,
user,
options
)
}
val enabled = when(notificationType) {
TYPE_UNFUNDED_WALLET_REMINDER -> Statsig.checkGate(FEATURE_GATE_UNFUNDED_WALLET)
TYPE_PRICE_ALERT -> Statsig.checkGate(FEATURE_GATE_PRICE_ALERT)
else -> true
}
// Passing null will skip the notification
notificationReceivedEvent.complete(if (enabled) notification else null)
}
} else {
notificationReceivedEvent.complete(notification)
}
}
private fun getStatsigTier(): String = when(BuildConfig.FLAVOR) {
"dev" -> "development"
"beta" -> "beta"
"prod" -> "production"
else -> "production"
}
companion object {
// fake value that gets replaced by the proxy
private const val STATSIG_SDK_KEY = "client-000000000000000000000000000000000000000000"
private const val STATSIG_PROXY_URL =
"https://gating.android.wallet.gateway.uniswap.org/v1/statsig-proxy"
private const val STATSIG_ENVIRONMENT_KEY_TIER = "tier"
private const val FEATURE_GATE_UNFUNDED_WALLET = "notification_unfunded_wallet"
private const val FEATURE_GATE_PRICE_ALERT = "notification_price_alert"
private const val FIELD_NOTIFICATION_TYPE = "notification_type"
private const val TYPE_UNFUNDED_WALLET_REMINDER = "unfunded_wallet_reminder"
private const val TYPE_PRICE_ALERT = "price_alert"
}
}
...@@ -54,6 +54,12 @@ class MnemonicDisplayViewManager : ViewGroupManager<ComposeView>() { ...@@ -54,6 +54,12 @@ class MnemonicDisplayViewManager : ViewGroupManager<ComposeView>() {
putDouble(FIELD_HEIGHT, it.toDouble()) putDouble(FIELD_HEIGHT, it.toDouble())
} }
sendEvent(id, EVENT_HEIGHT_MEASURED, bundle) sendEvent(id, EVENT_HEIGHT_MEASURED, bundle)
},
onEmptyMnemonic = {
val bundle = Arguments.createMap().apply {
putString("mnemonicId", it)
}
sendEvent(id, EVENT_EMPTY_MNEMONIC, bundle)
} }
) )
} }
...@@ -75,6 +81,12 @@ class MnemonicDisplayViewManager : ViewGroupManager<ComposeView>() { ...@@ -75,6 +81,12 @@ class MnemonicDisplayViewManager : ViewGroupManager<ComposeView>() {
"bubbled" to EVENT_HEIGHT_MEASURED, "bubbled" to EVENT_HEIGHT_MEASURED,
"captured" to EVENT_HEIGHT_MEASURED "captured" to EVENT_HEIGHT_MEASURED
) )
),
EVENT_EMPTY_MNEMONIC to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to EVENT_EMPTY_MNEMONIC,
"captured" to EVENT_EMPTY_MNEMONIC
)
) )
) )
} }
...@@ -103,6 +115,7 @@ class MnemonicDisplayViewManager : ViewGroupManager<ComposeView>() { ...@@ -103,6 +115,7 @@ class MnemonicDisplayViewManager : ViewGroupManager<ComposeView>() {
companion object { companion object {
private const val REACT_CLASS = "MnemonicDisplay" private const val REACT_CLASS = "MnemonicDisplay"
private const val EVENT_HEIGHT_MEASURED = "onHeightMeasured" private const val EVENT_HEIGHT_MEASURED = "onHeightMeasured"
private const val EVENT_EMPTY_MNEMONIC = "onEmptyMnemonic"
private const val FIELD_HEIGHT = "height" private const val FIELD_HEIGHT = "height"
} }
} }
...@@ -24,6 +24,7 @@ import androidx.compose.ui.text.AnnotatedString ...@@ -24,6 +24,7 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.uniswap.onboarding.shared.CopyButton import com.uniswap.onboarding.shared.CopyButton
import com.uniswap.theme.relativeOffset import com.uniswap.theme.relativeOffset
import kotlinx.coroutines.withTimeoutOrNull
import kotlin.math.abs import kotlin.math.abs
@Composable @Composable
...@@ -32,7 +33,8 @@ fun MnemonicDisplay( ...@@ -32,7 +33,8 @@ fun MnemonicDisplay(
mnemonicId: String, mnemonicId: String,
copyText: String, copyText: String,
copiedText: String, copiedText: String,
onHeightMeasured: (height: Float) -> Unit onHeightMeasured: (height: Float) -> Unit,
onEmptyMnemonic: (mnemonicId: String) -> Unit
) { ) {
val words by viewModel.words.collectAsState() val words by viewModel.words.collectAsState()
val textToCopy = AnnotatedString(words.joinToString(" ") { it.text }) val textToCopy = AnnotatedString(words.joinToString(" ") { it.text })
...@@ -41,6 +43,16 @@ fun MnemonicDisplay( ...@@ -41,6 +43,16 @@ fun MnemonicDisplay(
LaunchedEffect(mnemonicId) { LaunchedEffect(mnemonicId) {
viewModel.setup(mnemonicId) viewModel.setup(mnemonicId)
// Check and log if the mnemonic is empty after 1 second to avoid calling onEmptyMnemonic too early
withTimeoutOrNull<Unit>(1000L) {
viewModel.words.collect { currentWords ->
if (currentWords.isEmpty() || currentWords.any { it.text.isBlank() }) {
onEmptyMnemonic(mnemonicId)
return@collect
}
}
}
} }
BoxWithConstraints { BoxWithConstraints {
......
...@@ -12,5 +12,4 @@ ...@@ -12,5 +12,4 @@
<item name="android:navigationBarColor">@color/background_material_light</item> <item name="android:navigationBarColor">@color/background_material_light</item>
<item name="android:editTextBackground">@android:color/transparent</item> <item name="android:editTextBackground">@android:color/transparent</item>
</style> </style>
</resources> </resources>
...@@ -19,6 +19,7 @@ buildscript { ...@@ -19,6 +19,7 @@ buildscript {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven { url 'https://jitpack.io' }
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle") classpath("com.android.tools.build:gradle")
......
...@@ -9,5 +9,11 @@ ...@@ -9,5 +9,11 @@
<key>NSExtensionPrincipalClass</key> <key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string> <string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict> </dict>
<key>OneSignal_app_groups_key</key>
<string>group.com.uniswap.mobile.onesignal</string>
<key>STATSIG_SDK_KEY</key>
<string>$(STATSIG_SDK_KEY)</string>
<key>BUNDLE_ID_SUFFIX</key>
<string>$(BUNDLE_ID_SUFFIX)</string>
</dict> </dict>
</plist> </plist>
// File copied from Onesignal docs: https://documentation.onesignal.com/docs/react-native-sdk-setup
import UserNotifications import UserNotifications
import OneSignalExtension import OneSignalExtension
import Statsig
class NotificationService: UNNotificationServiceExtension { class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var receivedRequest: UNNotificationRequest!
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.receivedRequest = request let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
self.contentHandler = contentHandler
self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) let userInfo = request.content.userInfo
// Fields per OneSignal docs
let custom = userInfo["custom"] as? [String: Any]
let additionalData = custom?["a"] as? [String: Any]
let notificationType = additionalData?[Constants.fieldNotificationType] as? String
let isGatedNotification = notificationType == Constants.typeUnfundedWallet
|| notificationType == Constants.typePriceAlert
if (!isGatedNotification) {
OneSignalExtension.didReceiveNotificationExtensionRequest(request, with: bestAttemptContent, withContentHandler: contentHandler)
return
}
func handleGatedNotification() {
let enabled: Bool
switch notificationType {
case Constants.typeUnfundedWallet:
enabled = Statsig.checkGate(Constants.gateUnfundedWallet)
case Constants.typePriceAlert:
enabled = Statsig.checkGate(Constants.gatePriceAlert)
default:
enabled = true
}
if let bestAttemptContent = bestAttemptContent { // Passing in empty notification content will skip the notif
/* DEBUGGING: Uncomment the 2 lines below to check this extension is executing OneSignalExtension.didReceiveNotificationExtensionRequest(
Note, this extension only runs when mutable-content is set request,
Setting an attachment or action buttons automatically adds this */ with: enabled ? bestAttemptContent : UNMutableNotificationContent(),
#if DEBUG withContentHandler: contentHandler)
print("Running NotificationServiceExtension") }
bestAttemptContent.body = "[Modified] " + bestAttemptContent.body
#endif if (!Statsig.isInitialized()) {
// The real sdk key is needed on iOS even though it's substituted in proxy
// Because the key is used to hash the feature gate names and wouldn't work properly otherwise
let statsigSdkKey = Bundle.main.object(forInfoDictionaryKey: "STATSIG_SDK_KEY") as? String ?? ""
let statsigUser = StatsigUser(
userID: UIDevice.current.identifierForVendor?.uuidString,
custom: [
"app": "mobile"
])
OneSignalExtension.didReceiveNotificationExtensionRequest(self.receivedRequest, with: bestAttemptContent, withContentHandler: self.contentHandler) Statsig.initialize(
sdkKey: statsigSdkKey,
user: statsigUser,
options: StatsigOptions(
environment: StatsigEnvironment(tier: getStatsigEnvironemntTier()),
initializationURL: URL(string: "\(Constants.statsigProxyHost)/v1/statsig-proxy/initialize"),
eventLoggingURL: URL(string: "\(Constants.statsigProxyHost)/v1/statsig-proxy/rgstr")
)) { _errorMessage in
handleGatedNotification()
}
} else {
handleGatedNotification()
} }
} }
override func serviceExtensionTimeWillExpire() { func getStatsigEnvironemntTier() -> String {
// Called just before the extension will be terminated by the system. let bundleSuffix = Bundle.main.object(forInfoDictionaryKey: "BUNDLE_ID_SUFFIX") as? String
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { switch bundleSuffix {
OneSignalExtension.serviceExtensionTimeWillExpireRequest(self.receivedRequest, with: self.bestAttemptContent) case ".dev":
contentHandler(bestAttemptContent) return "development"
case ".beta":
return "beta"
default:
return "production"
} }
} }
} }
struct Constants {
static let statsigProxyHost = "https://gating.ios.wallet.gateway.uniswap.org"
static let fieldNotificationType = "notification_type"
static let typeUnfundedWallet = "unfunded_wallet_reminder"
static let typePriceAlert = "price_alert"
static let gateUnfundedWallet = "notification_unfunded_wallet"
static let gatePriceAlert = "notification_price_alert"
}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<dict> <dict>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.com.$(PRODUCT_NAME).onesignal</string> <string>group.com.uniswap.mobile.onesignal</string>
</array> </array>
</dict> </dict>
</plist> </plist>
...@@ -57,6 +57,8 @@ end ...@@ -57,6 +57,8 @@ end
target 'OneSignalNotificationServiceExtension' do target 'OneSignalNotificationServiceExtension' do
use_frameworks! :linkage => :static use_frameworks! :linkage => :static
pod 'OneSignalXCFramework', '3.12.6' pod 'OneSignalXCFramework', '3.12.6'
pod 'Statsig', '1.49.0'
end end
def prepare_target_commons def prepare_target_commons
......
...@@ -1235,11 +1235,6 @@ PODS: ...@@ -1235,11 +1235,6 @@ PODS:
- ExpoModulesCore - ExpoModulesCore
- EXScreenCapture (5.8.1): - EXScreenCapture (5.8.1):
- ExpoModulesCore - ExpoModulesCore
- EXSplashScreen (0.26.5):
- ExpoModulesCore
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
- FBLazyVector (0.73.6) - FBLazyVector (0.73.6)
- FBReactNativeSpec (0.73.6): - FBReactNativeSpec (0.73.6):
- RCT-Folly (= 2022.05.16.00) - RCT-Folly (= 2022.05.16.00)
...@@ -1265,7 +1260,7 @@ PODS: ...@@ -1265,7 +1260,7 @@ PODS:
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- FirebaseAppCheckInterop (11.4.0) - FirebaseAppCheckInterop (11.6.0)
- FirebaseAuth (11.2.0): - FirebaseAuth (11.2.0):
- FirebaseAppCheckInterop (~> 11.0) - FirebaseAppCheckInterop (~> 11.0)
- FirebaseAuthInterop (~> 11.0) - FirebaseAuthInterop (~> 11.0)
...@@ -1275,14 +1270,14 @@ PODS: ...@@ -1275,14 +1270,14 @@ PODS:
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GTMSessionFetcher/Core (~> 3.4) - GTMSessionFetcher/Core (~> 3.4)
- RecaptchaInterop (~> 100.0) - RecaptchaInterop (~> 100.0)
- FirebaseAuthInterop (11.4.0) - FirebaseAuthInterop (11.6.0)
- FirebaseCore (11.2.0): - FirebaseCore (11.2.0):
- FirebaseCoreInternal (~> 11.0) - FirebaseCoreInternal (~> 11.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0) - GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreExtension (11.4.1): - FirebaseCoreExtension (11.4.1):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
- FirebaseCoreInternal (11.4.2): - FirebaseCoreInternal (11.6.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseFirestore (11.2.0): - FirebaseFirestore (11.2.0):
- FirebaseCore (~> 11.0) - FirebaseCore (~> 11.0)
...@@ -1304,7 +1299,7 @@ PODS: ...@@ -1304,7 +1299,7 @@ PODS:
- gRPC-Core (~> 1.65.0) - gRPC-Core (~> 1.65.0)
- leveldb-library (~> 1.22) - leveldb-library (~> 1.22)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseSharedSwift (11.4.0) - FirebaseSharedSwift (11.6.0)
- fmt (6.2.1) - fmt (6.2.1)
- glog (0.3.5) - glog (0.3.5)
- GoogleUtilities/AppDelegateSwizzler (8.0.2): - GoogleUtilities/AppDelegateSwizzler (8.0.2):
...@@ -1439,9 +1434,9 @@ PODS: ...@@ -1439,9 +1434,9 @@ PODS:
- libwebp/sharpyuv (1.3.2) - libwebp/sharpyuv (1.3.2)
- libwebp/webp (1.3.2): - libwebp/webp (1.3.2):
- libwebp/sharpyuv - libwebp/sharpyuv
- MMKV (1.3.4): - MMKV (2.0.0):
- MMKVCore (~> 1.3.4) - MMKVCore (~> 2.0.0)
- MMKVCore (1.3.4) - MMKVCore (2.0.0)
- nanopb (3.30910.0): - nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0) - nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0) - nanopb/encode (= 3.30910.0)
...@@ -2542,6 +2537,8 @@ PODS: ...@@ -2542,6 +2537,8 @@ PODS:
- ReactNativePerformance (4.1.2): - ReactNativePerformance (4.1.2):
- React-Core - React-Core
- RecaptchaInterop (100.0.0) - RecaptchaInterop (100.0.0)
- RNBootSplash (6.3.1):
- React-Core
- RNCAsyncStorage (1.17.10): - RNCAsyncStorage (1.17.10):
- React-Core - React-Core
- RNCMaskedView (0.2.9): - RNCMaskedView (0.2.9):
...@@ -2613,6 +2610,7 @@ PODS: ...@@ -2613,6 +2610,7 @@ PODS:
- SocketRocket (0.6.1) - SocketRocket (0.6.1)
- sparkfabrik-react-native-idfa-aaid (1.2.0): - sparkfabrik-react-native-idfa-aaid (1.2.0):
- React - React
- Statsig (1.49.0)
- UIImageColors (2.1.0) - UIImageColors (2.1.0)
- Yoga (1.14.0) - Yoga (1.14.0)
- ZXingObjC/Core (3.6.9) - ZXingObjC/Core (3.6.9)
...@@ -2646,7 +2644,6 @@ DEPENDENCIES: ...@@ -2646,7 +2644,6 @@ DEPENDENCIES:
- ExpoStoreReview (from `../../../node_modules/expo-store-review/ios`) - ExpoStoreReview (from `../../../node_modules/expo-store-review/ios`)
- ExpoWebBrowser (from `../../../node_modules/expo-web-browser/ios`) - ExpoWebBrowser (from `../../../node_modules/expo-web-browser/ios`)
- EXScreenCapture (from `../../../node_modules/expo-screen-capture/ios`) - EXScreenCapture (from `../../../node_modules/expo-screen-capture/ios`)
- EXSplashScreen (from `../../../node_modules/expo-splash-screen/ios`)
- FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`) - FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../../../node_modules/react-native/React/FBReactNativeSpec`) - FBReactNativeSpec (from `../../../node_modules/react-native/React/FBReactNativeSpec`)
- glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`) - glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`)
...@@ -2712,6 +2709,7 @@ DEPENDENCIES: ...@@ -2712,6 +2709,7 @@ DEPENDENCIES:
- React-utils (from `../../../node_modules/react-native/ReactCommon/react/utils`) - React-utils (from `../../../node_modules/react-native/ReactCommon/react/utils`)
- ReactCommon/turbomodule/core (from `../../../node_modules/react-native/ReactCommon`) - ReactCommon/turbomodule/core (from `../../../node_modules/react-native/ReactCommon`)
- "ReactNativePerformance (from `../../../node_modules/@shopify/react-native-performance`)" - "ReactNativePerformance (from `../../../node_modules/@shopify/react-native-performance`)"
- RNBootSplash (from `../../../node_modules/react-native-bootsplash`)
- "RNCAsyncStorage (from `../../../node_modules/@react-native-async-storage/async-storage`)" - "RNCAsyncStorage (from `../../../node_modules/@react-native-async-storage/async-storage`)"
- "RNCMaskedView (from `../../../node_modules/@react-native-masked-view/masked-view`)" - "RNCMaskedView (from `../../../node_modules/@react-native-masked-view/masked-view`)"
- "RNDateTimePicker (from `../../../node_modules/@react-native-community/datetimepicker`)" - "RNDateTimePicker (from `../../../node_modules/@react-native-community/datetimepicker`)"
...@@ -2730,6 +2728,7 @@ DEPENDENCIES: ...@@ -2730,6 +2728,7 @@ DEPENDENCIES:
- RNScreens (from `../../../node_modules/react-native-screens`) - RNScreens (from `../../../node_modules/react-native-screens`)
- RNSVG (from `../../../node_modules/react-native-svg`) - RNSVG (from `../../../node_modules/react-native-svg`)
- "sparkfabrik-react-native-idfa-aaid (from `../../../node_modules/@sparkfabrik/react-native-idfa-aaid`)" - "sparkfabrik-react-native-idfa-aaid (from `../../../node_modules/@sparkfabrik/react-native-idfa-aaid`)"
- Statsig (= 1.49.0)
- UIImageColors (= 2.1.0) - UIImageColors (= 2.1.0)
- Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`) - Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`)
...@@ -2778,6 +2777,7 @@ SPEC REPOS: ...@@ -2778,6 +2777,7 @@ SPEC REPOS:
- SDWebImage - SDWebImage
- SDWebImageWebPCoder - SDWebImageWebPCoder
- SocketRocket - SocketRocket
- Statsig
- UIImageColors - UIImageColors
- ZXingObjC - ZXingObjC
...@@ -2826,8 +2826,6 @@ EXTERNAL SOURCES: ...@@ -2826,8 +2826,6 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/expo-web-browser/ios" :path: "../../../node_modules/expo-web-browser/ios"
EXScreenCapture: EXScreenCapture:
:path: "../../../node_modules/expo-screen-capture/ios" :path: "../../../node_modules/expo-screen-capture/ios"
EXSplashScreen:
:path: "../../../node_modules/expo-splash-screen/ios"
FBLazyVector: FBLazyVector:
:path: "../../../node_modules/react-native/Libraries/FBLazyVector" :path: "../../../node_modules/react-native/Libraries/FBLazyVector"
FBReactNativeSpec: FBReactNativeSpec:
...@@ -2951,6 +2949,8 @@ EXTERNAL SOURCES: ...@@ -2951,6 +2949,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native/ReactCommon" :path: "../../../node_modules/react-native/ReactCommon"
ReactNativePerformance: ReactNativePerformance:
:path: "../../../node_modules/@shopify/react-native-performance" :path: "../../../node_modules/@shopify/react-native-performance"
RNBootSplash:
:path: "../../../node_modules/react-native-bootsplash"
RNCAsyncStorage: RNCAsyncStorage:
:path: "../../../node_modules/@react-native-async-storage/async-storage" :path: "../../../node_modules/@react-native-async-storage/async-storage"
RNCMaskedView: RNCMaskedView:
...@@ -3026,20 +3026,19 @@ SPEC CHECKSUMS: ...@@ -3026,20 +3026,19 @@ SPEC CHECKSUMS:
ExpoStoreReview: 3911cdf5a416230a421af41f93e763595bd7a681 ExpoStoreReview: 3911cdf5a416230a421af41f93e763595bd7a681
ExpoWebBrowser: e31c97230ea93b51a2a74c6c2381b0d4fca5698c ExpoWebBrowser: e31c97230ea93b51a2a74c6c2381b0d4fca5698c
EXScreenCapture: 7a491c16de021638078f50281d411e3352bb28c2 EXScreenCapture: 7a491c16de021638078f50281d411e3352bb28c2
EXSplashScreen: 0fabdcf746d29e7f8b8969879cb09125cdd365d2
FBLazyVector: f64d1e2ea739b4d8f7e4740cde18089cd97fe864 FBLazyVector: f64d1e2ea739b4d8f7e4740cde18089cd97fe864
FBReactNativeSpec: 91c0784dbf98ed9c434927ea46f41b780fe3a232 FBReactNativeSpec: 91c0784dbf98ed9c434927ea46f41b780fe3a232
Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c
FirebaseAppCheck: a6a1c1ca169d795212b9e70b5cfb880083a28e7c FirebaseAppCheck: a6a1c1ca169d795212b9e70b5cfb880083a28e7c
FirebaseAppCheckInterop: 1b9643ae2f1ee214488caa2f8e32b7bc2f0f3735 FirebaseAppCheckInterop: 347aa09a805219a31249b58fc956888e9fcb314b
FirebaseAuth: 2a198b8cdbbbd457f08d74df7040feb0a0e7777a FirebaseAuth: 2a198b8cdbbbd457f08d74df7040feb0a0e7777a
FirebaseAuthInterop: 9ac948965ac13ec9d8a080f39490ddb2bda30520 FirebaseAuthInterop: a919d415797d23b7bfe195a04f322b86c65020ef
FirebaseCore: a282032ae9295c795714ded2ec9c522fc237f8da FirebaseCore: a282032ae9295c795714ded2ec9c522fc237f8da
FirebaseCoreExtension: f1bc67a4702931a7caa097d8e4ac0a1b0d16720e FirebaseCoreExtension: f1bc67a4702931a7caa097d8e4ac0a1b0d16720e
FirebaseCoreInternal: 35731192cab10797b88411be84940d2beb33a238 FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
FirebaseFirestore: 62708adbc1dfcd6d165a7c0a202067b441912dc9 FirebaseFirestore: 62708adbc1dfcd6d165a7c0a202067b441912dc9
FirebaseFirestoreInternal: ad9b9ee2d3d430c8f31333a69b3b6737a7206232 FirebaseFirestoreInternal: ad9b9ee2d3d430c8f31333a69b3b6737a7206232
FirebaseSharedSwift: 505dae2d05969dbf6d43749a642bb1bf230f0252 FirebaseSharedSwift: a4e5dfca3e210633bb3a3dfb94176c019211948b
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
...@@ -3050,8 +3049,8 @@ SPEC CHECKSUMS: ...@@ -3050,8 +3049,8 @@ SPEC CHECKSUMS:
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
MMKV: ed58ad794b3f88c24d604a5b74f3fba17fcbaf74 MMKV: f7d1d5945c8765f97f39c3d121f353d46735d801
MMKVCore: a67a1cede26175c413176f404a7cedec43f96a0b MMKVCore: c04b296010fcb1d1638f2c69405096aac12f6390
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
OneSignalXCFramework: ff1c970b7aeb4ac0fe48fb35393eb5d8bf378135 OneSignalXCFramework: ff1c970b7aeb4ac0fe48fb35393eb5d8bf378135
OpenTelemetrySwiftApi: 657da8071c2908caecce11548e006f779924ff9c OpenTelemetrySwiftApi: 657da8071c2908caecce11548e006f779924ff9c
...@@ -3115,6 +3114,7 @@ SPEC CHECKSUMS: ...@@ -3115,6 +3114,7 @@ SPEC CHECKSUMS:
ReactCommon: e168ee1704806f10f47ee8c7f825b5850b18eaea ReactCommon: e168ee1704806f10f47ee8c7f825b5850b18eaea
ReactNativePerformance: ab7dee4c4862623d72c1530a9fc71b55458edf71 ReactNativePerformance: ab7dee4c4862623d72c1530a9fc71b55458edf71
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21 RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
RNBootSplash: 66c8458007bda40cc25a3f25e4326244a71d9a73
RNCAsyncStorage: 0c357f3156fcb16c8589ede67cc036330b6698ca RNCAsyncStorage: 0c357f3156fcb16c8589ede67cc036330b6698ca
RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6 RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6
RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14 RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14
...@@ -3136,10 +3136,11 @@ SPEC CHECKSUMS: ...@@ -3136,10 +3136,11 @@ SPEC CHECKSUMS:
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
sparkfabrik-react-native-idfa-aaid: 1b72a6264a2175473e309ffa6434db87c58af264 sparkfabrik-react-native-idfa-aaid: 1b72a6264a2175473e309ffa6434db87c58af264
Statsig: 970abcd107e8e64bb68f6b8504a94c39d7f9e318
UIImageColors: d2ef3b0877d203cbb06489eeb78ea8b7788caabe UIImageColors: d2ef3b0877d203cbb06489eeb78ea8b7788caabe
Yoga: 805bf71192903b20fc14babe48080582fee65a80 Yoga: 805bf71192903b20fc14babe48080582fee65a80
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
PODFILE CHECKSUM: 525fd4a1c78879023ae05970b18e66b654c4c07a PODFILE CHECKSUM: 18445ed90dc0fd39adbfc715d3fbf6316e3013ad
COCOAPODS: 1.14.3 COCOAPODS: 1.14.3
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#import <React/RCTBundleURLProvider.h> #import <React/RCTBundleURLProvider.h>
#import <ReactNativePerformance/ReactNativePerformance.h> #import <ReactNativePerformance/ReactNativePerformance.h>
#import <RCTAppSetupUtils.h> #import <RCTAppSetupUtils.h>
#import <RNBootSplash.h>
@implementation AppDelegate @implementation AppDelegate
...@@ -48,6 +49,7 @@ ...@@ -48,6 +49,7 @@
[super application:application didFinishLaunchingWithOptions:newLaunchOptions]; [super application:application didFinishLaunchingWithOptions:newLaunchOptions];
[[RCTI18nUtil sharedInstance] allowRTL:NO]; [[RCTI18nUtil sharedInstance] allowRTL:NO];
[RNBootSplash initWithStoryboard:@"SplashScreen" rootView:self.window.rootViewController.view];
return YES; return YES;
} }
......
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string/> <string></string>
<key>LSApplicationQueriesSchemes</key> <key>LSApplicationQueriesSchemes</key>
<array> <array>
<string>itms-apps</string> <string>itms-apps</string>
...@@ -85,8 +85,6 @@ ...@@ -85,8 +85,6 @@
</dict> </dict>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) Wallet needs access to your Camera to scan QR codes</string> <string>$(PRODUCT_NAME) Wallet needs access to your Camera to scan QR codes</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) Wallet needs access to your Camera Roll to choose an avatar for your username</string>
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
<string>Enabling Face ID helps $(PRODUCT_NAME) Wallet keep your assets secure.</string> <string>Enabling Face ID helps $(PRODUCT_NAME) Wallet keep your assets secure.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
...@@ -95,6 +93,8 @@ ...@@ -95,6 +93,8 @@
<string>$(PRODUCT_NAME) Wallet does not require access to your location.</string> <string>$(PRODUCT_NAME) Wallet does not require access to your location.</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) Wallet does not require access to the microphone.</string> <string>$(PRODUCT_NAME) Wallet does not require access to the microphone.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) Wallet needs access to your Camera Roll to choose an avatar for your username</string>
<key>NSUbiquitousContainers</key> <key>NSUbiquitousContainers</key>
<dict> <dict>
<key>iCloud.Uniswap</key> <key>iCloud.Uniswap</key>
...@@ -111,6 +111,8 @@ ...@@ -111,6 +111,8 @@
<array> <array>
<string>TokenPriceConfigurationIntent</string> <string>TokenPriceConfigurationIntent</string>
</array> </array>
<key>OneSignal_app_groups_key</key>
<string>group.com.uniswap.mobile.onesignal</string>
<key>OneSignal_suppress_launch_urls</key> <key>OneSignal_suppress_launch_urls</key>
<true/> <true/>
<key>UIAppFonts</key> <key>UIAppFonts</key>
......
...@@ -19,6 +19,7 @@ RCT_EXPORT_SWIFTUI_PROPERTY(mnemonicId, NSString, MnemonicDisplayView); ...@@ -19,6 +19,7 @@ RCT_EXPORT_SWIFTUI_PROPERTY(mnemonicId, NSString, MnemonicDisplayView);
RCT_EXPORT_SWIFTUI_PROPERTY(copyText, NSString, MnemonicDisplayView); RCT_EXPORT_SWIFTUI_PROPERTY(copyText, NSString, MnemonicDisplayView);
RCT_EXPORT_SWIFTUI_PROPERTY(copiedText, NSString, MnemonicDisplayView); RCT_EXPORT_SWIFTUI_PROPERTY(copiedText, NSString, MnemonicDisplayView);
RCT_EXPORT_SWIFTUI_CALLBACK(onHeightMeasured, RCTDirectEventBlock, MnemonicDisplayView) RCT_EXPORT_SWIFTUI_CALLBACK(onHeightMeasured, RCTDirectEventBlock, MnemonicDisplayView)
RCT_EXPORT_SWIFTUI_CALLBACK(onEmptyMnemonic, RCTDirectEventBlock, MnemonicDisplayView)
- (UIView *)view { - (UIView *)view {
MnemonicDisplayView *proxy = [[MnemonicDisplayView alloc] init]; MnemonicDisplayView *proxy = [[MnemonicDisplayView alloc] init];
......
...@@ -35,6 +35,14 @@ import SwiftUI ...@@ -35,6 +35,14 @@ import SwiftUI
} }
} }
var onEmptyMnemonic: RCTDirectEventBlock? {
didSet {
vc.rootView.props.onEmptyMnemonic = { [weak self] mnemonicId in
self?.onEmptyMnemonic?(["mnemonicId": mnemonicId])
}
}
}
var view: UIView { var view: UIView {
vc.view.backgroundColor = .clear vc.view.backgroundColor = .clear
return vc.view return vc.view
...@@ -47,6 +55,7 @@ class MnemonicDisplayProps: ObservableObject { ...@@ -47,6 +55,7 @@ class MnemonicDisplayProps: ObservableObject {
@Published var copiedText: String = "" @Published var copiedText: String = ""
@Published var mnemonicWords: [String] = Array(repeating: "", count: 12) @Published var mnemonicWords: [String] = Array(repeating: "", count: 12)
var onHeightMeasured: ((CGFloat) -> Void)? var onHeightMeasured: ((CGFloat) -> Void)?
var onEmptyMnemonic: ((String) -> Void)?
} }
struct MnemonicDisplay: View { struct MnemonicDisplay: View {
...@@ -137,5 +146,10 @@ struct MnemonicDisplay: View { ...@@ -137,5 +146,10 @@ struct MnemonicDisplay: View {
} }
} }
) )
.onAppear {
if props.mnemonicWords.isEmpty || props.mnemonicWords.allSatisfy({ $0.isEmpty }) {
props.onEmptyMnemonic?(props.mnemonicId)
}
}
} }
} }
...@@ -27,8 +27,8 @@ ...@@ -27,8 +27,8 @@
</array> </array>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.com.$(PRODUCT_NAME).onesignal</string>
<string>group.com.uniswap.widgets</string> <string>group.com.uniswap.widgets</string>
<string>group.com.uniswap.mobile.onesignal</string>
</array> </array>
</dict> </dict>
</plist> </plist>
...@@ -101,3 +101,14 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({ ...@@ -101,3 +101,14 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({
jest.mock('openai') jest.mock('openai')
jest.mock("react-native-bootsplash", () => {
return {
hide: jest.fn().mockResolvedValue(),
isVisible: jest.fn().mockResolvedValue(false),
useHideAnimation: jest.fn().mockReturnValue({
container: {},
logo: { source: 0 },
brand: { source: 0 },
}),
};
});
...@@ -19,9 +19,10 @@ ...@@ -19,9 +19,10 @@
"env:android:keystore:download": "bash ./scripts/downloadAndroidKeystore.sh", "env:android:keystore:download": "bash ./scripts/downloadAndroidKeystore.sh",
"env:fastlane:download": "bash ./scripts/downloadFastlaneEnv.sh", "env:fastlane:download": "bash ./scripts/downloadFastlaneEnv.sh",
"env:fastlane:upload": "bash ./scripts/uploadFastlaneEnv.sh", "env:fastlane:upload": "bash ./scripts/uploadFastlaneEnv.sh",
"env:local:download": "bash ../../scripts/downloadEnvLocal.sh mobile-local-envs ../../.env.defaults.local && yarn env:local:copy:swift", "env:local:download": "bash ../../scripts/downloadEnvLocal.sh xmznnx7ozuojy5lnohcmt73aee ../../.env.defaults.local && yarn env:local:copy:swift",
"env:local:upload": "bash ../../scripts/uploadEnvLocal.sh mobile-local-envs ../../.env.defaults.local", "env:local:upload": "bash ../../scripts/uploadEnvLocal.sh xmznnx7ozuojy5lnohcmt73aee ../../.env.defaults.local",
"env:local:copy:swift": "python3 scripts/copy_env_vars_to_swift.py", "env:local:copy:swift": "python3 scripts/copy_env_vars_to_swift.py",
"e2e": "maestro test \".maestro/flows/$*\"",
"e2e:packager": "DETOX_MODE=mocked yarn start", "e2e:packager": "DETOX_MODE=mocked yarn start",
"e2e:android:build:debug": "DETOX_MODE=mocked detox build -c android.emu.debug", "e2e:android:build:debug": "DETOX_MODE=mocked detox build -c android.emu.debug",
"e2e:android:test:debug": "detox test -c android.emu.debug", "e2e:android:test:debug": "detox test -c android.emu.debug",
...@@ -114,7 +115,6 @@ ...@@ -114,7 +115,6 @@
"expo-local-authentication": "13.8.0", "expo-local-authentication": "13.8.0",
"expo-localization": "14.8.3", "expo-localization": "14.8.3",
"expo-screen-capture": "5.8.1", "expo-screen-capture": "5.8.1",
"expo-splash-screen": "0.26.5",
"expo-store-review": "6.8.3", "expo-store-review": "6.8.3",
"fuse.js": "6.5.3", "fuse.js": "6.5.3",
"i18next": "23.10.0", "i18next": "23.10.0",
...@@ -125,6 +125,7 @@ ...@@ -125,6 +125,7 @@
"react-i18next": "14.1.0", "react-i18next": "14.1.0",
"react-native": "0.73.6", "react-native": "0.73.6",
"react-native-appsflyer": "6.13.1", "react-native-appsflyer": "6.13.1",
"react-native-bootsplash": "6.3.1",
"react-native-context-menu-view": "1.15.0", "react-native-context-menu-view": "1.15.0",
"react-native-device-info": "10.0.2", "react-native-device-info": "10.0.2",
"react-native-fast-image": "8.6.3", "react-native-fast-image": "8.6.3",
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
set -e set -e
REQUIRED_XCODE_VERSION="16.1" REQUIRED_XCODE_VERSION="16.2"
check_xcode_version() { check_xcode_version() {
local current_version=$(xcodebuild -version | grep "Xcode" | cut -d' ' -f2) local current_version=$(xcodebuild -version | grep "Xcode" | cut -d' ' -f2)
...@@ -23,4 +23,3 @@ cd ios/ ...@@ -23,4 +23,3 @@ cd ios/
bundle install bundle install
bundle exec pod install bundle exec pod install
cd .. cd ..
import { ApolloProvider } from '@apollo/client' import { ApolloProvider } from '@apollo/client'
import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev' import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev'
import { DdRum, DdSdkReactNative } from '@datadog/mobile-react-native' import { DdRum, DdSdkReactNative, RumActionType } from '@datadog/mobile-react-native'
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
import { PerformanceProfiler, RenderPassReport } from '@shopify/react-native-performance' import { PerformanceProfiler, RenderPassReport } from '@shopify/react-native-performance'
import { MMKVWrapper } from 'apollo3-cache-persist' import { MMKVWrapper } from 'apollo3-cache-persist'
import * as SplashScreen from 'expo-splash-screen' import { default as React, StrictMode, useCallback, useEffect, useRef } from 'react'
import { default as React, StrictMode, useCallback, useEffect } from 'react'
import { I18nextProvider } from 'react-i18next' import { I18nextProvider } from 'react-i18next'
import { LogBox, NativeModules, StatusBar } from 'react-native' import { LogBox, NativeModules, StatusBar } from 'react-native'
import appsFlyer from 'react-native-appsflyer' import appsFlyer from 'react-native-appsflyer'
...@@ -65,7 +64,7 @@ import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/conte ...@@ -65,7 +64,7 @@ import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/conte
import i18n from 'uniswap/src/i18n' import i18n from 'uniswap/src/i18n'
import { CurrencyId } from 'uniswap/src/types/currency' import { CurrencyId } from 'uniswap/src/types/currency'
import { getUniqueId } from 'utilities/src/device/getUniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { isDetoxBuild } from 'utilities/src/environment/constants' import { datadogEnabled, isDetoxBuild } from 'utilities/src/environment/constants'
import { attachUnhandledRejectionHandler, setAttributesToDatadog } from 'utilities/src/logger/Datadog' import { attachUnhandledRejectionHandler, setAttributesToDatadog } from 'utilities/src/logger/Datadog'
import { registerConsoleOverrides } from 'utilities/src/logger/console' import { registerConsoleOverrides } from 'utilities/src/logger/console'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -93,9 +92,6 @@ if (__DEV__) { ...@@ -93,9 +92,6 @@ if (__DEV__) {
loadErrorMessages() loadErrorMessages()
} }
// Keep the splash screen visible while we fetch resources until one of our landing pages loads
SplashScreen.preventAutoHideAsync().catch(() => undefined)
// Log boxes on simulators can block detox tap event when they cover buttons placed at // Log boxes on simulators can block detox tap event when they cover buttons placed at
// the bottom of the screen and cause tests to fail. // the bottom of the screen and cause tests to fail.
if (isDetoxBuild) { if (isDetoxBuild) {
...@@ -185,8 +181,26 @@ function AppOuter(): JSX.Element | null { ...@@ -185,8 +181,26 @@ function AppOuter(): JSX.Element | null {
customEndpoint, customEndpoint,
reduxStore: store, reduxStore: store,
}) })
const jsBundleLoadedRef = useRef(false)
/**
* Function called by the @shopify/react-native-performance PerformanceProfiler that returns a
* RenderPassReport. We then forward this report to Datadog, Amplitude, etc.
*/
const onReportPrepared = useCallback(async (report: RenderPassReport) => {
if (datadogEnabled) {
const shouldLogJsBundleLoaded = report.timeToBootJsMillis && !jsBundleLoadedRef.current
if (shouldLogJsBundleLoaded) {
await DdRum.addAction(RumActionType.CUSTOM, 'application_start_js', {
loading_time: report.timeToBootJsMillis,
})
jsBundleLoadedRef.current = true
}
if (report.interactive) {
await DdRum.addTiming('screenInteractive')
}
}
const onReportPrepared = useCallback((report: RenderPassReport) => {
sendAnalyticsEvent(MobileEventName.PerformanceReport, report) sendAnalyticsEvent(MobileEventName.PerformanceReport, report)
}, []) }, [])
......
...@@ -8,7 +8,7 @@ import { AccountList } from 'src/components/accounts/AccountList' ...@@ -8,7 +8,7 @@ import { AccountList } from 'src/components/accounts/AccountList'
import { isCloudStorageAvailable } from 'src/features/CloudBackup/RNCloudStorageBackupsManager' import { isCloudStorageAvailable } from 'src/features/CloudBackup/RNCloudStorageBackupsManager'
import { closeModal, openModal } from 'src/features/modals/modalSlice' import { closeModal, openModal } from 'src/features/modals/modalSlice'
import { selectModalState } from 'src/features/modals/selectModalState' import { selectModalState } from 'src/features/modals/selectModalState'
import { Button, Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { DeprecatedButton, Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { ActionSheetModal, MenuItemProp } from 'uniswap/src/components/modals/ActionSheetModal' import { ActionSheetModal, MenuItemProp } from 'uniswap/src/components/modals/ActionSheetModal'
...@@ -268,9 +268,9 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -268,9 +268,9 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
variant="subheading1" variant="subheading1"
/> />
<Flex px="$spacing24"> <Flex px="$spacing24">
<Button size="small" testID={TestID.WalletSettings} theme="secondary" onPress={onManageWallet}> <DeprecatedButton size="small" testID={TestID.WalletSettings} theme="secondary" onPress={onManageWallet}>
{t('account.wallet.button.manage')} {t('account.wallet.button.manage')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
<Flex maxHeight={fullScreenContentHeight / 2}> <Flex maxHeight={fullScreenContentHeight / 2}>
......
...@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux' ...@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { closeModal, openModal } from 'src/features/modals/modalSlice' import { closeModal, openModal } from 'src/features/modals/modalSlice'
import { LockPreviewImage } from 'src/features/onboarding/LockPreviewImage' import { LockPreviewImage } from 'src/features/onboarding/LockPreviewImage'
import { Button, Flex, Text } from 'ui/src' import { DeprecatedButton, Flex, Text } from 'ui/src'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
...@@ -60,7 +60,7 @@ export function BackupReminderModal(): JSX.Element { ...@@ -60,7 +60,7 @@ export function BackupReminderModal(): JSX.Element {
</Flex> </Flex>
<Flex row gap="$spacing8"> <Flex row gap="$spacing8">
<Trace logPress element={ElementName.MaybeLaterButton} modal={ModalName.BackupReminder}> <Trace logPress element={ElementName.MaybeLaterButton} modal={ModalName.BackupReminder}>
<Button <DeprecatedButton
alignSelf="center" alignSelf="center"
color="$neutral2" color="$neutral2"
flex={1} flex={1}
...@@ -69,12 +69,12 @@ export function BackupReminderModal(): JSX.Element { ...@@ -69,12 +69,12 @@ export function BackupReminderModal(): JSX.Element {
onPress={onPressMaybeLater} onPress={onPressMaybeLater}
> >
{t('common.button.later')} {t('common.button.later')}
</Button> </DeprecatedButton>
</Trace> </Trace>
<Trace logPress element={ElementName.Continue} modal={ModalName.BackupReminder}> <Trace logPress element={ElementName.Continue} modal={ModalName.BackupReminder}>
<Button alignSelf="center" flex={1} size="medium" theme="primary" onPress={onPressBackup}> <DeprecatedButton alignSelf="center" flex={1} size="medium" theme="primary" onPress={onPressBackup}>
{t('common.button.continue')} {t('common.button.continue')}
</Button> </DeprecatedButton>
</Trace> </Trace>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -6,7 +6,7 @@ import { Action } from 'redux' ...@@ -6,7 +6,7 @@ import { Action } from 'redux'
import { closeModal } from 'src/features/modals/modalSlice' import { closeModal } from 'src/features/modals/modalSlice'
import { selectCustomEndpoint } from 'src/features/tweaks/selectors' import { selectCustomEndpoint } from 'src/features/tweaks/selectors'
import { setCustomEndpoint } from 'src/features/tweaks/slice' import { setCustomEndpoint } from 'src/features/tweaks/slice'
import { Accordion, Button, Flex, Text } from 'ui/src' import { Accordion, DeprecatedButton, Flex, Text } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { TextInput } from 'uniswap/src/components/input/TextInput' import { TextInput } from 'uniswap/src/components/input/TextInput'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -78,13 +78,13 @@ export function ExperimentsModal(): JSX.Element { ...@@ -78,13 +78,13 @@ export function ExperimentsModal(): JSX.Element {
</Flex> </Flex>
<Flex grow row alignItems="center" gap="$spacing16"> <Flex grow row alignItems="center" gap="$spacing16">
<Button flex={1} size="small" onPress={setEndpoint}> <DeprecatedButton flex={1} size="small" onPress={setEndpoint}>
Set Set
</Button> </DeprecatedButton>
<Button flex={1} size="small" onPress={clearEndpoint}> <DeprecatedButton flex={1} size="small" onPress={clearEndpoint}>
Clear Clear
</Button> </DeprecatedButton>
</Flex> </Flex>
</Accordion.Content> </Accordion.Content>
</Accordion.Item> </Accordion.Item>
...@@ -93,9 +93,9 @@ export function ExperimentsModal(): JSX.Element { ...@@ -93,9 +93,9 @@ export function ExperimentsModal(): JSX.Element {
<AccordionHeader title="🚀 Apollo Cache" /> <AccordionHeader title="🚀 Apollo Cache" />
<Accordion.Content> <Accordion.Content>
<Button flex={1} size="small" onPress={async (): Promise<unknown> => await apollo.resetStore()}> <DeprecatedButton flex={1} size="small" onPress={async (): Promise<unknown> => await apollo.resetStore()}>
Reset Cache Reset Cache
</Button> </DeprecatedButton>
</Accordion.Content> </Accordion.Content>
</Accordion.Item> </Accordion.Item>
......
...@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' ...@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { Action } from 'redux' import { Action } from 'redux'
import { closeModal } from 'src/features/modals/modalSlice' import { closeModal } from 'src/features/modals/modalSlice'
import { Button, Flex, Image, Text, useIsDarkMode, useSporeColors } from 'ui/src' import { DeprecatedButton, Flex, Image, Text, useIsDarkMode, useSporeColors } from 'ui/src'
import { CEX_TRANSFER_MODAL_BG_DARK, CEX_TRANSFER_MODAL_BG_LIGHT } from 'ui/src/assets' import { CEX_TRANSFER_MODAL_BG_DARK, CEX_TRANSFER_MODAL_BG_LIGHT } from 'ui/src/assets'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
...@@ -39,14 +39,14 @@ export function KoreaCexTransferInfoModal(): JSX.Element { ...@@ -39,14 +39,14 @@ export function KoreaCexTransferInfoModal(): JSX.Element {
{t('fiatOnRamp.cexTransferModal.description')} {t('fiatOnRamp.cexTransferModal.description')}
</Text> </Text>
</Flex> </Flex>
<Button <DeprecatedButton
color="$neutral1" color="$neutral1"
mt="$spacing8" mt="$spacing8"
theme="secondary" theme="secondary"
onPress={() => openURL(uniswapUrls.helpArticleUrls.cexTransferKorea)} onPress={() => openURL(uniswapUrls.helpArticleUrls.cexTransferKorea)}
> >
{t('common.button.learn')} {t('common.button.learn')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Modal> </Modal>
) )
......
...@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next' ...@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { closeModal, openModal } from 'src/features/modals/modalSlice' import { closeModal, openModal } from 'src/features/modals/modalSlice'
import { Button, Flex, Text, useIsDarkMode } from 'ui/src' import { DeprecatedButton, Flex, Text, useIsDarkMode } from 'ui/src'
import ViewOnlyWalletDark from 'ui/src/assets/graphics/view-only-wallet-dark.svg' import ViewOnlyWalletDark from 'ui/src/assets/graphics/view-only-wallet-dark.svg'
import ViewOnlyWalletLight from 'ui/src/assets/graphics/view-only-wallet-light.svg' import ViewOnlyWalletLight from 'ui/src/assets/graphics/view-only-wallet-light.svg'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -53,10 +53,16 @@ export function ViewOnlyExplainerModal(): JSX.Element { ...@@ -53,10 +53,16 @@ export function ViewOnlyExplainerModal(): JSX.Element {
</Flex> </Flex>
</Flex> </Flex>
<Flex gap="$spacing8"> <Flex gap="$spacing8">
<Button alignSelf="center" borderRadius="$rounded20" px={40} theme="primary" onPress={onPressImportWallet}> <DeprecatedButton
alignSelf="center"
borderRadius="$rounded20"
px={40}
theme="primary"
onPress={onPressImportWallet}
>
{t('account.wallet.viewOnly.button')} {t('account.wallet.viewOnly.button')}
</Button> </DeprecatedButton>
<Button <DeprecatedButton
alignSelf="center" alignSelf="center"
backgroundColor={undefined} backgroundColor={undefined}
borderRadius="$rounded20" borderRadius="$rounded20"
...@@ -66,7 +72,7 @@ export function ViewOnlyExplainerModal(): JSX.Element { ...@@ -66,7 +72,7 @@ export function ViewOnlyExplainerModal(): JSX.Element {
onPress={onClose} onPress={onClose}
> >
{t('common.button.later')} {t('common.button.later')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</Modal> </Modal>
......
...@@ -64,6 +64,7 @@ import { SettingsCloudBackupPasswordConfirmScreen } from 'src/screens/SettingsCl ...@@ -64,6 +64,7 @@ import { SettingsCloudBackupPasswordConfirmScreen } from 'src/screens/SettingsCl
import { SettingsCloudBackupPasswordCreateScreen } from 'src/screens/SettingsCloudBackupPasswordCreateScreen' import { SettingsCloudBackupPasswordCreateScreen } from 'src/screens/SettingsCloudBackupPasswordCreateScreen'
import { SettingsCloudBackupProcessingScreen } from 'src/screens/SettingsCloudBackupProcessingScreen' import { SettingsCloudBackupProcessingScreen } from 'src/screens/SettingsCloudBackupProcessingScreen'
import { SettingsCloudBackupStatus } from 'src/screens/SettingsCloudBackupStatus' import { SettingsCloudBackupStatus } from 'src/screens/SettingsCloudBackupStatus'
import { SettingsNotificationsScreen } from 'src/screens/SettingsNotificationsScreen'
import { SettingsPrivacyScreen } from 'src/screens/SettingsPrivacyScreen' import { SettingsPrivacyScreen } from 'src/screens/SettingsPrivacyScreen'
import { SettingsScreen } from 'src/screens/SettingsScreen' import { SettingsScreen } from 'src/screens/SettingsScreen'
import { SettingsViewSeedPhraseScreen } from 'src/screens/SettingsViewSeedPhraseScreen' import { SettingsViewSeedPhraseScreen } from 'src/screens/SettingsViewSeedPhraseScreen'
...@@ -133,6 +134,7 @@ function SettingsStackGroup(): JSX.Element { ...@@ -133,6 +134,7 @@ function SettingsStackGroup(): JSX.Element {
<SettingsStack.Screen component={SettingsCloudBackupStatus} name={MobileScreens.SettingsCloudBackupStatus} /> <SettingsStack.Screen component={SettingsCloudBackupStatus} name={MobileScreens.SettingsCloudBackupStatus} />
<SettingsStack.Screen component={SettingsAppearanceScreen} name={MobileScreens.SettingsAppearance} /> <SettingsStack.Screen component={SettingsAppearanceScreen} name={MobileScreens.SettingsAppearance} />
<SettingsStack.Screen component={SettingsPrivacyScreen} name={MobileScreens.SettingsPrivacy} /> <SettingsStack.Screen component={SettingsPrivacyScreen} name={MobileScreens.SettingsPrivacy} />
<SettingsStack.Screen component={SettingsNotificationsScreen} name={MobileScreens.SettingsNotifications} />
</SettingsStack.Navigator> </SettingsStack.Navigator>
) )
} }
......
...@@ -62,6 +62,7 @@ export type SettingsStackParamList = { ...@@ -62,6 +62,7 @@ export type SettingsStackParamList = {
[MobileScreens.SettingsCloudBackupStatus]: { address: Address } [MobileScreens.SettingsCloudBackupStatus]: { address: Address }
[MobileScreens.SettingsHelpCenter]: undefined [MobileScreens.SettingsHelpCenter]: undefined
[MobileScreens.SettingsLanguage]: undefined [MobileScreens.SettingsLanguage]: undefined
[MobileScreens.SettingsNotifications]: undefined
[MobileScreens.SettingsPrivacy]: undefined [MobileScreens.SettingsPrivacy]: undefined
[MobileScreens.SettingsViewSeedPhrase]: { address: Address; walletNeedsRestore?: boolean } [MobileScreens.SettingsViewSeedPhrase]: { address: Address; walletNeedsRestore?: boolean }
[MobileScreens.SettingsWallet]: { address: Address } [MobileScreens.SettingsWallet]: { address: Address }
......
...@@ -4,7 +4,7 @@ import { Storage, persistReducer, persistStore } from 'redux-persist' ...@@ -4,7 +4,7 @@ import { Storage, persistReducer, persistStore } from 'redux-persist'
import { MOBILE_STATE_VERSION, migrations } from 'src/app/migrations' import { MOBILE_STATE_VERSION, migrations } from 'src/app/migrations'
import { MobileState, mobilePersistedStateList, mobileReducer } from 'src/app/mobileReducer' import { MobileState, mobilePersistedStateList, mobileReducer } from 'src/app/mobileReducer'
import { rootMobileSaga } from 'src/app/saga' import { rootMobileSaga } from 'src/app/saga'
import { getFiatOnRampAggregatorApi } from 'uniswap/src/features/fiatOnRamp/api' import { fiatOnRampAggregatorApi } from 'uniswap/src/features/fiatOnRamp/api'
import { isNonJestDev } from 'utilities/src/environment/constants' import { isNonJestDev } from 'utilities/src/environment/constants'
import { createDatadogReduxEnhancer } from 'utilities/src/logger/Datadog' import { createDatadogReduxEnhancer } from 'utilities/src/logger/Datadog'
import { createStore } from 'wallet/src/state' import { createStore } from 'wallet/src/state'
...@@ -51,7 +51,7 @@ if (isNonJestDev) { ...@@ -51,7 +51,7 @@ if (isNonJestDev) {
enhancers.push(reactotron.createEnhancer()) enhancers.push(reactotron.createEnhancer())
} }
const middlewares: Middleware[] = [getFiatOnRampAggregatorApi().middleware] const middlewares: Middleware[] = [fiatOnRampAggregatorApi.middleware]
export const setupStore = ( export const setupStore = (
preloadedState?: PreloadedState<MobileState>, preloadedState?: PreloadedState<MobileState>,
......
import React, { PropsWithChildren, ReactElement, memo, useCallback, useMemo, useState } from 'react' import React, { PropsWithChildren, ReactElement, memo, useCallback, useEffect, useMemo, useState } from 'react'
import { I18nManager } from 'react-native' import { I18nManager } from 'react-native'
import { SharedValue, useDerivedValue } from 'react-native-reanimated' import { SharedValue, useDerivedValue } from 'react-native-reanimated'
import { LineChart, LineChartProvider } from 'react-native-wagmi-charts' import { LineChart, LineChartProvider } from 'react-native-wagmi-charts'
...@@ -25,6 +25,7 @@ import { ElementNameType } from 'uniswap/src/features/telemetry/constants' ...@@ -25,6 +25,7 @@ import { ElementNameType } from 'uniswap/src/features/telemetry/constants'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { CurrencyId } from 'uniswap/src/types/currency' import { CurrencyId } from 'uniswap/src/types/currency'
import { isDetoxBuild } from 'utilities/src/environment/constants' import { isDetoxBuild } from 'utilities/src/environment/constants'
import { logger } from 'utilities/src/logger/logger'
import { isAndroid } from 'utilities/src/platform' import { isAndroid } from 'utilities/src/platform'
type PriceTextProps = { type PriceTextProps = {
...@@ -102,6 +103,24 @@ export const PriceExplorerInner = memo(function _PriceExplorerInner(): JSX.Eleme ...@@ -102,6 +103,24 @@ export const PriceExplorerInner = memo(function _PriceExplorerInner(): JSX.Eleme
!isScreenNavigationReady, !isScreenNavigationReady,
) )
// Log the number of points in the data
useEffect(() => {
if (data?.priceHistory) {
if (data.priceHistory.length < 10) {
logger.warn('PriceExplorer.tsx', 'PriceExplorerInner', 'Missing token details data points', {
currencyId,
duration: selectedDuration,
dataLength: data?.priceHistory?.length,
})
}
logger.info('PriceExplorer.tsx', 'PriceExplorerInner', 'Token details data length', {
currencyId,
duration: selectedDuration,
dataLength: data?.priceHistory?.length,
})
}
}, [data?.priceHistory, selectedDuration, currencyId])
const { hapticFeedback } = useHapticFeedback() const { hapticFeedback } = useHapticFeedback()
const { convertFiatAmount } = useLocalizationContext() const { convertFiatAmount } = useLocalizationContext()
......
...@@ -8,7 +8,7 @@ import { launchImageLibrary } from 'react-native-image-picker' ...@@ -8,7 +8,7 @@ import { launchImageLibrary } from 'react-native-image-picker'
import { FadeIn, FadeOut } from 'react-native-reanimated' import { FadeIn, FadeOut } from 'react-native-reanimated'
import { Defs, LinearGradient, Path, Rect, Stop, Svg } from 'react-native-svg' import { Defs, LinearGradient, Path, Rect, Stop, Svg } from 'react-native-svg'
import { Button, Flex, SpinningLoader, Text, ThemeName, TouchableArea, useSporeColors } from 'ui/src' import { DeprecatedButton, Flex, SpinningLoader, Text, ThemeName, TouchableArea, useSporeColors } from 'ui/src'
import CameraScan from 'ui/src/assets/icons/camera-scan.svg' import CameraScan from 'ui/src/assets/icons/camera-scan.svg'
import { Global, PhotoStacked } from 'ui/src/components/icons' import { Global, PhotoStacked } from 'ui/src/components/icons'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
...@@ -261,7 +261,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -261,7 +261,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
</Flex> </Flex>
{isWalletConnectModal && props.numConnections > 0 && ( {isWalletConnectModal && props.numConnections > 0 && (
<Button <DeprecatedButton
fontFamily="$body" fontFamily="$body"
icon={<Global color={colors.neutral2.val} />} icon={<Global color={colors.neutral2.val} />}
backgroundColor={colors.surface3.val} backgroundColor={colors.surface3.val}
...@@ -269,7 +269,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -269,7 +269,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
onPress={props.onPressConnections} onPress={props.onPressConnections}
> >
{t('qrScanner.button.connections', { count: props.numConnections })} {t('qrScanner.button.connections', { count: props.numConnections })}
</Button> </DeprecatedButton>
)} )}
</Flex> </Flex>
</Flex> </Flex>
......
import React, { useState } from 'react' import React, { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Button, Flex, LabeledCheckbox, SpinningLoader, Text } from 'ui/src' import { DeprecatedButton, Flex, LabeledCheckbox, SpinningLoader, Text } from 'ui/src'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
export function RemoveLastMnemonicWalletFooter({ export function RemoveLastMnemonicWalletFooter({
...@@ -31,7 +31,7 @@ export function RemoveLastMnemonicWalletFooter({ ...@@ -31,7 +31,7 @@ export function RemoveLastMnemonicWalletFooter({
/> />
</Flex> </Flex>
<Flex centered row mt="$spacing8"> <Flex centered row mt="$spacing8">
<Button <DeprecatedButton
fill fill
disabled={!checkBoxAccepted} disabled={!checkBoxAccepted}
icon={inProgress ? <SpinningLoader color="$statusCritical" /> : undefined} icon={inProgress ? <SpinningLoader color="$statusCritical" /> : undefined}
...@@ -40,7 +40,7 @@ export function RemoveLastMnemonicWalletFooter({ ...@@ -40,7 +40,7 @@ export function RemoveLastMnemonicWalletFooter({
onPress={onPress} onPress={onPress}
> >
{!inProgress ? t('account.wallet.button.remove') : undefined} {!inProgress ? t('account.wallet.button.remove') : undefined}
</Button> </DeprecatedButton>
</Flex> </Flex>
</> </>
) )
......
...@@ -11,7 +11,7 @@ import { Delay } from 'src/components/layout/Delayed' ...@@ -11,7 +11,7 @@ import { Delay } from 'src/components/layout/Delayed'
import { useBiometricAppSettings, useBiometricPrompt } from 'src/features/biometrics/hooks' import { useBiometricAppSettings, useBiometricPrompt } from 'src/features/biometrics/hooks'
import { closeModal } from 'src/features/modals/modalSlice' import { closeModal } from 'src/features/modals/modalSlice'
import { selectModalState } from 'src/features/modals/selectModalState' import { selectModalState } from 'src/features/modals/selectModalState'
import { Button, Flex, SpinningLoader, Text, ThemeKeys, useSporeColors } from 'ui/src' import { DeprecatedButton, Flex, SpinningLoader, Text, ThemeKeys, useSporeColors } from 'ui/src'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { iconSizes, opacify } from 'ui/src/theme' import { iconSizes, opacify } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -204,11 +204,11 @@ export function RemoveWalletModal(): JSX.Element | null { ...@@ -204,11 +204,11 @@ export function RemoveWalletModal(): JSX.Element | null {
{inProgress ? ( {inProgress ? (
<AnimatedFlex style={animatedCancelButtonSpanStyles} /> <AnimatedFlex style={animatedCancelButtonSpanStyles} />
) : ( ) : (
<Button fill disabled={inProgress} theme="outline" onPress={onClose}> <DeprecatedButton fill disabled={inProgress} theme="outline" onPress={onClose}>
{t('common.button.cancel')} {t('common.button.cancel')}
</Button> </DeprecatedButton>
)} )}
<Button <DeprecatedButton
fill fill
icon={inProgress ? <SpinningLoader color={`$${labelColor}`} /> : undefined} icon={inProgress ? <SpinningLoader color={`$${labelColor}`} /> : undefined}
testID={isRemovingRecoveryPhrase ? ElementName.Continue : ElementName.Remove} testID={isRemovingRecoveryPhrase ? ElementName.Continue : ElementName.Remove}
...@@ -217,7 +217,7 @@ export function RemoveWalletModal(): JSX.Element | null { ...@@ -217,7 +217,7 @@ export function RemoveWalletModal(): JSX.Element | null {
onPress={onPress} onPress={onPress}
> >
{inProgress ? undefined : actionButtonLabel} {inProgress ? undefined : actionButtonLabel}
</Button> </DeprecatedButton>
</Flex> </Flex>
)} )}
</Flex> </Flex>
......
...@@ -6,7 +6,7 @@ import { useDispatch } from 'react-redux' ...@@ -6,7 +6,7 @@ import { useDispatch } from 'react-redux'
import { DappHeaderIcon } from 'src/components/Requests/DappHeaderIcon' import { DappHeaderIcon } from 'src/components/Requests/DappHeaderIcon'
import { wcWeb3Wallet } from 'src/features/walletConnect/saga' import { wcWeb3Wallet } from 'src/features/walletConnect/saga'
import { WalletConnectSession, removeSession } from 'src/features/walletConnect/walletConnectSlice' import { WalletConnectSession, removeSession } from 'src/features/walletConnect/walletConnectSlice'
import { Button, Flex, Text } from 'ui/src' import { DeprecatedButton, Flex, Text } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -94,12 +94,12 @@ export function DappConnectedNetworkModal({ session, onClose }: DappConnectedNet ...@@ -94,12 +94,12 @@ export function DappConnectedNetworkModal({ session, onClose }: DappConnectedNet
</Flex> </Flex>
</Flex> </Flex>
<Flex centered row gap="$spacing16"> <Flex centered row gap="$spacing16">
<Button fill theme="secondary" onPress={onClose}> <DeprecatedButton fill theme="secondary" onPress={onClose}>
{t('common.button.close')} {t('common.button.close')}
</Button> </DeprecatedButton>
<Button fill theme="detrimental" onPress={onDisconnect}> <DeprecatedButton fill theme="detrimental" onPress={onDisconnect}>
{t('common.button.disconnect')} {t('common.button.disconnect')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</Modal> </Modal>
......
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
} from 'react-native' } from 'react-native'
import { AnimatedStyle, useDerivedValue } from 'react-native-reanimated' import { AnimatedStyle, useDerivedValue } from 'react-native-reanimated'
import { ScrollDownOverlay } from 'src/components/Requests/ModalWithOverlay/ScrollDownOverlay' import { ScrollDownOverlay } from 'src/components/Requests/ModalWithOverlay/ScrollDownOverlay'
import { Button, Flex } from 'ui/src' import { DeprecatedButton, Flex } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalProps } from 'uniswap/src/components/modals/ModalProps' import { ModalProps } from 'uniswap/src/components/modals/ModalProps'
...@@ -184,12 +184,18 @@ function ModalFooter({ ...@@ -184,12 +184,18 @@ function ModalFooter({
pt="$spacing12" pt="$spacing12"
px="$spacing24" px="$spacing24"
> >
<Button fill size="medium" testID={TestID.Cancel} theme="tertiary" onPress={onReject}> <DeprecatedButton fill size="medium" testID={TestID.Cancel} theme="tertiary" onPress={onReject}>
{t('common.button.cancel')} {t('common.button.cancel')}
</Button> </DeprecatedButton>
<Button fill disabled={!confirmationEnabled} size="medium" testID={TestID.Confirm} onPress={onConfirm}> <DeprecatedButton
fill
disabled={!confirmationEnabled}
size="medium"
testID={TestID.Confirm}
onPress={onConfirm}
>
{confirmationButtonText ?? t('common.button.accept')} {confirmationButtonText ?? t('common.button.accept')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</BottomSheetFooter> </BottomSheetFooter>
) )
......
...@@ -130,7 +130,7 @@ function RequestModal({ currRequest }: RequestModalProps): JSX.Element { ...@@ -130,7 +130,7 @@ function RequestModal({ currRequest }: RequestModalProps): JSX.Element {
caption={t('walletConnect.request.warning.message')} caption={t('walletConnect.request.warning.message')}
rejectText={t('common.button.dismiss')} rejectText={t('common.button.dismiss')}
icon={ icon={
<EyeIcon color={colors.neutral2.get()} height={iconSizes.icon24} strokeWidth={1.5} width={iconSizes.icon24} /> <EyeIcon color={colors.neutral1.get()} height={iconSizes.icon24} strokeWidth={1.5} width={iconSizes.icon24} />
} }
isOpen={!isRequestFromSignerAccount} isOpen={!isRequestFromSignerAccount}
modalName={ModalName.WCViewOnlyWarning} modalName={ModalName.WCViewOnlyWarning}
......
...@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' ...@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { closeAllModals } from 'src/features/modals/modalSlice' import { closeAllModals } from 'src/features/modals/modalSlice'
import { Button, Flex, Text, useSporeColors } from 'ui/src' import { DeprecatedButton, Flex, Text, useSporeColors } from 'ui/src'
import { WalletFilled } from 'ui/src/components/icons' import { WalletFilled } from 'ui/src/components/icons'
import { iconSizes, opacify } from 'ui/src/theme' import { iconSizes, opacify } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -48,9 +48,9 @@ export function RestoreWalletModal(): JSX.Element | null { ...@@ -48,9 +48,9 @@ export function RestoreWalletModal(): JSX.Element | null {
{t('account.wallet.restore.description')} {t('account.wallet.restore.description')}
</Text> </Text>
<Flex centered row gap="$spacing12" pt="$spacing12"> <Flex centered row gap="$spacing12" pt="$spacing12">
<Button fill testID={TestID.RestoreWallet} theme="primary" onPress={onRestore}> <DeprecatedButton fill testID={TestID.RestoreWallet} theme="primary" onPress={onRestore}>
{t('common.button.restore')} {t('common.button.restore')}
</Button> </DeprecatedButton>
</Flex> </Flex>
</Flex> </Flex>
</Modal> </Modal>
......
import { useMemo, useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSettingsStackNavigation } from 'src/app/navigation/types' import { useSettingsStackNavigation } from 'src/app/navigation/types'
import { Button, Flex, Text, TouchableArea } from 'ui/src' import { DeprecatedButton, Flex, Text, TouchableArea } from 'ui/src'
import { RotatableChevron } from 'ui/src/components/icons' import { RotatableChevron } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { AccountType } from 'uniswap/src/features/accounts/types' import { AccountType } from 'uniswap/src/features/accounts/types'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { SignerMnemonicAccount } from 'wallet/src/features/wallet/accounts/types' import { useAccountsList } from 'wallet/src/features/wallet/hooks'
import { useAccounts } from 'wallet/src/features/wallet/hooks'
const DEFAULT_ACCOUNTS_TO_DISPLAY = 6 const DEFAULT_ACCOUNTS_TO_DISPLAY = 6
export function WalletSettings(): JSX.Element { export function WalletSettings(): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const navigation = useSettingsStackNavigation() const navigation = useSettingsStackNavigation()
const addressToAccount = useAccounts() const allAccounts = useAccountsList()
const [showAll, setShowAll] = useState(false) const [showAll, setShowAll] = useState(false)
const allAccounts = useMemo(() => {
const accounts = Object.values(addressToAccount)
const _mnemonicWallets = accounts
.filter((a): a is SignerMnemonicAccount => a.type === AccountType.SignerMnemonic)
.sort((a, b) => {
return a.derivationIndex - b.derivationIndex
})
const _viewOnlyWallets = accounts
.filter((a) => a.type === AccountType.Readonly)
.sort((a, b) => {
return a.timeImportedMs - b.timeImportedMs
})
return [..._mnemonicWallets, ..._viewOnlyWallets]
}, [addressToAccount])
const toggleViewAll = (): void => { const toggleViewAll = (): void => {
setShowAll(!showAll) setShowAll(!showAll)
} }
...@@ -74,11 +58,11 @@ export function WalletSettings(): JSX.Element { ...@@ -74,11 +58,11 @@ export function WalletSettings(): JSX.Element {
) )
})} })}
{allAccounts.length > DEFAULT_ACCOUNTS_TO_DISPLAY && ( {allAccounts.length > DEFAULT_ACCOUNTS_TO_DISPLAY && (
<Button theme="tertiary" onPress={toggleViewAll}> <DeprecatedButton theme="tertiary" onPress={toggleViewAll}>
<Text color="$neutral1" variant="buttonLabel2"> <Text color="$neutral1" variant="buttonLabel2">
{showAll ? t('settings.section.wallet.button.viewLess') : t('settings.section.wallet.button.viewAll')} {showAll ? t('settings.section.wallet.button.viewLess') : t('settings.section.wallet.button.viewAll')}
</Text> </Text>
</Button> </DeprecatedButton>
)} )}
</Flex> </Flex>
) )
......
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useTokenDetailsContext } from 'src/components/TokenDetails/TokenDetailsContext' import { useTokenDetailsContext } from 'src/components/TokenDetails/TokenDetailsContext'
import { Button, Flex, useSporeColors } from 'ui/src' import { DeprecatedButton, Flex, GeneratedIcon, useSporeColors } from 'ui/src'
import { SwapCoin } from 'ui/src/components/icons'
import { opacify, validColor } from 'ui/src/theme' import { opacify, validColor } from 'ui/src/theme'
import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useTokenBasicProjectPartsFragment } from 'uniswap/src/data/graphql/uniswap-data-api/fragments' import { useTokenBasicProjectPartsFragment } from 'uniswap/src/data/graphql/uniswap-data-api/fragments'
import { TokenList } from 'uniswap/src/features/dataApi/types' import { TokenList } from 'uniswap/src/features/dataApi/types'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName, ElementNameType, SectionName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ElementNameType, SectionName } from 'uniswap/src/features/telemetry/constants'
import { TestID, TestIDType } from 'uniswap/src/test/fixtures/testIDs' import { TestID, TestIDType } from 'uniswap/src/test/fixtures/testIDs'
...@@ -19,6 +22,7 @@ function CTAButton({ ...@@ -19,6 +22,7 @@ function CTAButton({
testID, testID,
tokenColor, tokenColor,
disabled, disabled,
icon,
}: { }: {
title: string title: string
element: ElementNameType element: ElementNameType
...@@ -27,12 +31,14 @@ function CTAButton({ ...@@ -27,12 +31,14 @@ function CTAButton({
testID?: TestIDType testID?: TestIDType
tokenColor?: Maybe<string> tokenColor?: Maybe<string>
disabled?: boolean disabled?: boolean
icon?: GeneratedIcon
}): JSX.Element { }): JSX.Element {
const colors = useSporeColors() const colors = useSporeColors()
return ( return (
<Trace logPress element={element} section={SectionName.TokenDetails}> <Trace logPress element={element} section={SectionName.TokenDetails}>
<Button <DeprecatedButton
fill fill
icon={icon}
opacity={disabled ? 0.5 : 1} opacity={disabled ? 0.5 : 1}
color={tokenColor ? getContrastPassingTextColor(tokenColor) : '$white'} color={tokenColor ? getContrastPassingTextColor(tokenColor) : '$white'}
pressStyle={{ backgroundColor: validColor(opacify(60, tokenColor ?? colors.accent1.val)) }} pressStyle={{ backgroundColor: validColor(opacify(60, tokenColor ?? colors.accent1.val)) }}
...@@ -42,7 +48,7 @@ function CTAButton({ ...@@ -42,7 +48,7 @@ function CTAButton({
onPress={disabled ? onPressDisabled : onPress} onPress={disabled ? onPressDisabled : onPress}
> >
{title} {title}
</Button> </DeprecatedButton>
</Trace> </Trace>
) )
} }
...@@ -50,15 +56,18 @@ function CTAButton({ ...@@ -50,15 +56,18 @@ function CTAButton({
export function TokenDetailsActionButtons({ export function TokenDetailsActionButtons({
onPressBuy, onPressBuy,
onPressSell, onPressSell,
onPressSwap,
onPressDisabled, onPressDisabled,
userHasBalance, userHasBalance,
}: { }: {
onPressBuy: () => void onPressBuy: () => void
onPressSell: () => void onPressSell: () => void
onPressSwap: () => void
onPressDisabled?: () => void onPressDisabled?: () => void
userHasBalance: boolean userHasBalance: boolean
}): JSX.Element { }): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const isOffRampEnabled = useFeatureFlag(FeatureFlags.FiatOffRamp)
const { currencyId, currencyInfo, isChainEnabled, tokenColor } = useTokenDetailsContext() const { currencyId, currencyInfo, isChainEnabled, tokenColor } = useTokenDetailsContext()
...@@ -80,6 +89,19 @@ export function TokenDetailsActionButtons({ ...@@ -80,6 +89,19 @@ export function TokenDetailsActionButtons({
pt="$spacing12" pt="$spacing12"
px="$spacing16" px="$spacing16"
> >
{isOffRampEnabled ? (
<CTAButton
disabled={disabled}
element={ElementName.Swap}
testID={TestID.TokenDetailsSwapButton}
title={t('common.button.swap')}
tokenColor={tokenColor}
icon={SwapCoin}
onPress={onPressSwap}
onPressDisabled={onPressDisabled}
/>
) : (
<>
<CTAButton <CTAButton
disabled={disabled} disabled={disabled}
element={ElementName.Buy} element={ElementName.Buy}
...@@ -100,6 +122,8 @@ export function TokenDetailsActionButtons({ ...@@ -100,6 +122,8 @@ export function TokenDetailsActionButtons({
onPressDisabled={onPressDisabled} onPressDisabled={onPressDisabled}
/> />
)} )}
</>
)}
</Flex> </Flex>
) )
} }
...@@ -7,7 +7,7 @@ import { navigate } from 'src/app/navigation/rootNavigation' ...@@ -7,7 +7,7 @@ import { navigate } from 'src/app/navigation/rootNavigation'
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice' import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, Text, TouchableArea } from 'ui/src' import { Flex, Text, TouchableArea } from 'ui/src'
import { CopyAlt, ScanHome, Settings } from 'ui/src/components/icons' import { CopyAlt, ScanHome, SettingsHome } from 'ui/src/components/icons'
import { AccountType } from 'uniswap/src/features/accounts/types' import { AccountType } from 'uniswap/src/features/accounts/types'
import { useAvatar } from 'uniswap/src/features/address/avatar' import { useAvatar } from 'uniswap/src/features/address/avatar'
import { pushNotification } from 'uniswap/src/features/notifications/slice' import { pushNotification } from 'uniswap/src/features/notifications/slice'
...@@ -66,7 +66,7 @@ const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }): ...@@ -66,7 +66,7 @@ const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }):
return ( return (
<GestureDetector gesture={tap}> <GestureDetector gesture={tap}>
<Animated.View style={animatedStyle}> <Animated.View style={animatedStyle}>
<Settings color="$neutral2" size="$icon.24" /> <SettingsHome color="$neutral2" size="$icon.28" />
</Animated.View> </Animated.View>
</GestureDetector> </GestureDetector>
) )
...@@ -118,7 +118,7 @@ export function AccountHeader(): JSX.Element { ...@@ -118,7 +118,7 @@ export function AccountHeader(): JSX.Element {
} }
} }
const onPressScan = useCallback(async () => { const onPressScan = useCallback(async () => {
// in case we received a pending session from a previous scan after closing modal // in case we received a pending session from a previous scan after closing modal.
dispatch(removePendingSession()) dispatch(removePendingSession())
dispatch(openModal({ name: ModalName.WalletConnectScan, initialState: ScannerModalState.ScanQr })) dispatch(openModal({ name: ModalName.WalletConnectScan, initialState: ScannerModalState.ScanQr }))
}, [dispatch]) }, [dispatch])
...@@ -166,12 +166,7 @@ export function AccountHeader(): JSX.Element { ...@@ -166,12 +166,7 @@ export function AccountHeader(): JSX.Element {
</TouchableArea> </TouchableArea>
</Flex> </Flex>
) : ( ) : (
<TouchableArea <TouchableArea hitSlop={20} testID={TestID.AccountHeaderCopyAddress} onPress={onPressCopyAddress}>
alignSelf="center"
hitSlop={20}
testID={TestID.AccountHeaderCopyAddress}
onPress={onPressCopyAddress}
>
<Flex centered row shrink gap="$spacing4"> <Flex centered row shrink gap="$spacing4">
<Text adjustsFontSizeToFit color="$neutral1" numberOfLines={1} variant="subheading2"> <Text adjustsFontSizeToFit color="$neutral1" numberOfLines={1} variant="subheading2">
{sanitizeAddressText(shortenAddress(activeAddress))} {sanitizeAddressText(shortenAddress(activeAddress))}
...@@ -181,9 +176,9 @@ export function AccountHeader(): JSX.Element { ...@@ -181,9 +176,9 @@ export function AccountHeader(): JSX.Element {
</TouchableArea> </TouchableArea>
)} )}
</Flex> </Flex>
<Flex row alignItems="flex-start" gap="$spacing16" pt="$spacing4"> <Flex row alignItems="flex-start" gap="$spacing16">
<TouchableArea scaleTo={SCAN_ICON_ACTIVE_SCALE} activeOpacity={1} onPress={onPressScan}> <TouchableArea scaleTo={SCAN_ICON_ACTIVE_SCALE} activeOpacity={1} onPress={onPressScan}>
<ScanHome color="$neutral2" size="$icon.24" /> <ScanHome color="$neutral2" size="$icon.28" />
</TouchableArea> </TouchableArea>
<RotatingSettingsIcon onPressSettings={onPressSettings} /> <RotatingSettingsIcon onPressSettings={onPressSettings} />
</Flex> </Flex>
......
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Button, useSporeColors } from 'ui/src' import { DeprecatedButton, useSporeColors } from 'ui/src'
import CheckCircle from 'ui/src/assets/icons/check-circle.svg' import CheckCircle from 'ui/src/assets/icons/check-circle.svg'
import CopySheets from 'ui/src/assets/icons/copy-sheets.svg' import CopySheets from 'ui/src/assets/icons/copy-sheets.svg'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
...@@ -39,8 +39,8 @@ export function CopyTextButton({ copyText }: Props): JSX.Element { ...@@ -39,8 +39,8 @@ export function CopyTextButton({ copyText }: Props): JSX.Element {
useTimeout(resetIsCopied, RESET_COPY_STATE_DELAY) useTimeout(resetIsCopied, RESET_COPY_STATE_DELAY)
return ( return (
<Button icon={isCopied ? copiedIcon : copyIcon} theme="tertiary" onPress={onPress}> <DeprecatedButton icon={isCopied ? copiedIcon : copyIcon} theme="tertiary" onPress={onPress}>
{isCopied ? t('common.button.copied') : t('common.button.copy')} {isCopied ? t('common.button.copied') : t('common.button.copy')}
</Button> </DeprecatedButton>
) )
} }
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
ethToken, ethToken,
tokenMarket, tokenMarket,
tokenProject, tokenProject,
tokenProjectMarket,
} from 'uniswap/src/test/fixtures' } from 'uniswap/src/test/fixtures'
import { queryResolvers } from 'uniswap/src/test/utils' import { queryResolvers } from 'uniswap/src/test/utils'
import { getSymbolDisplayText } from 'uniswap/src/utils/currency' import { getSymbolDisplayText } from 'uniswap/src/utils/currency'
...@@ -31,7 +32,16 @@ jest.mock('@react-navigation/native', () => { ...@@ -31,7 +32,16 @@ jest.mock('@react-navigation/native', () => {
const mockStore = configureMockStore() const mockStore = configureMockStore()
const favoriteToken = ethToken({ const favoriteToken = ethToken({
project: tokenProject(), project: {
...tokenProject(),
markets: [
{
...tokenProjectMarket(),
price: amount({ value: 76543.21 }),
pricePercentChange24h: amount({ value: 6.54 }),
},
],
},
market: tokenMarket({ market: tokenMarket({
price: amount({ value: 12345.67 }), price: amount({ value: 12345.67 }),
pricePercentChange: amount({ value: 4.56 }), pricePercentChange: amount({ value: 4.56 }),
...@@ -79,8 +89,8 @@ describe('FavoriteTokenCard', () => { ...@@ -79,8 +89,8 @@ describe('FavoriteTokenCard', () => {
describe('when token data is available', () => { describe('when token data is available', () => {
const cases = [ const cases = [
{ test: 'symbol', value: getSymbolDisplayText(favoriteToken.symbol)! }, { test: 'symbol', value: getSymbolDisplayText(favoriteToken.symbol)! },
{ test: 'price', value: '$12,345.67' }, { test: 'price', value: '$76,543.21' },
{ test: 'relative price change', value: '4.56%' }, { test: 'relative price change', value: '6.54%' },
] ]
it.each(cases)('renders correct $test', async ({ value }) => { it.each(cases)('renders correct $test', async ({ value }) => {
...@@ -91,6 +101,22 @@ describe('FavoriteTokenCard', () => { ...@@ -91,6 +101,22 @@ describe('FavoriteTokenCard', () => {
}) })
}) })
it('falls back to token price if token project price is not available', async () => {
const { resolvers: modifiedResolvers } = queryResolvers({
token: () => ({
...favoriteToken,
project: { ...favoriteToken.project, markets: [] },
}),
})
const { queryByText } = render(<FavoriteTokenCard {...defaultProps} />, { resolvers: modifiedResolvers })
await waitFor(() => {
expect(queryByText('$12,345.67')).toBeTruthy()
expect(queryByText('4.56%')).toBeTruthy()
})
})
it('navigates to the token details screen when pressed', async () => { it('navigates to the token details screen when pressed', async () => {
const { findByTestId } = render(<FavoriteTokenCard {...defaultProps} />, { resolvers }) const { findByTestId } = render(<FavoriteTokenCard {...defaultProps} />, { resolvers })
......
...@@ -14,7 +14,10 @@ import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' ...@@ -14,7 +14,10 @@ import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { borderRadii, imageSizes, opacify } from 'ui/src/theme' import { borderRadii, imageSizes, opacify } from 'ui/src/theme'
import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo'
import { PollingInterval } from 'uniswap/src/constants/misc' import { PollingInterval } from 'uniswap/src/constants/misc'
import { useFavoriteTokenCardQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import {
FavoriteTokenCardQuery,
useFavoriteTokenCardQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { fromGraphQLChain } from 'uniswap/src/features/chains/utils'
import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils' import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils'
...@@ -55,7 +58,7 @@ function FavoriteTokenCard({ ...@@ -55,7 +58,7 @@ function FavoriteTokenCard({
const { data, networkStatus, startPolling, stopPolling } = useFavoriteTokenCardQuery({ const { data, networkStatus, startPolling, stopPolling } = useFavoriteTokenCardQuery({
variables: currencyIdToContractInput(currencyId), variables: currencyIdToContractInput(currencyId),
// Rely on cache for fast favoriting UX, and poll for updates. // Rely on cache for fast favoriting UX, and poll for updates.
fetchPolicy: 'cache-first', fetchPolicy: 'cache-and-network',
returnPartialData: true, returnPartialData: true,
}) })
...@@ -66,8 +69,10 @@ function FavoriteTokenCard({ ...@@ -66,8 +69,10 @@ function FavoriteTokenCard({
// Mirror behavior in top tokens list, use first chain the token is on for the symbol // Mirror behavior in top tokens list, use first chain the token is on for the symbol
const chainId = fromGraphQLChain(token?.chain) ?? defaultChainId const chainId = fromGraphQLChain(token?.chain) ?? defaultChainId
const price = convertFiatAmountFormatted(token?.market?.price?.value, NumberType.FiatTokenPrice) // Coingecko price is more accurate but lacks long tail tokens
const pricePercentChange = token?.market?.pricePercentChange?.value // Uniswap price comes from Uniswap pools, which may be updated less frequently
const { price, pricePercentChange } = getCoingeckoPrice(token) ?? getUniswapPrice(token)
const priceFormatted = convertFiatAmountFormatted(price, NumberType.FiatTokenPrice)
const onRemove = useCallback(() => { const onRemove = useCallback(() => {
if (currencyId) { if (currencyId) {
...@@ -139,7 +144,7 @@ function FavoriteTokenCard({ ...@@ -139,7 +144,7 @@ function FavoriteTokenCard({
</Flex> </Flex>
<Flex gap="$spacing2"> <Flex gap="$spacing2">
<Text adjustsFontSizeToFit numberOfLines={1} variant="heading3"> <Text adjustsFontSizeToFit numberOfLines={1} variant="heading3">
{price} {priceFormatted}
</Text> </Text>
<RelativeChange <RelativeChange
arrowSize="$icon.16" arrowSize="$icon.16"
...@@ -155,4 +160,29 @@ function FavoriteTokenCard({ ...@@ -155,4 +160,29 @@ function FavoriteTokenCard({
) )
} }
function getCoingeckoPrice(token?: FavoriteTokenCardQuery['token']): {
price: number | undefined
pricePercentChange: number | undefined
} | null {
const market = token?.project?.markets?.[0]
if (!market?.price?.value || !market?.pricePercentChange24h?.value) {
return null
}
return {
price: market.price.value,
pricePercentChange: market.pricePercentChange24h.value,
}
}
function getUniswapPrice(token?: FavoriteTokenCardQuery['token']): {
price: number | undefined
pricePercentChange: number | undefined
} {
return {
price: token?.market?.price?.value,
pricePercentChange: token?.market?.pricePercentChange?.value,
}
}
export default memo(FavoriteTokenCard) export default memo(FavoriteTokenCard)
...@@ -85,10 +85,10 @@ export function SearchEmptySection({ selectedChain }: { selectedChain: UniverseC ...@@ -85,10 +85,10 @@ export function SearchEmptySection({ selectedChain }: { selectedChain: UniverseC
</Flex> </Flex>
</AnimatedFlex> </AnimatedFlex>
<WarningModal <WarningModal
backgroundIconColor={colors.surface2.get()} backgroundIconColor={colors.surface3.get()}
caption={t('explore.search.section.popularTokenInfo')} caption={t('explore.search.section.popularTokenInfo')}
rejectText={t('common.button.close')} rejectText={t('common.button.close')}
icon={<Star color="$neutral2" size="$icon.24" />} icon={<Star color="$neutral1" size="$icon.24" />}
isOpen={showPopularInfo} isOpen={showPopularInfo}
modalName={ModalName.NetworkFeeInfo} modalName={ModalName.NetworkFeeInfo}
severity={WarningSeverity.None} severity={WarningSeverity.None}
......
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { ListRenderItemInfo } from 'react-native' import { ListRenderItemInfo } from 'react-native'
import { FlatList } from 'react-native-gesture-handler' import { FlatList } from 'react-native-gesture-handler'
import { SEARCH_ITEM_PX, SEARCH_ITEM_PY } from 'src/components/explore/search/constants'
import { SearchNFTCollectionItem } from 'src/components/explore/search/items/SearchNFTCollectionItem' import { SearchNFTCollectionItem } from 'src/components/explore/search/items/SearchNFTCollectionItem'
import { getSearchResultId, gqlNFTToNFTCollectionSearchResult } from 'src/components/explore/search/utils' import { getSearchResultId, gqlNFTToNFTCollectionSearchResult } from 'src/components/explore/search/utils'
import { Flex, Loader } from 'ui/src' import { Flex, Loader } from 'ui/src'
...@@ -26,7 +27,7 @@ export function SearchPopularNFTCollections(): JSX.Element { ...@@ -26,7 +27,7 @@ export function SearchPopularNFTCollections(): JSX.Element {
if (loading) { if (loading) {
return ( return (
<Flex px="$spacing24" py="$spacing8"> <Flex px={SEARCH_ITEM_PX} py={SEARCH_ITEM_PY}>
<Loader.Token repeat={2} /> <Loader.Token repeat={2} />
</Flex> </Flex>
) )
......
...@@ -14,6 +14,11 @@ query SearchPopularTokens { ...@@ -14,6 +14,11 @@ query SearchPopularTokens {
protectionInfo { protectionInfo {
result result
attackTypes attackTypes
blockaidFees {
buy
sell
transfer
}
} }
} }
# `topTokens` returns WETH rather than ETH # `topTokens` returns WETH rather than ETH
...@@ -33,6 +38,11 @@ query SearchPopularTokens { ...@@ -33,6 +38,11 @@ query SearchPopularTokens {
protectionInfo { protectionInfo {
result result
attackTypes attackTypes
blockaidFees {
buy
sell
transfer
}
} }
} }
} }
...@@ -2,6 +2,7 @@ import { TokenRankingsStat } from '@uniswap/client-explore/dist/uniswap/explore/ ...@@ -2,6 +2,7 @@ import { TokenRankingsStat } from '@uniswap/client-explore/dist/uniswap/explore/
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { ListRenderItemInfo } from 'react-native' import { ListRenderItemInfo } from 'react-native'
import { FlatList } from 'react-native-gesture-handler' import { FlatList } from 'react-native-gesture-handler'
import { SEARCH_ITEM_PX, SEARCH_ITEM_PY } from 'src/components/explore/search/constants'
import { SearchTokenItem } from 'src/components/explore/search/items/SearchTokenItem' import { SearchTokenItem } from 'src/components/explore/search/items/SearchTokenItem'
import { getSearchResultId } from 'src/components/explore/search/utils' import { getSearchResultId } from 'src/components/explore/search/utils'
import { Flex, Loader } from 'ui/src' import { Flex, Loader } from 'ui/src'
...@@ -60,7 +61,7 @@ export function SearchPopularTokens({ selectedChain }: { selectedChain: Universe ...@@ -60,7 +61,7 @@ export function SearchPopularTokens({ selectedChain }: { selectedChain: Universe
if (isLoading) { if (isLoading) {
return ( return (
<Flex px="$spacing24" py="$spacing8"> <Flex px={SEARCH_ITEM_PX} py={SEARCH_ITEM_PY}>
<Loader.Token repeat={2} /> <Loader.Token repeat={2} />
</Flex> </Flex>
) )
......
import React from 'react' import React from 'react'
import { NFTHeaderItem, TokenHeaderItem, WalletHeaderItem } from 'src/components/explore/search/constants' import {
NFTHeaderItem,
SEARCH_ITEM_PX,
TokenHeaderItem,
WalletHeaderItem,
} from 'src/components/explore/search/constants'
import { SectionHeaderText } from 'src/components/explore/search/SearchSectionHeader' import { SectionHeaderText } from 'src/components/explore/search/SearchSectionHeader'
import { SearchHeader } from 'src/components/explore/search/types' import { SearchHeader } from 'src/components/explore/search/types'
import { Flex, Loader } from 'ui/src' import { Flex, Loader } from 'ui/src'
...@@ -9,7 +14,7 @@ function SectionLoader({ searchHeader, repeat = 1 }: { searchHeader: SearchHeade ...@@ -9,7 +14,7 @@ function SectionLoader({ searchHeader, repeat = 1 }: { searchHeader: SearchHeade
return ( return (
<Flex gap="$spacing12"> <Flex gap="$spacing12">
<SectionHeaderText icon={searchHeader.icon} title={searchHeader.title} /> <SectionHeaderText icon={searchHeader.icon} title={searchHeader.title} />
<Flex mx="$spacing24"> <Flex px={SEARCH_ITEM_PX}>
<Loader.SearchResult repeat={repeat} /> <Loader.SearchResult repeat={repeat} />
</Flex> </Flex>
</Flex> </Flex>
......
import { SearchHeader, SearchHeaderKey } from 'src/components/explore/search/types' import { SearchHeader, SearchHeaderKey } from 'src/components/explore/search/types'
import { Coin, Gallery, Person } from 'ui/src/components/icons' import { Coin, Gallery, Person } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { getChainInfo } from 'uniswap/src/features/chains/chainInfo' import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import i18n from 'uniswap/src/i18n' import i18n from 'uniswap/src/i18n'
export const SEARCH_RESULT_HEADER_KEY: SearchHeaderKey = 'header' export const SEARCH_RESULT_HEADER_KEY: SearchHeaderKey = 'header'
export const SEARCH_ITEM_PX = '$spacing20'
export const SEARCH_ITEM_PY = '$spacing8'
export const SEARCH_ITEM_ICON_SIZE = iconSizes.icon36
const ICON_SIZE = '$icon.24' const ICON_SIZE = '$icon.24'
const ICON_COLOR = '$neutral2' const ICON_COLOR = '$neutral2'
......
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SEARCH_ITEM_ICON_SIZE, SEARCH_ITEM_PX, SEARCH_ITEM_PY } from 'src/components/explore/search/constants'
import { SearchWalletItemBase } from 'src/components/explore/search/items/SearchWalletItemBase' import { SearchWalletItemBase } from 'src/components/explore/search/items/SearchWalletItemBase'
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { imageSizes } from 'ui/src/theme'
import { useENSAvatar, useENSName } from 'uniswap/src/features/ens/api' import { useENSAvatar, useENSName } from 'uniswap/src/features/ens/api'
import { getCompletedENSName } from 'uniswap/src/features/ens/useENS' import { getCompletedENSName } from 'uniswap/src/features/ens/useENS'
import { SearchContext } from 'uniswap/src/features/search/SearchContext' import { SearchContext } from 'uniswap/src/features/search/SearchContext'
...@@ -48,8 +48,8 @@ export function SearchENSAddressItem({ searchResult, searchContext }: SearchENSA ...@@ -48,8 +48,8 @@ export function SearchENSAddressItem({ searchResult, searchContext }: SearchENSA
return ( return (
<SearchWalletItemBase searchContext={searchContext} searchResult={searchResult}> <SearchWalletItemBase searchContext={searchContext} searchResult={searchResult}>
<Flex row alignItems="center" gap="$spacing12" px="$spacing24" py="$spacing12"> <Flex row alignItems="center" gap="$spacing12" px={SEARCH_ITEM_PX} py={SEARCH_ITEM_PY}>
<AccountIcon address={address} avatarUri={avatar} size={imageSizes.image40} /> <AccountIcon address={address} avatarUri={avatar} size={SEARCH_ITEM_ICON_SIZE} />
<Flex shrink> <Flex shrink>
<Text ellipsizeMode="tail" numberOfLines={1} testID={`address-display/name/${ensName}`} variant="body1"> <Text ellipsizeMode="tail" numberOfLines={1} testID={`address-display/name/${ensName}`} variant="body1">
{completedENSName || formattedAddress} {completedENSName || formattedAddress}
......
import { default as React } from 'react' import { default as React } from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { SEARCH_ITEM_ICON_SIZE, SEARCH_ITEM_PX, SEARCH_ITEM_PY } from 'src/components/explore/search/constants'
import { getBlockExplorerIcon } from 'src/components/icons/BlockExplorerIcon' import { getBlockExplorerIcon } from 'src/components/icons/BlockExplorerIcon'
import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { Arrow } from 'ui/src/components/arrow/Arrow' import { Arrow } from 'ui/src/components/arrow/Arrow'
...@@ -36,9 +37,16 @@ export function SearchEtherscanItem({ etherscanResult }: SearchEtherscanItemProp ...@@ -36,9 +37,16 @@ export function SearchEtherscanItem({ etherscanResult }: SearchEtherscanItemProp
return ( return (
<TouchableArea testID={TestID.SearchEtherscanItem} onPress={onPressViewEtherscan}> <TouchableArea testID={TestID.SearchEtherscanItem} onPress={onPressViewEtherscan}>
<Flex row alignItems="center" gap="$spacing12" justifyContent="space-between" px="$spacing24" py="$spacing12"> <Flex
row
alignItems="center"
gap="$spacing12"
justifyContent="space-between"
px={SEARCH_ITEM_PX}
py={SEARCH_ITEM_PY}
>
<Flex centered row gap="$spacing12"> <Flex centered row gap="$spacing12">
<EtherscanIcon size="$icon.40" /> <EtherscanIcon size={SEARCH_ITEM_ICON_SIZE} />
<Text variant="body1">{shortenAddress(address)}</Text> <Text variant="body1">{shortenAddress(address)}</Text>
</Flex> </Flex>
<Arrow color={colors.neutral2.val} direction="ne" size={iconSizes.icon24} /> <Arrow color={colors.neutral2.val} direction="ne" size={iconSizes.icon24} />
......
import { default as React } from 'react' import { default as React } from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { useAppStackNavigation } from 'src/app/navigation/types' import { useAppStackNavigation } from 'src/app/navigation/types'
import { SEARCH_ITEM_ICON_SIZE, SEARCH_ITEM_PX, SEARCH_ITEM_PY } from 'src/components/explore/search/constants'
import { Flex, Text, TouchableArea } from 'ui/src' import { Flex, Text, TouchableArea } from 'ui/src'
import { Verified } from 'ui/src/components/icons' import { Verified } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { SearchContext } from 'uniswap/src/features/search/SearchContext' import { SearchContext } from 'uniswap/src/features/search/SearchContext'
import { NFTCollectionSearchResult, SearchResultType } from 'uniswap/src/features/search/SearchResult' import { NFTCollectionSearchResult, SearchResultType } from 'uniswap/src/features/search/SearchResult'
import { addToSearchHistory } from 'uniswap/src/features/search/searchHistorySlice' import { addToSearchHistory } from 'uniswap/src/features/search/searchHistorySlice'
...@@ -57,14 +57,14 @@ export function SearchNFTCollectionItem({ collection, searchContext }: NFTCollec ...@@ -57,14 +57,14 @@ export function SearchNFTCollectionItem({ collection, searchContext }: NFTCollec
return ( return (
<TouchableArea testID={TestID.SearchNFTCollectionItem} onPress={onPress}> <TouchableArea testID={TestID.SearchNFTCollectionItem} onPress={onPress}>
<Flex row alignItems="center" gap="$spacing8" justifyContent="flex-start" px="$spacing24" py="$spacing12"> <Flex row alignItems="center" gap="$spacing8" justifyContent="flex-start" px={SEARCH_ITEM_PX} py={SEARCH_ITEM_PY}>
<Flex <Flex
centered centered
borderRadius="$roundedFull" borderRadius="$roundedFull"
height={iconSizes.icon40} height={SEARCH_ITEM_ICON_SIZE}
mr="$spacing4" mr="$spacing4"
overflow="hidden" overflow="hidden"
width={iconSizes.icon40} width={SEARCH_ITEM_ICON_SIZE}
> >
{imageUrl ? ( {imageUrl ? (
<NFTViewer uri={imageUrl} /> <NFTViewer uri={imageUrl} />
......
...@@ -3,8 +3,9 @@ import ContextMenu from 'react-native-context-menu-view' ...@@ -3,8 +3,9 @@ import ContextMenu from 'react-native-context-menu-view'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks' import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks' import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { SEARCH_ITEM_ICON_SIZE, SEARCH_ITEM_PX, SEARCH_ITEM_PY } from 'src/components/explore/search/constants'
import { disableOnPress } from 'src/utils/disableOnPress' import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, Text, TouchableArea } from 'ui/src' import { Flex, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo'
import WarningIcon from 'uniswap/src/components/warnings/WarningIcon' import WarningIcon from 'uniswap/src/components/warnings/WarningIcon'
import { getWarningIconColors } from 'uniswap/src/components/warnings/utils' import { getWarningIconColors } from 'uniswap/src/components/warnings/utils'
...@@ -18,6 +19,7 @@ import { getTokenWarningSeverity } from 'uniswap/src/features/tokens/safetyUtils ...@@ -18,6 +19,7 @@ import { getTokenWarningSeverity } from 'uniswap/src/features/tokens/safetyUtils
import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { buildCurrencyId, buildNativeCurrencyId } from 'uniswap/src/utils/currencyId' import { buildCurrencyId, buildNativeCurrencyId } from 'uniswap/src/utils/currencyId'
import { shortenAddress } from 'utilities/src/addresses'
type SearchTokenItemProps = { type SearchTokenItemProps = {
token: TokenSearchResult token: TokenSearchResult
...@@ -25,6 +27,7 @@ type SearchTokenItemProps = { ...@@ -25,6 +27,7 @@ type SearchTokenItemProps = {
} }
export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps): JSX.Element { export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps): JSX.Element {
const isDarkMode = useIsDarkMode()
const dispatch = useDispatch() const dispatch = useDispatch()
const tokenDetailsNavigation = useTokenDetailsNavigation() const tokenDetailsNavigation = useTokenDetailsNavigation()
...@@ -80,12 +83,18 @@ export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps): ...@@ -80,12 +83,18 @@ export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps):
onLongPress={disableOnPress} onLongPress={disableOnPress}
onPress={onPress} onPress={onPress}
> >
<Flex row alignItems="center" gap="$spacing12" px="$spacing24" py="$spacing12"> <Flex row alignItems="center" gap="$spacing12" px={SEARCH_ITEM_PX} py={SEARCH_ITEM_PY}>
<TokenLogo chainId={chainId} name={name} symbol={symbol} url={logoUrl ?? undefined} /> <TokenLogo
chainId={chainId}
name={name}
symbol={symbol}
url={logoUrl ?? undefined}
size={SEARCH_ITEM_ICON_SIZE}
/>
<Flex shrink alignItems="flex-start"> <Flex shrink alignItems="flex-start">
<Flex centered row gap="$spacing8"> <Flex centered row gap="$spacing8">
<Flex shrink> <Flex shrink>
<Text color="$neutral1" numberOfLines={1} variant="body1"> <Text color="$neutral1" numberOfLines={1} variant="subheading1">
{name} {name}
</Text> </Text>
</Flex> </Flex>
...@@ -94,9 +103,16 @@ export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps): ...@@ -94,9 +103,16 @@ export function SearchTokenItem({ token, searchContext }: SearchTokenItemProps):
)} )}
</Flex> </Flex>
<Flex centered row gap="$spacing8"> <Flex centered row gap="$spacing8">
<Text color="$neutral2" numberOfLines={1} variant="subheading2"> <Text color="$neutral2" numberOfLines={1} variant="body2">
{symbol} {symbol}
</Text> </Text>
{address && (
<Flex shrink>
<Text color={isDarkMode ? '$neutral3' : '$neutral2'} numberOfLines={1} variant="body3">
{shortenAddress(address)}
</Text>
</Flex>
)}
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>
......
import React from 'react' import React from 'react'
import { SEARCH_ITEM_ICON_SIZE, SEARCH_ITEM_PX, SEARCH_ITEM_PY } from 'src/components/explore/search/constants'
import { SearchWalletItemBase } from 'src/components/explore/search/items/SearchWalletItemBase' import { SearchWalletItemBase } from 'src/components/explore/search/items/SearchWalletItemBase'
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { imageSizes } from 'ui/src/theme'
import { useAvatar } from 'uniswap/src/features/address/avatar' import { useAvatar } from 'uniswap/src/features/address/avatar'
import { SearchContext } from 'uniswap/src/features/search/SearchContext' import { SearchContext } from 'uniswap/src/features/search/SearchContext'
import { UnitagSearchResult } from 'uniswap/src/features/search/SearchResult' import { UnitagSearchResult } from 'uniswap/src/features/search/SearchResult'
...@@ -24,8 +24,8 @@ export function SearchUnitagItem({ searchResult, searchContext }: SearchUnitagIt ...@@ -24,8 +24,8 @@ export function SearchUnitagItem({ searchResult, searchContext }: SearchUnitagIt
return ( return (
<SearchWalletItemBase searchContext={searchContext} searchResult={searchResult}> <SearchWalletItemBase searchContext={searchContext} searchResult={searchResult}>
<Flex row alignItems="center" gap="$spacing12" px="$spacing24" py="$spacing12"> <Flex row alignItems="center" gap="$spacing12" px={SEARCH_ITEM_PX} py={SEARCH_ITEM_PY}>
<AccountIcon address={address} avatarUri={avatar} size={imageSizes.image40} /> <AccountIcon address={address} avatarUri={avatar} size={SEARCH_ITEM_ICON_SIZE} />
<Flex alignItems="flex-start" justifyContent="center"> <Flex alignItems="flex-start" justifyContent="center">
<DisplayNameText includeUnitagSuffix displayName={displayName} textProps={{ variant: 'body1' }} /> <DisplayNameText includeUnitagSuffix displayName={displayName} textProps={{ variant: 'body1' }} />
<Text color="$neutral2" variant="body2"> <Text color="$neutral2" variant="body2">
......
import React from 'react' import React from 'react'
import { SEARCH_ITEM_ICON_SIZE, SEARCH_ITEM_PX, SEARCH_ITEM_PY } from 'src/components/explore/search/constants'
import { SearchWalletItemBase } from 'src/components/explore/search/items/SearchWalletItemBase' import { SearchWalletItemBase } from 'src/components/explore/search/items/SearchWalletItemBase'
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { imageSizes } from 'ui/src/theme'
import { useENSAvatar, useENSName } from 'uniswap/src/features/ens/api' import { useENSAvatar, useENSName } from 'uniswap/src/features/ens/api'
import { SearchContext } from 'uniswap/src/features/search/SearchContext' import { SearchContext } from 'uniswap/src/features/search/SearchContext'
import { WalletByAddressSearchResult } from 'uniswap/src/features/search/SearchResult' import { WalletByAddressSearchResult } from 'uniswap/src/features/search/SearchResult'
...@@ -25,8 +25,8 @@ export function SearchWalletByAddressItem({ ...@@ -25,8 +25,8 @@ export function SearchWalletByAddressItem({
return ( return (
<SearchWalletItemBase searchContext={searchContext} searchResult={searchResult}> <SearchWalletItemBase searchContext={searchContext} searchResult={searchResult}>
<Flex row alignItems="center" gap="$spacing12" px="$spacing24" py="$spacing12"> <Flex row alignItems="center" gap="$spacing12" px={SEARCH_ITEM_PX} py={SEARCH_ITEM_PY}>
<AccountIcon address={address} avatarUri={avatar} size={imageSizes.image40} /> <AccountIcon address={address} avatarUri={avatar} size={SEARCH_ITEM_ICON_SIZE} />
<Flex shrink> <Flex shrink>
<Text ellipsizeMode="tail" numberOfLines={1} testID={`address-display/name/${ensName}`} variant="body1"> <Text ellipsizeMode="tail" numberOfLines={1} testID={`address-display/name/${ensName}`} variant="body1">
{ensName || formattedAddress} {ensName || formattedAddress}
......
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Button, SpinningLoader, useIsShortMobileDevice } from 'ui/src' import { DeprecatedButton, SpinningLoader, useIsShortMobileDevice } from 'ui/src'
import { InfoCircleFilled } from 'ui/src/components/icons' import { InfoCircleFilled } from 'ui/src/components/icons'
interface FiatOnRampCtaButtonProps { interface FiatOnRampCtaButtonProps {
...@@ -23,7 +23,7 @@ export function FiatOnRampCtaButton({ ...@@ -23,7 +23,7 @@ export function FiatOnRampCtaButton({
const buttonAvailable = eligible || isLoading const buttonAvailable = eligible || isLoading
const continueText = eligible ? continueButtonText : t('fiatOnRamp.error.unsupported') const continueText = eligible ? continueButtonText : t('fiatOnRamp.error.unsupported')
return ( return (
<Button <DeprecatedButton
color={buttonAvailable ? '$white' : '$neutral2'} color={buttonAvailable ? '$white' : '$neutral2'}
disabled={disabled} disabled={disabled}
icon={ icon={
...@@ -35,6 +35,6 @@ export function FiatOnRampCtaButton({ ...@@ -35,6 +35,6 @@ export function FiatOnRampCtaButton({
onPress={onPress} onPress={onPress}
> >
{!isLoading && continueText} {!isLoading && continueText}
</Button> </DeprecatedButton>
) )
} }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment