ci(release): publish latest release

parent 89851955
* @uniswap/web-admins
Excited to share some new updates! Here’s what’s new: IPFS hash of the deployment:
- CIDv0: `QmQgjnjsbdgihYv6dHDgLpnXBkTwSwi3bthr3YLBgry9We`
Expanded support for NFTs — In addition to mainnet Ethereum, you are now able to see NFTs across all other networks that we currently support, including: Polygon, Arbitrum, Optimism, Base, BSC, and Blast. - CIDv1: `bafybeibc3s5567p7ggbzhvrxuqztosya2qdsixkjlre5onkgbmkiipjba4`
Improved Unicons — We gave your wallet’s unique Unicon a makeover. Check out the rest of your accounts to see your upgraded icons. The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeibc3s5567p7ggbzhvrxuqztosya2qdsixkjlre5onkgbmkiipjba4.ipfs.dweb.link/
- https://bafybeibc3s5567p7ggbzhvrxuqztosya2qdsixkjlre5onkgbmkiipjba4.ipfs.cf-ipfs.com/
- [ipfs://QmQgjnjsbdgihYv6dHDgLpnXBkTwSwi3bthr3YLBgry9We/](ipfs://QmQgjnjsbdgihYv6dHDgLpnXBkTwSwi3bthr3YLBgry9We/)
## 5.31.0 (2024-06-05)
### Features
* **web:** add utm tags to copyLink buttons (#8556) 7a70df6
* **web:** Only show identicon in send speedbump if ENS or Unitag (#8475) 5fea09e
* **web:** use max-image-preview for nft SEO (#8557) caf4d41
### Bug Fixes
* **web:** Correctly route back to previous page on AddLiq page (#8523) 21a8143
* **web:** don't disable swap settings for unconnected chains [staging] (#8653) b9731e2
* **web:** fix broken translations (#8581) 5a86cb6
* **web:** fix translations not loading (#8503) 12acb23
* **web:** improve connection loading/error state (#8533) 9371fb3
* **web:** properly filter buy/sell txs on TDP (#8511) 28ac0ef
* **web:** round Send input if greatern than max decimals allowed (#8525) f56c514
* **web:** update USDC address on Celo Alfajores testnet (#8484) d265731
* **web:** update useOnClickOutside to handle tooltips [staging] (#8705) 1a4610f
* **web:** use prev sitemaps for sitemap generation.. (#8524) b72a23f
### Continuous Integration
* **web:** update sitemaps ff410fb
mobile/1.28.2 web/5.31.0
\ No newline at end of file \ No newline at end of file
...@@ -14,6 +14,7 @@ ignores: [ ...@@ -14,6 +14,7 @@ ignores: [
"@amplitude/analytics-react-native", "@amplitude/analytics-react-native",
"@react-native-masked-view/masked-view", "@react-native-masked-view/masked-view",
"@react-native-firebase/app-check", "@react-native-firebase/app-check",
"@shopify/react-native-skia",
"react-native-image-colors", "react-native-image-colors",
"react-native-restart", "react-native-restart",
# Dependencies that depcheck thinks are missing but are actually present or never used # Dependencies that depcheck thinks are missing but are actually present or never used
......
...@@ -96,11 +96,10 @@ And select the version that pops up. ...@@ -96,11 +96,10 @@ And select the version that pops up.
Taken from [RN instructions](https://reactnative.dev/docs/environment-setup?guide=native&platform=android) Taken from [RN instructions](https://reactnative.dev/docs/environment-setup?guide=native&platform=android)
``` ```
brew tap homebrew/cask-versions brew install --cask zulu@17
brew install --cask zulu17
# Get path to where cask was installed to double-click installer # Get path to where cask was installed to double-click installer
brew info --cask zulu17 brew info --cask zulu@17
``` ```
Add the following to your .rc file Add the following to your .rc file
...@@ -176,18 +175,7 @@ These are some tools you might want to familiarize yourself with to understand t ...@@ -176,18 +175,7 @@ These are some tools you might want to familiarize yourself with to understand t
## Migrations ## Migrations
We use `redux-persist` to persist Redux state between user sessions. When the Redux state schema is altered, a migration may be needed to transfer the existing persisted state to the new Redux schema. Failing to define a migration results in the app defaulting to the persisted schema, which will very likely cause `undefined` errors because the code has references to Redux state properties that were dropped in favor the persisted schema. 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.
### When to define a migration
Anytime a required property is added or any property is renamed or deleted to/from Redux state. Migrations are not necessary when optional properties are added to an existing slice. Make sure to always add new required properties to the `schema.ts` file as well.
### How to migrate
1. Increment the `version` of `persistConfig` defined within `store.ts`
2. Create a migration function within `migrations.ts`. The migration key should be the same as the `version` defined in the previous step
3. Write a test for your migration within `migrations.test.ts`
4. Create a new schema within `schema.ts` and ensure it is being exported by the `getSchema` function at the bottom of the file
## Troubleshooting ## Troubleshooting
......
...@@ -136,12 +136,12 @@ android { ...@@ -136,12 +136,12 @@ android {
} }
beta { beta {
applicationIdSuffix ".beta" applicationIdSuffix ".beta"
versionName "1.28.2" versionName "1.28"
dimension "variant" dimension "variant"
} }
prod { prod {
dimension "variant" dimension "variant"
versionName "1.28.2" versionName "1.28"
} }
} }
......
...@@ -646,9 +646,6 @@ PODS: ...@@ -646,9 +646,6 @@ PODS:
- BoringSSL-GRPC/Interface (0.0.24) - BoringSSL-GRPC/Interface (0.0.24)
- DoubleConversion (1.1.6) - DoubleConversion (1.1.6)
- EthersRS (0.0.5) - EthersRS (0.0.5)
- EXAV (13.10.5):
- ExpoModulesCore
- ReactCommon/turbomodule/core
- EXBarCodeScanner (12.9.3): - EXBarCodeScanner (12.9.3):
- EXImageLoader - EXImageLoader
- ExpoModulesCore - ExpoModulesCore
...@@ -2042,7 +2039,6 @@ DEPENDENCIES: ...@@ -2042,7 +2039,6 @@ DEPENDENCIES:
- boost (from `../../../node_modules/react-native/third-party-podspecs/boost.podspec`) - boost (from `../../../node_modules/react-native/third-party-podspecs/boost.podspec`)
- DoubleConversion (from `../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - DoubleConversion (from `../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- "EthersRS (from `../../../node_modules/@uniswap/ethers-rs-mobile`)" - "EthersRS (from `../../../node_modules/@uniswap/ethers-rs-mobile`)"
- EXAV (from `../../../node_modules/expo-av/ios`)
- EXBarCodeScanner (from `../../../node_modules/expo-barcode-scanner/ios`) - EXBarCodeScanner (from `../../../node_modules/expo-barcode-scanner/ios`)
- EXFont (from `../../../node_modules/expo-font/ios`) - EXFont (from `../../../node_modules/expo-font/ios`)
- EXImageLoader (from `../../../node_modules/expo-image-loader/ios`) - EXImageLoader (from `../../../node_modules/expo-image-loader/ios`)
...@@ -2196,8 +2192,6 @@ EXTERNAL SOURCES: ...@@ -2196,8 +2192,6 @@ EXTERNAL SOURCES:
:podspec: "../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" :podspec: "../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
EthersRS: EthersRS:
:path: "../../../node_modules/@uniswap/ethers-rs-mobile" :path: "../../../node_modules/@uniswap/ethers-rs-mobile"
EXAV:
:path: "../../../node_modules/expo-av/ios"
EXBarCodeScanner: EXBarCodeScanner:
:path: "../../../node_modules/expo-barcode-scanner/ios" :path: "../../../node_modules/expo-barcode-scanner/ios"
EXFont: EXFont:
...@@ -2408,7 +2402,6 @@ SPEC CHECKSUMS: ...@@ -2408,7 +2402,6 @@ SPEC CHECKSUMS:
BoringSSL-GRPC: 3175b25143e648463a56daeaaa499c6cb86dad33 BoringSSL-GRPC: 3175b25143e648463a56daeaaa499c6cb86dad33
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
EthersRS: 56b70e73d22d4e894b7e762eef1129159bcd3135 EthersRS: 56b70e73d22d4e894b7e762eef1129159bcd3135
EXAV: 07e50f934907fa8274dd06fbcd20ee4b9478c619
EXBarCodeScanner: d59fd943cebee3f913ebf4ffde0d05d344da8b78 EXBarCodeScanner: d59fd943cebee3f913ebf4ffde0d05d344da8b78
EXFont: f20669cb266ef48b004f1eb1f2b20db96cd1df9f EXFont: f20669cb266ef48b004f1eb1f2b20db96cd1df9f
EXImageLoader: 55080616b2fe9da19ef8c7f706afd9814e279b6b EXImageLoader: 55080616b2fe9da19ef8c7f706afd9814e279b6b
......
...@@ -2536,7 +2536,7 @@ ...@@ -2536,7 +2536,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.28.2; MARKETING_VERSION = 1.28;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
...@@ -2628,7 +2628,7 @@ ...@@ -2628,7 +2628,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.28.2; MARKETING_VERSION = 1.28;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
...@@ -2713,7 +2713,7 @@ ...@@ -2713,7 +2713,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.28.2; MARKETING_VERSION = 1.28;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
...@@ -2799,7 +2799,7 @@ ...@@ -2799,7 +2799,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.28.2; MARKETING_VERSION = 1.28;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
...@@ -2873,7 +2873,7 @@ ...@@ -2873,7 +2873,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.28.2; MARKETING_VERSION = 1.28;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3103,7 +3103,7 @@ ...@@ -3103,7 +3103,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.28.2; MARKETING_VERSION = 1.28;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
...@@ -3207,7 +3207,7 @@ ...@@ -3207,7 +3207,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.28.2; MARKETING_VERSION = 1.28;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3278,7 +3278,7 @@ ...@@ -3278,7 +3278,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.28.2; MARKETING_VERSION = 1.28;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
......
...@@ -9,17 +9,6 @@ import { localizeMock as mockRNLocalize } from 'react-native-localize/mock' ...@@ -9,17 +9,6 @@ import { localizeMock as mockRNLocalize } from 'react-native-localize/mock'
import { AppearanceSettingType } from 'wallet/src/features/appearance/slice' import { AppearanceSettingType } from 'wallet/src/features/appearance/slice'
import { mockLocalizationContext } from 'wallet/src/test/mocks/utils' import { mockLocalizationContext } from 'wallet/src/test/mocks/utils'
// avoids polluting console in test runs, while keeping important log levels
global.console = {
...console,
// uncomment to ignore a specific log level
log: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
// warn: jest.fn(),
// error: jest.fn(),
}
// Mock Sentry crash reporting // Mock Sentry crash reporting
jest.mock('@sentry/react-native', () => ({ jest.mock('@sentry/react-native', () => ({
init: () => jest.fn(), init: () => jest.fn(),
......
...@@ -97,7 +97,6 @@ ...@@ -97,7 +97,6 @@
"dayjs": "1.11.7", "dayjs": "1.11.7",
"ethers": "5.7.2", "ethers": "5.7.2",
"expo": "50.0.15", "expo": "50.0.15",
"expo-av": "13.10.5",
"expo-barcode-scanner": "12.9.3", "expo-barcode-scanner": "12.9.3",
"expo-blur": "12.9.2", "expo-blur": "12.9.2",
"expo-camera": "14.1.2", "expo-camera": "14.1.2",
......
...@@ -70,7 +70,7 @@ import { selectFavoriteTokens } from 'wallet/src/features/favorites/selectors' ...@@ -70,7 +70,7 @@ import { selectFavoriteTokens } from 'wallet/src/features/favorites/selectors'
import { useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks' import { useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks'
import { LocalizationContextProvider } from 'wallet/src/features/language/LocalizationContext' import { LocalizationContextProvider } from 'wallet/src/features/language/LocalizationContext'
import { useCurrentLanguageInfo } from 'wallet/src/features/language/hooks' import { useCurrentLanguageInfo } from 'wallet/src/features/language/hooks'
import { updateLanguage } from 'wallet/src/features/language/slice' import { syncAppWithDeviceLanguage } from 'wallet/src/features/language/slice'
import { clearNotificationQueue } from 'wallet/src/features/notifications/slice' import { clearNotificationQueue } from 'wallet/src/features/notifications/slice'
import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater' import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater'
import { Account } from 'wallet/src/features/wallet/accounts/types' import { Account } from 'wallet/src/features/wallet/accounts/types'
...@@ -255,11 +255,11 @@ function AppInner(): JSX.Element { ...@@ -255,11 +255,11 @@ function AppInner(): JSX.Element {
useEffect(() => { useEffect(() => {
if (allowAnalytics) { if (allowAnalytics) {
appsFlyer.startSdk() appsFlyer.startSdk()
logger.info('AppsFlyer', 'status', 'started') logger.debug('AppsFlyer', 'status', 'started')
} else { } else {
appsFlyer.stop(!allowAnalytics, (res: unknown) => { appsFlyer.stop(!allowAnalytics, (res: unknown) => {
if (typeof res === 'string' && res === 'Success') { if (typeof res === 'string' && res === 'Success') {
logger.info('AppsFlyer', 'status', 'stopped') logger.debug('AppsFlyer', 'status', 'stopped')
} else { } else {
logger.warn( logger.warn(
'AppsFlyer', 'AppsFlyer',
...@@ -273,7 +273,7 @@ function AppInner(): JSX.Element { ...@@ -273,7 +273,7 @@ function AppInner(): JSX.Element {
useEffect(() => { useEffect(() => {
dispatch(clearNotificationQueue()) // clear all in-app toasts on app start dispatch(clearNotificationQueue()) // clear all in-app toasts on app start
dispatch(updateLanguage(null)) dispatch(syncAppWithDeviceLanguage())
}, [dispatch]) }, [dispatch])
useEffect(() => { useEffect(() => {
......
...@@ -104,6 +104,7 @@ import { ...@@ -104,6 +104,7 @@ import {
} from 'wallet/src/features/wallet/accounts/types' } from 'wallet/src/features/wallet/accounts/types'
import { initialWalletState, SwapProtectionSetting } from 'wallet/src/features/wallet/slice' import { initialWalletState, SwapProtectionSetting } from 'wallet/src/features/wallet/slice'
import { createMigrate } from 'wallet/src/state/createMigrate' import { createMigrate } from 'wallet/src/state/createMigrate'
import { getAllKeysOfNestedObject } from 'wallet/src/state/testUtils'
import { import {
fiatPurchaseTransactionInfo, fiatPurchaseTransactionInfo,
signerMnemonicAccount, signerMnemonicAccount,
...@@ -124,26 +125,6 @@ const fiatOnRampTxDetailsFailed = transactionDetails({ ...@@ -124,26 +125,6 @@ const fiatOnRampTxDetailsFailed = transactionDetails({
}), }),
}) })
// helps with object assignment
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getAllKeysOfNestedObject = (obj: any, prefix = ''): string[] => {
const keys = Object.keys(obj)
if (!keys.length && prefix !== '') {
return [prefix.slice(0, -1)]
}
return keys.reduce<string[]>((res, el) => {
if (Array.isArray(obj[el])) {
return [...res]
}
if (typeof obj[el] === 'object' && obj[el] !== null) {
return [...res, ...getAllKeysOfNestedObject(obj[el], prefix + el + '.')]
}
return [...res, prefix + el]
}, [])
}
describe('Redux state migrations', () => { describe('Redux state migrations', () => {
it('is able to perform all migrations starting from the initial schema', async () => { it('is able to perform all migrations starting from the initial schema', async () => {
const initialSchemaStub = { const initialSchemaStub = {
...@@ -204,6 +185,10 @@ describe('Redux state migrations', () => { ...@@ -204,6 +185,10 @@ describe('Redux state migrations', () => {
}, },
} }
if (!migratedSchema) {
throw new Error('Migrated schema is undefined')
}
const migratedSchemaKeys = new Set(getAllKeysOfNestedObject(migratedSchema)) const migratedSchemaKeys = new Set(getAllKeysOfNestedObject(migratedSchema))
const latestSchemaKeys = new Set(getAllKeysOfNestedObject(getSchema())) const latestSchemaKeys = new Set(getAllKeysOfNestedObject(getSchema()))
const initialStateKeys = new Set(getAllKeysOfNestedObject(initialState)) const initialStateKeys = new Set(getAllKeysOfNestedObject(initialState))
......
...@@ -19,6 +19,7 @@ import { ...@@ -19,6 +19,7 @@ import {
} from 'wallet/src/features/transactions/types' } from 'wallet/src/features/transactions/types'
import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types' import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types'
import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice' import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice'
import { removeWalletIsUnlockedState } from 'wallet/src/state/sharedMigrations'
export const OLD_DEMO_ACCOUNT_ADDRESS = '0xdd0E380579dF30E38524F9477808d9eE37E2dEa6' export const OLD_DEMO_ACCOUNT_ADDRESS = '0xdd0E380579dF30E38524F9477808d9eE37E2dEa6'
...@@ -876,10 +877,7 @@ export const migrations = { ...@@ -876,10 +877,7 @@ export const migrations = {
return newState return newState
}, },
63: function removeWalletIsUnlockedState(state: any) { 63: removeWalletIsUnlockedState,
const newState = { ...state }
delete newState.wallet.isUnlocked
return newState
},
} }
export const MOBILE_STATE_VERSION = 63
...@@ -23,7 +23,7 @@ import { ElementName, MobileEventName, ModalName } from 'uniswap/src/features/te ...@@ -23,7 +23,7 @@ import { ElementName, MobileEventName, ModalName } from 'uniswap/src/features/te
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { ActionSheetModal, MenuItemProp } from 'wallet/src/components/modals/ActionSheetModal' import { ActionSheetModal, MenuItemProp } from 'wallet/src/components/modals/ActionSheetModal'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
......
import React from 'react' import React, { useCallback } from 'react'
import { AccountSwitcherModal } from 'src/app/modals/AccountSwitcherModal' import { AccountSwitcherModal } from 'src/app/modals/AccountSwitcherModal'
import { ExperimentsModal } from 'src/app/modals/ExperimentsModal' import { ExperimentsModal } from 'src/app/modals/ExperimentsModal'
import { ExploreModal } from 'src/app/modals/ExploreModal' import { ExploreModal } from 'src/app/modals/ExploreModal'
...@@ -15,14 +15,22 @@ import { LockScreenModal } from 'src/features/authentication/LockScreenModal' ...@@ -15,14 +15,22 @@ import { LockScreenModal } from 'src/features/authentication/LockScreenModal'
import { ExchangeTransferModal } from 'src/features/fiatOnRamp/ExchangeTransferModal' import { ExchangeTransferModal } from 'src/features/fiatOnRamp/ExchangeTransferModal'
import { FiatOnRampAggregatorModal } from 'src/features/fiatOnRamp/FiatOnRampAggregatorModal' import { FiatOnRampAggregatorModal } from 'src/features/fiatOnRamp/FiatOnRampAggregatorModal'
import { FiatOnRampModal } from 'src/features/fiatOnRamp/FiatOnRampModal' import { FiatOnRampModal } from 'src/features/fiatOnRamp/FiatOnRampModal'
import { closeModal } from 'src/features/modals/modalSlice'
import { ExtensionWaitlistModal } from 'src/features/scantastic/ExtensionWaitlistModal' import { ExtensionWaitlistModal } from 'src/features/scantastic/ExtensionWaitlistModal'
import { ScantasticModal } from 'src/features/scantastic/ScantasticModal' import { ScantasticModal } from 'src/features/scantastic/ScantasticModal'
import { ReceiveCryptoModal } from 'src/screens/ReceiveCryptoModal' import { ReceiveCryptoModal } from 'src/screens/ReceiveCryptoModal'
import { SettingsFiatCurrencyModal } from 'src/screens/SettingsFiatCurrencyModal' import { SettingsFiatCurrencyModal } from 'src/screens/SettingsFiatCurrencyModal'
import { SettingsLanguageModal } from 'src/screens/SettingsLanguageModal'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { SettingsLanguageModal } from 'wallet/src/components/settings/language/SettingsLanguageModal'
import { useAppDispatch } from 'wallet/src/state'
export function AppModals(): JSX.Element { export function AppModals(): JSX.Element {
const dispatch = useAppDispatch()
const onCloseLanguageModal = useCallback(() => {
dispatch(closeModal({ name: ModalName.LanguageSelector }))
}, [dispatch])
return ( return (
<> <>
<LazyModalRenderer name={ModalName.ExchangeTransferModal}> <LazyModalRenderer name={ModalName.ExchangeTransferModal}>
...@@ -84,7 +92,7 @@ export function AppModals(): JSX.Element { ...@@ -84,7 +92,7 @@ export function AppModals(): JSX.Element {
</LazyModalRenderer> </LazyModalRenderer>
<LazyModalRenderer name={ModalName.LanguageSelector}> <LazyModalRenderer name={ModalName.LanguageSelector}>
<SettingsLanguageModal /> <SettingsLanguageModal onClose={onCloseLanguageModal} />
</LazyModalRenderer> </LazyModalRenderer>
<LazyModalRenderer name={ModalName.FiatCurrencySelector}> <LazyModalRenderer name={ModalName.FiatCurrencySelector}>
......
...@@ -31,7 +31,7 @@ import { borderRadii, fonts } from 'ui/src/theme' ...@@ -31,7 +31,7 @@ import { borderRadii, fonts } from 'ui/src/theme'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isAndroid, isIOS } from 'uniswap/src/utils/platform' import { isAndroid, isIOS } from 'utilities/src/platform'
import { useHighestBalanceNativeCurrencyId } from 'wallet/src/features/dataApi/balances' import { useHighestBalanceNativeCurrencyId } from 'wallet/src/features/dataApi/balances'
import { prepareSwapFormState } from 'wallet/src/features/transactions/swap/utils' import { prepareSwapFormState } from 'wallet/src/features/transactions/swap/utils'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
......
...@@ -3,7 +3,7 @@ import { isRejectedWithValue } from '@reduxjs/toolkit' ...@@ -3,7 +3,7 @@ import { isRejectedWithValue } from '@reduxjs/toolkit'
import * as Sentry from '@sentry/react' import * as Sentry from '@sentry/react'
import { MMKV } from 'react-native-mmkv' import { MMKV } from 'react-native-mmkv'
import { Storage, persistReducer, persistStore } from 'redux-persist' import { Storage, persistReducer, persistStore } from 'redux-persist'
import { migrations } from 'src/app/migrations' import { MOBILE_STATE_VERSION, migrations } from 'src/app/migrations'
import { isNonJestDev } from 'utilities/src/environment' import { isNonJestDev } from 'utilities/src/environment'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { fiatOnRampAggregatorApi, fiatOnRampApi } from 'wallet/src/features/fiatOnRamp/api' import { fiatOnRampAggregatorApi, fiatOnRampApi } from 'wallet/src/features/fiatOnRamp/api'
...@@ -66,7 +66,7 @@ export const persistConfig = { ...@@ -66,7 +66,7 @@ export const persistConfig = {
key: 'root', key: 'root',
storage: reduxStorage, storage: reduxStorage,
whitelist, whitelist,
version: 63, version: MOBILE_STATE_VERSION,
migrate: createMigrate(migrations), migrate: createMigrate(migrations),
} }
......
...@@ -4,7 +4,7 @@ import { useLineChartDatetime } from 'react-native-wagmi-charts' ...@@ -4,7 +4,7 @@ import { useLineChartDatetime } from 'react-native-wagmi-charts'
import { AnimatedText } from 'src/components/text/AnimatedText' import { AnimatedText } from 'src/components/text/AnimatedText'
import { Flex, useSporeColors } from 'ui/src' import { Flex, useSporeColors } from 'ui/src'
import { AnimatedCaretChange } from 'ui/src/components/icons' import { AnimatedCaretChange } from 'ui/src/components/icons'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { FiatCurrency } from 'wallet/src/features/fiatCurrency/constants' import { FiatCurrency } from 'wallet/src/features/fiatCurrency/constants'
import { useAppFiatCurrency, useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks' import { useAppFiatCurrency, useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks'
import { useCurrentLocale } from 'wallet/src/features/language/hooks' import { useCurrentLocale } from 'wallet/src/features/language/hooks'
......
...@@ -112,8 +112,8 @@ describe(useLineChartPrice, () => { ...@@ -112,8 +112,8 @@ describe(useLineChartPrice, () => {
it('returns currentSpot if it is provided', async () => { it('returns currentSpot if it is provided', async () => {
const spotPrice = makeMutable(1) const spotPrice = makeMutable(1)
const { result } = renderHookWithProviders(useLineChartPrice, { const { result } = renderHookWithProviders(() => useLineChartPrice(spotPrice), {
initialProps: [spotPrice], initialProps: spotPrice,
}) })
expect(result.current).toEqual({ expect(result.current).toEqual({
...@@ -149,7 +149,7 @@ describe(useLineChartPrice, () => { ...@@ -149,7 +149,7 @@ describe(useLineChartPrice, () => {
it('returns active cursor price even if currentSpot and data are provided', async () => { it('returns active cursor price even if currentSpot and data are provided', async () => {
mockCursorPrice('3') mockCursorPrice('3')
const { result } = renderHookWithProviders(useLineChartPrice, { const { result } = renderHookWithProviders(useLineChartPrice, {
initialProps: [makeMutable(4)], initialProps: makeMutable(4),
}) })
expect(result.current).toEqual({ expect(result.current).toEqual({
...@@ -162,7 +162,7 @@ describe(useLineChartPrice, () => { ...@@ -162,7 +162,7 @@ describe(useLineChartPrice, () => {
it('updates returned active cursor price when it changes', async () => { it('updates returned active cursor price when it changes', async () => {
mockCursorPrice('1') mockCursorPrice('1')
const { result } = renderHookWithProviders(useLineChartPrice, { const { result } = renderHookWithProviders(useLineChartPrice, {
initialProps: [makeMutable(4)], initialProps: makeMutable(4),
}) })
expect(result.current).toEqual( expect(result.current).toEqual(
......
...@@ -2,7 +2,7 @@ import React from 'react' ...@@ -2,7 +2,7 @@ import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useBiometricName } from 'src/features/biometrics/hooks' import { useBiometricName } from 'src/features/biometrics/hooks'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { import {
WarningModal, WarningModal,
WarningModalProps, WarningModalProps,
......
...@@ -24,7 +24,7 @@ import { ...@@ -24,7 +24,7 @@ import {
import { zIndices } from 'ui/src/theme' import { zIndices } from 'ui/src/theme'
import { CurrencyId } from 'uniswap/src/types/currency' import { CurrencyId } from 'uniswap/src/types/currency'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard' import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { isError, isNonPollingRequestInFlight } from 'wallet/src/data/utils' import { isError, isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { HiddenTokensRow } from 'wallet/src/features/portfolio/HiddenTokensRow' import { HiddenTokensRow } from 'wallet/src/features/portfolio/HiddenTokensRow'
......
...@@ -41,10 +41,12 @@ export function TokenDetailsActionButtons({ ...@@ -41,10 +41,12 @@ export function TokenDetailsActionButtons({
onPressBuy, onPressBuy,
onPressSell, onPressSell,
tokenColor, tokenColor,
userHasBalance,
}: { }: {
onPressBuy: () => void onPressBuy: () => void
onPressSell: () => void onPressSell: () => void
tokenColor?: Maybe<string> tokenColor?: Maybe<string>
userHasBalance: boolean
}): JSX.Element { }): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -64,12 +66,14 @@ export function TokenDetailsActionButtons({ ...@@ -64,12 +66,14 @@ export function TokenDetailsActionButtons({
tokenColor={tokenColor} tokenColor={tokenColor}
onPress={onPressBuy} onPress={onPressBuy}
/> />
<CTAButton {userHasBalance && (
element={ElementName.Sell} <CTAButton
title={t('common.button.sell')} element={ElementName.Sell}
tokenColor={tokenColor} title={t('common.button.sell')}
onPress={onPressSell} tokenColor={tokenColor}
/> onPress={onPressSell}
/>
)}
</Flex> </Flex>
) )
} }
...@@ -10,7 +10,7 @@ import { getAuthMethod } from 'src/features/telemetry/utils' ...@@ -10,7 +10,7 @@ import { getAuthMethod } from 'src/features/telemetry/utils'
import { getFullAppVersion } from 'src/utils/version' import { getFullAppVersion } from 'src/utils/version'
import { useIsDarkMode } from 'ui/src' import { useIsDarkMode } from 'ui/src'
import { MobileUserPropertyName, setUserProperty } from 'uniswap/src/features/telemetry/user' import { MobileUserPropertyName, setUserProperty } from 'uniswap/src/features/telemetry/user'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { analytics } from 'utilities/src/telemetry/analytics/analytics' import { analytics } from 'utilities/src/telemetry/analytics/analytics'
import { useAppFiatCurrency } from 'wallet/src/features/fiatCurrency/hooks' import { useAppFiatCurrency } from 'wallet/src/features/fiatCurrency/hooks'
......
...@@ -87,7 +87,6 @@ export function WalletConnectModal({ ...@@ -87,7 +87,6 @@ export function WalletConnectModal({
setShouldFreezeCamera(true) setShouldFreezeCamera(true)
Alert.alert( Alert.alert(
t('walletConnect.error.unsupported.title'), t('walletConnect.error.unsupported.title'),
// TODO(EXT-495): Add Scantastic product name here when ready
t('walletConnect.error.unsupported.message'), t('walletConnect.error.unsupported.message'),
[ [
{ {
......
...@@ -100,25 +100,22 @@ exports[`TokenItem renders without error 1`] = ` ...@@ -100,25 +100,22 @@ exports[`TokenItem renders without error 1`] = `
testID="token-logo" testID="token-logo"
> >
<Image <Image
resizeMode="contain" height={40}
source={ source={
{ {
"uri": "https://loremflickr.com/640/480", "uri": "https://loremflickr.com/640/480",
} }
} }
style={ style={
[ {
{ "aspectRatio": undefined,
"backgroundColor": "#2222220D", "backgroundColor": "#2222220D",
"borderColor": "#2222220D", "borderRadius": 20,
"borderRadius": 20, "flex": undefined,
"borderWidth": 0.5, }
"height": 40,
"width": 40,
},
]
} }
testID="token-image" testID="img-token-image"
width={40}
/> />
</View> </View>
</View> </View>
......
...@@ -30,7 +30,7 @@ import { Jiggly } from 'ui/src/animations' ...@@ -30,7 +30,7 @@ import { Jiggly } from 'ui/src/animations'
import { ONBOARDING_LANDING_DARK, ONBOARDING_LANDING_LIGHT, UNISWAP_APP_ICON } from 'ui/src/assets' import { ONBOARDING_LANDING_DARK, ONBOARDING_LANDING_LIGHT, UNISWAP_APP_ICON } from 'ui/src/assets'
import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions'
import { imageSizes } from 'ui/src/theme' import { imageSizes } from 'ui/src/theme'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { useTimeout } from 'utilities/src/time/timing' import { useTimeout } from 'utilities/src/time/timing'
import { Language } from 'wallet/src/features/language/constants' import { Language } from 'wallet/src/features/language/constants'
......
...@@ -14,7 +14,7 @@ import { removePendingSession } from 'src/features/walletConnect/walletConnectSl ...@@ -14,7 +14,7 @@ import { removePendingSession } from 'src/features/walletConnect/walletConnectSl
import { Flex, useDeviceInsets, useSporeColors } from 'ui/src' import { Flex, useDeviceInsets, useSporeColors } from 'ui/src'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { useActivityData } from 'wallet/src/features/activity/useActivityData' import { useActivityData } from 'wallet/src/features/activity/useActivityData'
......
...@@ -13,7 +13,7 @@ import { Flex, Text, useDeviceInsets, useSporeColors } from 'ui/src' ...@@ -13,7 +13,7 @@ import { Flex, Text, useDeviceInsets, useSporeColors } from 'ui/src'
import { NoTransactions } from 'ui/src/components/icons' import { NoTransactions } from 'ui/src/components/icons'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard' import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { useFormattedTransactionDataForFeed } from 'wallet/src/features/activity/hooks' import { useFormattedTransactionDataForFeed } from 'wallet/src/features/activity/hooks'
......
...@@ -8,7 +8,7 @@ import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers' ...@@ -8,7 +8,7 @@ import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers'
import { Flex, useDeviceInsets, useSporeColors } from 'ui/src' import { Flex, useDeviceInsets, useSporeColors } from 'ui/src'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { NftsList } from 'wallet/src/components/nfts/NftsList' import { NftsList } from 'wallet/src/components/nfts/NftsList'
import { NFTItem } from 'wallet/src/features/nfts/types' import { NFTItem } from 'wallet/src/features/nfts/types'
......
...@@ -10,8 +10,8 @@ import { ElementName, ModalName, UnitagEventName } from 'uniswap/src/features/te ...@@ -10,8 +10,8 @@ import { ElementName, ModalName, UnitagEventName } from 'uniswap/src/features/te
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { useUnitagUpdater } from 'uniswap/src/features/unitags/context' import { useUnitagUpdater } from 'uniswap/src/features/unitags/context'
import { UnitagErrorCodes } from 'uniswap/src/features/unitags/types' import { UnitagErrorCodes } from 'uniswap/src/features/unitags/types'
import { isIOS } from 'uniswap/src/utils/platform'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { isIOS } from 'utilities/src/platform'
import { useAsyncData } from 'utilities/src/react/hooks' import { useAsyncData } from 'utilities/src/react/hooks'
import { TextInput } from 'wallet/src/components/input/TextInput' import { TextInput } from 'wallet/src/components/input/TextInput'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
......
...@@ -7,8 +7,8 @@ import { FeatureFlags, WALLET_FEATURE_FLAG_NAMES } from 'uniswap/src/features/ga ...@@ -7,8 +7,8 @@ import { FeatureFlags, WALLET_FEATURE_FLAG_NAMES } from 'uniswap/src/features/ga
import { Statsig } from 'uniswap/src/features/gating/sdk/statsig' import { Statsig } from 'uniswap/src/features/gating/sdk/statsig'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { isAndroid } from 'uniswap/src/utils/platform'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { isAndroid } from 'utilities/src/platform'
import { ONE_DAY_MS, ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_DAY_MS, ONE_SECOND_MS } from 'utilities/src/time/time'
import { finalizeTransaction } from 'wallet/src/features/transactions/slice' import { finalizeTransaction } from 'wallet/src/features/transactions/slice'
import { TransactionStatus, TransactionType } from 'wallet/src/features/transactions/types' import { TransactionStatus, TransactionType } from 'wallet/src/features/transactions/types'
...@@ -81,19 +81,19 @@ function* maybeRequestAppRating() { ...@@ -81,19 +81,19 @@ function* maybeRequestAppRating() {
const shouldPrompt = consecutiveSwapsCondition && (hasNeverPrompted || reminderCondition) const shouldPrompt = consecutiveSwapsCondition && (hasNeverPrompted || reminderCondition)
if (!shouldPrompt) { if (!shouldPrompt) {
logger.debug( logger.debug('appRating', 'maybeRequestAppRating', 'Skipping app rating', {
'appRating', lastPrompt: appRatingPromptedMs,
'maybeRequestAppRating', lastProvided: appRatingProvidedMs,
`Skipping app rating (lastPrompt: ${appRatingPromptedMs}, lastProvided: ${appRatingProvidedMs}, consecutiveSwapsCondition: ${consecutiveSwapsCondition})` consecutiveSwapsCondition,
) })
return return
} }
logger.info( logger.info('appRating', 'maybeRequestAppRating', 'Requesting app rating', {
'appRating', lastPrompt: appRatingPromptedMs,
'maybeRequestAppRating', lastProvided: appRatingProvidedMs,
`Requesting app rating (lastPrompt: ${appRatingPromptedMs}, lastProvided: ${appRatingProvidedMs}, consecutiveSwapsCondition: ${consecutiveSwapsCondition})` consecutiveSwapsCondition,
) })
// Alerts // Alerts
const shouldShowNativeReviewModal = yield* call(openRatingOptionsAlert) const shouldShowNativeReviewModal = yield* call(openRatingOptionsAlert)
......
...@@ -5,7 +5,7 @@ import { useLockScreenContext } from 'src/features/authentication/lockScreenCont ...@@ -5,7 +5,7 @@ import { useLockScreenContext } from 'src/features/authentication/lockScreenCont
import { useBiometricPrompt } from 'src/features/biometrics/hooks' import { useBiometricPrompt } from 'src/features/biometrics/hooks'
import { Flex, TouchableArea, useDeviceDimensions, useDeviceInsets, useIsDarkMode } from 'ui/src' import { Flex, TouchableArea, useDeviceDimensions, useDeviceInsets, useIsDarkMode } from 'ui/src'
import { UNISWAP_LOGO_LARGE } from 'ui/src/assets' import { UNISWAP_LOGO_LARGE } from 'ui/src/assets'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
export const SPLASH_SCREEN = { uri: 'SplashScreen' } export const SPLASH_SCREEN = { uri: 'SplashScreen' }
......
...@@ -8,7 +8,7 @@ import { useAppSelector } from 'src/app/hooks' ...@@ -8,7 +8,7 @@ import { useAppSelector } from 'src/app/hooks'
import { BiometricAuthenticationStatus, tryLocalAuthenticate } from 'src/features/biometrics' import { BiometricAuthenticationStatus, tryLocalAuthenticate } from 'src/features/biometrics'
import { useBiometricContext } from 'src/features/biometrics/context' import { useBiometricContext } from 'src/features/biometrics/context'
import { BiometricSettingsState } from 'src/features/biometrics/slice' import { BiometricSettingsState } from 'src/features/biometrics/slice'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { useAsyncData } from 'utilities/src/react/hooks' import { useAsyncData } from 'utilities/src/react/hooks'
type TriggerArgs<T> = { type TriggerArgs<T> = {
......
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { import { Keyboard, TextInput as NativeTextInput } from 'react-native'
Keyboard,
LayoutChangeEvent,
LayoutRectangle,
TextInput as NativeTextInput,
StyleSheet,
} from 'react-native'
import InputWithSuffix from 'src/features/import/InputWithSuffix' import InputWithSuffix from 'src/features/import/InputWithSuffix'
import { Flex, Text, useMedia } from 'ui/src' import { Flex, Text, useMedia } from 'ui/src'
import { fonts } from 'ui/src/theme' import { fonts } from 'ui/src/theme'
...@@ -51,7 +45,6 @@ export function GenericImportForm({ ...@@ -51,7 +45,6 @@ export function GenericImportForm({
shouldUseMinHeight = true, shouldUseMinHeight = true,
}: Props): JSX.Element { }: Props): JSX.Element {
const [focused, setFocused] = useState(false) const [focused, setFocused] = useState(false)
const [layout, setLayout] = useState<LayoutRectangle | null>()
const textInputRef = useRef<NativeTextInput>(null) const textInputRef = useRef<NativeTextInput>(null)
const isKeyboardVisibleRef = useRef(false) const isKeyboardVisibleRef = useRef(false)
const media = useMedia() const media = useMedia()
...@@ -97,9 +90,7 @@ export function GenericImportForm({ ...@@ -97,9 +90,7 @@ export function GenericImportForm({
const INPUT_MIN_HEIGHT = 120 const INPUT_MIN_HEIGHT = 120
const INPUT_MIN_HEIGHT_SHORT = 90 const INPUT_MIN_HEIGHT_SHORT = 90
// Absolutely positioned paste button needs top padding to be vertically centered on bottom border of Flex const showError = errorMessage && value && (liveCheck || !focused)
const PASTE_BUTTON_TOP_PADDING = INPUT_MIN_HEIGHT / 2 + 4
const SHORT_PASTE_BUTTON_TOP_PADDING = INPUT_MIN_HEIGHT_SHORT / 2 - 12
return ( return (
<Trace section={SectionName.ImportAccountForm}> <Trace section={SectionName.ImportAccountForm}>
...@@ -119,7 +110,7 @@ export function GenericImportForm({ ...@@ -119,7 +110,7 @@ export function GenericImportForm({
minHeight: shouldUseMinHeight ? INPUT_MIN_HEIGHT_SHORT : undefined, minHeight: shouldUseMinHeight ? INPUT_MIN_HEIGHT_SHORT : undefined,
}} }}
backgroundColor="$surface1" backgroundColor="$surface1"
borderColor="$surface3" borderColor={showError ? '$statusCritical' : '$surface3'}
borderRadius="$rounded20" borderRadius="$rounded20"
borderWidth={1} borderWidth={1}
minHeight={shouldUseMinHeight ? INPUT_MIN_HEIGHT : undefined} minHeight={shouldUseMinHeight ? INPUT_MIN_HEIGHT : undefined}
...@@ -134,7 +125,6 @@ export function GenericImportForm({ ...@@ -134,7 +125,6 @@ export function GenericImportForm({
inputFontSize={INPUT_FONT_SIZE} inputFontSize={INPUT_FONT_SIZE}
inputMaxFontSizeMultiplier={INPUT_MAX_FONT_SIZE_MULTIPLIER} inputMaxFontSizeMultiplier={INPUT_MAX_FONT_SIZE_MULTIPLIER}
inputSuffix={inputSuffix} inputSuffix={inputSuffix}
layout={layout}
textAlign={textAlign} textAlign={textAlign}
textInputRef={textInputRef} textInputRef={textInputRef}
value={value} value={value}
...@@ -143,39 +133,21 @@ export function GenericImportForm({ ...@@ -143,39 +133,21 @@ export function GenericImportForm({
onFocus={handleFocus} onFocus={handleFocus}
onSubmitEditing={handleSubmit} onSubmitEditing={handleSubmit}
/> />
{!value && ( {!value && placeholderLabel && (
<Flex <Flex
grow centered
row bottom={shouldUseMinHeight ? undefined : 0}
alignItems="center" left="$spacing24"
gap="$spacing8"
position="absolute" position="absolute"
pt="$spacing16" py="$spacing16"
px="$spacing24" top={0}>
width="100%" <Text color="$neutral2" fontSize={INPUT_FONT_SIZE} pointerEvents="none">
onLayout={(event: LayoutChangeEvent): void => setLayout(event.nativeEvent.layout)}>
<Text
adjustsFontSizeToFit
$short={{ variant: 'body3' }}
color="$neutral2"
maxFontSizeMultiplier={INPUT_MAX_FONT_SIZE_MULTIPLIER}
numberOfLines={1}
style={styles.placeholderLabelStyle}
variant="subheading2">
{placeholderLabel} {placeholderLabel}
</Text> </Text>
</Flex> </Flex>
)} )}
{!value && !shouldUseMinHeight && ( {!value && !shouldUseMinHeight && (
<Flex <Flex bottom={0} justifyContent="center" position="absolute" right="$spacing12" top={0}>
row
alignItems="flex-end"
justifyContent="flex-end"
position="absolute"
pr="$spacing12"
pt="$spacing12"
right={0}
width="100%">
<PasteButton <PasteButton
afterClipboardReceived={afterPasteButtonPress} afterClipboardReceived={afterPasteButtonPress}
beforePress={beforePasteButtonPress} beforePress={beforePasteButtonPress}
...@@ -184,12 +156,8 @@ export function GenericImportForm({ ...@@ -184,12 +156,8 @@ export function GenericImportForm({
</Flex> </Flex>
)} )}
{!value && shouldUseMinHeight && ( {!value && shouldUseMinHeight && (
<Flex centered width="100%"> <Flex centered bottom={0} height={0} left={0} position="absolute" right={0}>
<Flex <Flex position="absolute">
$short={{ pt: SHORT_PASTE_BUTTON_TOP_PADDING }}
position="absolute"
pt={PASTE_BUTTON_TOP_PADDING}
top={0}>
<PasteButton <PasteButton
afterClipboardReceived={afterPasteButtonPress} afterClipboardReceived={afterPasteButtonPress}
beforePress={beforePasteButtonPress} beforePress={beforePasteButtonPress}
...@@ -200,7 +168,7 @@ export function GenericImportForm({ ...@@ -200,7 +168,7 @@ export function GenericImportForm({
)} )}
</Flex> </Flex>
<Flex> <Flex>
{errorMessage && value && (liveCheck || !focused) && ( {showError && (
<Flex centered row gap="$spacing12"> <Flex centered row gap="$spacing12">
<Text color="$statusCritical" variant="body3"> <Text color="$statusCritical" variant="body3">
{errorMessage} {errorMessage}
...@@ -212,9 +180,3 @@ export function GenericImportForm({ ...@@ -212,9 +180,3 @@ export function GenericImportForm({
</Trace> </Trace>
) )
} }
const styles = StyleSheet.create({
placeholderLabelStyle: {
flexShrink: 1,
},
})
import { TextInput as NativeTextInput } from 'react-native'
import { ColorTokens } from 'ui/src'
export interface InputWithSuffixProps {
alwaysShowInputSuffix?: boolean
autoCorrect: boolean
blurOnSubmit: boolean
inputAlignment: 'center' | 'flex-start'
value?: string
inputFontSize: number
inputMaxFontSizeMultiplier: number
inputSuffix?: string
inputSuffixColor?: ColorTokens
multiline?: boolean
textAlign?: 'left' | 'right' | 'center'
textInputRef: React.RefObject<NativeTextInput>
onBlur?: () => void
onFocus?: () => void
onChangeText?: (text: string) => void
onSubmitEditing?: () => void
}
import { useCallback, useRef, useState } from 'react'
import { LayoutChangeEvent } from 'react-native'
import { Flex } from 'ui/src'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { isIOS } from 'utilities/src/platform'
import { TextInput } from 'wallet/src/components/input/TextInput'
import { InputWithSuffixProps } from './InputWIthSuffixProps'
const EPS = 1
export default function InputWithSuffix({
alwaysShowInputSuffix = false,
value,
inputSuffix,
inputSuffixColor,
inputAlignment: inputAlignmentProp,
inputFontSize,
inputMaxFontSizeMultiplier,
multiline = true,
textAlign,
textInputRef,
...inputProps
}: InputWithSuffixProps): JSX.Element {
const textInputWidth = useRef<number>(0)
const [shouldWrapLine, setShouldWrapLine] = useState(false)
const measureMaxWidth = useCallback((e: LayoutChangeEvent) => {
textInputWidth.current = e.nativeEvent.layout.width
}, [])
const measureInputWidth = useCallback(
(e: LayoutChangeEvent) => {
if (multiline) {
const contentWidth = e.nativeEvent.layout.width
const maxContentWidth = textInputWidth.current
// Check if the input content doesn't fit in a single line
// (add EPS to avoid rounding errors)
setShouldWrapLine(contentWidth + EPS >= maxContentWidth)
}
},
[multiline]
)
const isInputEmpty = !value?.length
// On iOS use just the multiline prop to determine if the input should wrap
// On Android, wrap the input only if it's content doesn't fit in a single line
// and the input is multiline
const isMultiline = multiline && (isIOS || shouldWrapLine)
const fallbackTextInputAlignment = inputAlignmentProp === 'flex-start' ? 'left' : 'center'
const textInputAlignment = textAlign ?? fallbackTextInputAlignment
const suffix =
inputSuffix && (alwaysShowInputSuffix || (value && !value.includes(inputSuffix))) ? (
<TextInput
backgroundColor="$transparent"
color={inputSuffixColor ?? '$neutral2'}
editable={false}
fontSize={inputFontSize}
lineHeight={inputFontSize}
maxFontSizeMultiplier={inputMaxFontSizeMultiplier}
px="$none"
py="$none"
scrollEnabled={false}
textAlignVertical="bottom"
value={inputSuffix}
/>
) : null
return (
<Flex row alignItems="flex-end" justifyContent={inputAlignmentProp}>
{/*
Helper Flex to measure the max width of the input and switch to multiline if needed
(multiline input behavior on Android is weird and the input flickers when the width
of the TextInput component changes. As a workaround, we measure the max width of the input
and switch to multiline if the content doesn't fit in a single line)
*/}
<Flex row opacity={0} position="absolute" width="100%">
<Flex grow onLayout={measureMaxWidth} />
{suffix}
</Flex>
<TextInput
ref={textInputRef}
autoFocus
autoCapitalize="none"
backgroundColor="$transparent"
color="$neutral1"
flexShrink={1}
fontSize={inputFontSize}
lineHeight={inputFontSize}
maxFontSizeMultiplier={inputMaxFontSizeMultiplier}
multiline={isMultiline}
px="$none"
py="$none"
returnKeyType="done"
scrollEnabled={false}
spellCheck={false}
testID={ElementName.ImportAccountInput}
textAlign={isInputEmpty ? 'left' : textInputAlignment}
textAlignVertical={isInputEmpty ? 'center' : 'bottom'}
value={value}
onLayout={measureInputWidth}
{...inputProps}
/>
{suffix}
</Flex>
)
}
import { Flex } from 'ui/src'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { TextInput } from 'wallet/src/components/input/TextInput'
import { InputWithSuffixProps } from './InputWIthSuffixProps'
export default function InputWithSuffix({
alwaysShowInputSuffix = false,
value,
inputSuffix,
inputSuffixColor,
inputAlignment: inputAlignmentProp,
inputFontSize,
inputMaxFontSizeMultiplier,
multiline = true,
textAlign,
textInputRef,
...inputProps
}: InputWithSuffixProps): JSX.Element {
const isInputEmpty = !value?.length
const fallbackTextInputAlignment = inputAlignmentProp === 'flex-start' ? 'left' : 'center'
const textInputAlignment = textAlign ?? fallbackTextInputAlignment
return (
<Flex row alignItems="flex-end" justifyContent={inputAlignmentProp}>
<TextInput
ref={textInputRef}
autoFocus
autoCapitalize="none"
backgroundColor="$transparent"
color="$neutral1"
flexShrink={1}
fontSize={inputFontSize}
lineHeight={inputFontSize}
maxFontSizeMultiplier={inputMaxFontSizeMultiplier}
multiline={multiline}
px="$none"
py="$none"
returnKeyType="done"
scrollEnabled={false}
spellCheck={false}
testID={ElementName.ImportAccountInput}
textAlign={isInputEmpty ? 'left' : textInputAlignment}
textAlignVertical="bottom"
value={value}
{...inputProps}
/>
{inputSuffix && (alwaysShowInputSuffix || (value && !value.includes(inputSuffix))) ? (
<TextInput
backgroundColor="$transparent"
color={inputSuffixColor ?? '$neutral2'}
editable={false}
fontSize={inputFontSize}
lineHeight={inputFontSize}
maxFontSizeMultiplier={inputMaxFontSizeMultiplier}
multiline={multiline}
px="$none"
py="$none"
scrollEnabled={false}
textAlignVertical="bottom"
value={inputSuffix}
/>
) : null}
</Flex>
)
}
import { useCallback, useState } from 'react' import { NotImplementedError } from 'utilities/src/errors'
import { import { InputWithSuffixProps } from './InputWIthSuffixProps'
LayoutRectangle,
NativeSyntheticEvent,
TextInput as NativeTextInput,
TextInputContentSizeChangeEventData,
} from 'react-native'
import { ColorTokens, Flex, useSporeColors } from 'ui/src'
import { spacing } from 'ui/src/theme'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { isAndroid } from 'uniswap/src/utils/platform'
import { TextInput } from 'wallet/src/components/input/TextInput'
interface Props { export default function InputWithSuffix(_props: InputWithSuffixProps): JSX.Element {
alwaysShowInputSuffix?: boolean throw new NotImplementedError('InputWithSuffix component is not implemented')
autoCorrect: boolean
blurOnSubmit: boolean
inputAlignment: 'center' | 'flex-start'
value?: string
inputFontSize: number
inputMaxFontSizeMultiplier: number
inputSuffix?: string
inputSuffixColor?: ColorTokens
multiline?: boolean
textAlign?: 'left' | 'right' | 'center'
textInputRef: React.RefObject<NativeTextInput>
layout?: LayoutRectangle | null
onBlur?: () => void
onFocus?: () => void
onChangeText?: (text: string) => void
onSubmitEditing?: () => void
}
export default function InputWithSuffix(props: Props): JSX.Element {
return isAndroid ? (
<Flex width="100%">
<Inputs {...props} layerType="foreground" />
<Inputs {...props} layerType="background" />
</Flex>
) : (
<Inputs {...props} />
)
}
function Inputs({
alwaysShowInputSuffix = false,
value,
layout,
inputSuffix,
inputSuffixColor,
inputAlignment,
inputFontSize,
inputMaxFontSizeMultiplier,
multiline = true,
textAlign,
textInputRef,
layerType,
...inputProps
}: Props & { layerType?: 'foreground' | 'background' }): JSX.Element {
const colors = useSporeColors()
const [isMultiline, setIsMultiline] = useState(false)
const handleContentSizeChange = useCallback(
(e: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => {
if (multiline && textInputRef.current) {
setIsMultiline(Math.floor(e.nativeEvent.contentSize.height / inputFontSize) > 1)
}
},
[textInputRef, inputFontSize, multiline]
)
const isInputEmpty = !value?.length
const foregroundFallbackTextAlignment =
isMultiline || inputAlignment === 'flex-start' ? 'left' : 'center'
const foregroundTextAlignment = textAlign ?? foregroundFallbackTextAlignment
const fallbackBackgroundTextAlignment = inputAlignment === 'flex-start' ? 'left' : 'center'
const backgroundTextAlignment = textAlign ?? fallbackBackgroundTextAlignment
return (
<Flex
alignItems="flex-end"
flexDirection="row"
justifyContent={inputAlignment}
width="100%"
{...(layerType === 'foreground' ? { position: 'absolute', left: 0, bottom: 0 } : {})}
{...(layerType === 'background' ? { pointerEvents: 'none' } : {})}>
{layerType === 'foreground' ? (
<TextInput
ref={textInputRef}
autoFocus
backgroundColor="$transparent"
color="$neutral1"
fontSize={inputFontSize}
justifyContent="flex-start"
lineHeight={inputFontSize}
maxFontSizeMultiplier={inputMaxFontSizeMultiplier}
multiline={multiline}
px="$none"
py="$none"
scrollEnabled={false}
textAlign={isInputEmpty ? 'left' : foregroundTextAlignment}
textAlignVertical="bottom"
value={value}
width="100%"
onContentSizeChange={handleContentSizeChange}
{...inputProps}
/>
) : (
<TextInput
autoCapitalize="none"
backgroundColor="$transparent"
color="$neutral1"
fontSize={inputFontSize}
justifyContent="center"
lineHeight={inputFontSize}
maxFontSizeMultiplier={inputMaxFontSizeMultiplier}
multiline={multiline}
px="$none"
py="$none"
returnKeyType="done"
scrollEnabled={false}
selectionColor={colors.neutral1.val}
spellCheck={false}
testID={ElementName.ImportAccountInput}
textAlign={isInputEmpty ? 'left' : backgroundTextAlignment}
textAlignVertical="bottom"
value={value}
width={value ? 'auto' : (layout?.width || 0) + spacing.spacing8}
{...(layerType === 'background'
? { opacity: 0, editable: false }
: { ...inputProps, ref: textInputRef, autoFocus: true })}
/>
)}
{inputSuffix && (alwaysShowInputSuffix || (value && !value.includes(inputSuffix))) ? (
<TextInput
backgroundColor="$transparent"
color={inputSuffixColor ?? '$neutral2'}
editable={false}
fontSize={inputFontSize}
justifyContent="center"
lineHeight={inputFontSize}
maxFontSizeMultiplier={inputMaxFontSizeMultiplier}
multiline={multiline}
px="$none"
py="$none"
scrollEnabled={false}
textAlignVertical="bottom"
value={inputSuffix}
{...(layerType === 'foreground' ? { opacity: 0 } : {})}
/>
) : null}
</Flex>
)
} }
...@@ -45,7 +45,6 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` ...@@ -45,7 +45,6 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
"alignItems": "flex-end", "alignItems": "flex-end",
"flexDirection": "row", "flexDirection": "row",
"justifyContent": "center", "justifyContent": "center",
"width": "100%",
} }
} }
> >
...@@ -66,7 +65,7 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` ...@@ -66,7 +65,7 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
placeholderTextColor="#CECECE" placeholderTextColor="#CECECE"
returnKeyType="done" returnKeyType="done"
scrollEnabled={false} scrollEnabled={false}
selectionColor="#222222" selectionColor="#CECECE"
spellCheck={false} spellCheck={false}
style={ style={
{ {
...@@ -85,10 +84,10 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` ...@@ -85,10 +84,10 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
"borderTopRightRadius": 12, "borderTopRightRadius": 12,
"borderTopWidth": 1, "borderTopWidth": 1,
"color": "#222222", "color": "#222222",
"flexShrink": 1,
"fontFamily": "Basel-Book", "fontFamily": "Basel-Book",
"fontSize": 17, "fontSize": 17,
"height": "auto", "height": "auto",
"justifyContent": "center",
"lineHeight": 17, "lineHeight": 17,
"minWidth": 0, "minWidth": 0,
"paddingBottom": 0, "paddingBottom": 0,
...@@ -96,7 +95,6 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` ...@@ -96,7 +95,6 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
"paddingRight": 0, "paddingRight": 0,
"paddingTop": 0, "paddingTop": 0,
"textAlign": "left", "textAlign": "left",
"width": 8,
} }
} }
testID="import-account-input" testID="import-account-input"
...@@ -104,30 +102,26 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` ...@@ -104,30 +102,26 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
/> />
</View> </View>
<View <View
onLayout={[Function]}
style={ style={
{ {
"alignItems": "center", "alignItems": "center",
"flexDirection": "row", "flexDirection": "column",
"flexGrow": 1, "justifyContent": "center",
"gap": 8, "left": 24,
"paddingLeft": 24, "paddingBottom": 16,
"paddingRight": 24,
"paddingTop": 16, "paddingTop": 16,
"position": "absolute", "position": "absolute",
"width": "100%", "top": 0,
} }
} }
> >
<Text <Text
adjustsFontSizeToFit={true}
allowFontScaling={true} allowFontScaling={true}
maxFontSizeMultiplier={1.4} maxFontSizeMultiplier={1.4}
numberOfLines={1} pointerEvents="none"
style={ style={
{ {
"color": "#7D7D7D", "color": "#7D7D7D",
"flexShrink": 1,
"fontFamily": "Basel-Book", "fontFamily": "Basel-Book",
"fontSize": 17, "fontSize": 17,
"lineHeight": 24, "lineHeight": 24,
...@@ -142,9 +136,13 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` ...@@ -142,9 +136,13 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
style={ style={
{ {
"alignItems": "center", "alignItems": "center",
"bottom": 0,
"flexDirection": "column", "flexDirection": "column",
"height": 0,
"justifyContent": "center", "justifyContent": "center",
"width": "100%", "left": 0,
"position": "absolute",
"right": 0,
} }
} }
> >
...@@ -152,9 +150,7 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` ...@@ -152,9 +150,7 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
"paddingTop": 64,
"position": "absolute", "position": "absolute",
"top": 0,
} }
} }
> >
......
...@@ -4,8 +4,8 @@ import { StyleSheet } from 'react-native' ...@@ -4,8 +4,8 @@ import { StyleSheet } from 'react-native'
import { ColorTokens, Flex, FlexProps, Logos, SpaceTokens, Text, useSporeColors } from 'ui/src' import { ColorTokens, Flex, FlexProps, Logos, SpaceTokens, Text, useSporeColors } from 'ui/src'
import { TextVariantTokens, borderRadii, iconSizes, spacing } from 'ui/src/theme' import { TextVariantTokens, borderRadii, iconSizes, spacing } from 'ui/src/theme'
import { IAmount } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { IAmount } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { isIOS } from 'uniswap/src/utils/platform'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { isIOS } from 'utilities/src/platform'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext' import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
type ListPriceProps = FlexProps & { type ListPriceProps = FlexProps & {
......
...@@ -5,7 +5,7 @@ import { FadeIn, FadeOut } from 'react-native-reanimated' ...@@ -5,7 +5,7 @@ import { FadeIn, FadeOut } from 'react-native-reanimated'
import { SHORT_SCREEN_HEADER_HEIGHT_RATIO, Screen } from 'src/components/layout/Screen' import { SHORT_SCREEN_HEADER_HEIGHT_RATIO, Screen } from 'src/components/layout/Screen'
import { AnimatedFlex, Flex, SpaceTokens, Text, useDeviceInsets, useMedia } from 'ui/src' import { AnimatedFlex, Flex, SpaceTokens, Text, useDeviceInsets, useMedia } from 'ui/src'
import { fonts } from 'ui/src/theme' import { fonts } from 'ui/src/theme'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'utilities/src/platform'
type OnboardingScreenProps = { type OnboardingScreenProps = {
subtitle?: string subtitle?: string
......
...@@ -6,7 +6,7 @@ import { FadeIn, FadeOut } from 'react-native-reanimated' ...@@ -6,7 +6,7 @@ import { FadeIn, FadeOut } from 'react-native-reanimated'
import { Screen } from 'src/components/layout/Screen' import { Screen } from 'src/components/layout/Screen'
import { AnimatedFlex, Flex, SpaceTokens, Text, flexStyles, useMedia, useSporeColors } from 'ui/src' import { AnimatedFlex, Flex, SpaceTokens, Text, flexStyles, useMedia, useSporeColors } from 'ui/src'
import { opacify, spacing } from 'ui/src/theme' import { opacify, spacing } from 'ui/src/theme'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'utilities/src/platform'
import { useKeyboardLayout } from 'wallet/src/utils/useKeyboardLayout' import { useKeyboardLayout } from 'wallet/src/utils/useKeyboardLayout'
type OnboardingScreenProps = { type OnboardingScreenProps = {
......
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { OriginApplication } from '@uniswap/analytics' import { OriginApplication } from '@uniswap/analytics'
import DeviceInfo from 'react-native-device-info' import DeviceInfo, { getDeviceId } from 'react-native-device-info'
import { selectAllowAnalytics } from 'src/features/telemetry/selectors' import { selectAllowAnalytics } from 'src/features/telemetry/selectors'
import { call, delay, fork, select, takeEvery } from 'typed-redux-saga' import { call, delay, fork, select, takeEvery } from 'typed-redux-saga'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
...@@ -21,7 +21,9 @@ export function* telemetrySaga() { ...@@ -21,7 +21,9 @@ export function* telemetrySaga() {
originOverride: uniswapUrls.apiOrigin, originOverride: uniswapUrls.apiOrigin,
appBuild: DeviceInfo.getBundleId(), appBuild: DeviceInfo.getBundleId(),
}), }),
allowAnalytics allowAnalytics,
undefined,
async () => getDeviceId()
) )
yield* fork(watchTransactionEvents) yield* fork(watchTransactionEvents)
} }
......
...@@ -35,8 +35,8 @@ import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks' ...@@ -35,8 +35,8 @@ import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks'
import { ProfileMetadata } from 'uniswap/src/features/unitags/types' import { ProfileMetadata } from 'uniswap/src/features/unitags/types'
import { ChainId } from 'uniswap/src/types/chains' import { ChainId } from 'uniswap/src/types/chains'
import { MobileScreens, UnitagScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens, UnitagScreens } from 'uniswap/src/types/screens/mobile'
import { isIOS } from 'uniswap/src/utils/platform'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { isIOS } from 'utilities/src/platform'
import { normalizeTwitterUsername } from 'utilities/src/primitives/string' import { normalizeTwitterUsername } from 'utilities/src/primitives/string'
import { DisplayNameText } from 'wallet/src/components/accounts/DisplayNameText' import { DisplayNameText } from 'wallet/src/components/accounts/DisplayNameText'
import { TextInput } from 'wallet/src/components/input/TextInput' import { TextInput } from 'wallet/src/components/input/TextInput'
......
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
import { NativeModules } from 'react-native' import { NativeModules } from 'react-native'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
const { RNWalletConnect } = NativeModules const { RNWalletConnect } = NativeModules
......
import { getOneSignalPushToken } from 'src/features/notifications/Onesignal' import { getOneSignalPushToken } from 'src/features/notifications/Onesignal'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import { isAndroid } from 'uniswap/src/utils/platform'
import { isJestRun } from 'utilities/src/environment' import { isJestRun } from 'utilities/src/environment'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { isAndroid } from 'utilities/src/platform'
const WC_HOSTED_PUSH_SERVER_URL = `https://echo.walletconnect.com/${config.walletConnectProjectId}` const WC_HOSTED_PUSH_SERVER_URL = `https://echo.walletconnect.com/${config.walletConnectProjectId}`
......
...@@ -5,7 +5,7 @@ import { MobileEventName } from 'uniswap/src/features/telemetry/constants' ...@@ -5,7 +5,7 @@ import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { CurrencyId } from 'uniswap/src/types/currency' import { CurrencyId } from 'uniswap/src/types/currency'
import { WidgetEvent } from 'uniswap/src/types/widgets' import { WidgetEvent } from 'uniswap/src/types/widgets'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { analytics } from 'utilities/src/telemetry/analytics/analytics' import { analytics } from 'utilities/src/telemetry/analytics/analytics'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils' import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
......
/* eslint-disable @typescript-eslint/no-unused-expressions */ /* eslint-disable @typescript-eslint/no-unused-expressions */
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
// TODO: [MOB-247] remove polyfill once Hermes support it // TODO: [MOB-247] remove polyfill once Hermes support it
// https://github.com/facebook/hermes/issues/23 // https://github.com/facebook/hermes/issues/23
......
...@@ -4,7 +4,7 @@ import { Carousel } from 'src/components/carousel/Carousel' ...@@ -4,7 +4,7 @@ import { Carousel } from 'src/components/carousel/Carousel'
import { educationContent } from 'src/components/education' import { educationContent } from 'src/components/education'
import { Screen } from 'src/components/layout/Screen' import { Screen } from 'src/components/layout/Screen'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'utilities/src/platform'
export function EducationScreen({ export function EducationScreen({
route: { route: {
......
...@@ -18,7 +18,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace' ...@@ -18,7 +18,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName, ElementNameType } from 'uniswap/src/features/telemetry/constants' import { ElementName, ElementNameType } from 'uniswap/src/features/telemetry/constants'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { OnboardingScreens } from 'uniswap/src/types/screens/mobile'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { import {
PendingAccountActions, PendingAccountActions,
pendingAccountActions, pendingAccountActions,
......
...@@ -32,7 +32,7 @@ import { ...@@ -32,7 +32,7 @@ import {
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'utilities/src/platform'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard' import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { isError } from 'wallet/src/data/utils' import { isError } from 'wallet/src/data/utils'
import { NFTViewer } from 'wallet/src/features/images/NFTViewer' import { NFTViewer } from 'wallet/src/features/images/NFTViewer'
......
...@@ -38,7 +38,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace' ...@@ -38,7 +38,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { ChainId } from 'uniswap/src/types/chains' import { ChainId } from 'uniswap/src/types/chains'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isAndroid, isIOS } from 'uniswap/src/utils/platform' import { isAndroid, isIOS } from 'utilities/src/platform'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard' import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { NetworkLogo } from 'wallet/src/components/CurrencyLogo/NetworkLogo' import { NetworkLogo } from 'wallet/src/components/CurrencyLogo/NetworkLogo'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
......
...@@ -23,7 +23,7 @@ import { ElementName } from 'uniswap/src/features/telemetry/constants' ...@@ -23,7 +23,7 @@ import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { ImportType } from 'uniswap/src/types/onboarding' import { ImportType } from 'uniswap/src/types/onboarding'
import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile'
import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { useAsyncData } from 'utilities/src/react/hooks' import { useAsyncData } from 'utilities/src/react/hooks'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext' import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
import { BackupType } from 'wallet/src/features/wallet/accounts/types' import { BackupType } from 'wallet/src/features/wallet/accounts/types'
......
...@@ -16,7 +16,7 @@ import { ElementName } from 'uniswap/src/features/telemetry/constants' ...@@ -16,7 +16,7 @@ import { ElementName } from 'uniswap/src/features/telemetry/constants'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n/i18n'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { OnboardingScreens } from 'uniswap/src/types/screens/mobile'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'utilities/src/platform'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext' import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
import { useNativeAccountExists } from 'wallet/src/features/wallet/hooks' import { useNativeAccountExists } from 'wallet/src/features/wallet/hooks'
import { openSettings } from 'wallet/src/utils/linking' import { openSettings } from 'wallet/src/utils/linking'
......
import {
Easing,
EntryExitAnimationFunction,
StyleProps,
withDelay,
withSequence,
withTiming,
} from 'react-native-reanimated'
// Because the animation spec is denominated in frames, but Reanimated only works with milliseconds, this object stores all the frame values as their millisecond value equivalent. The conversion assumes 60fps, using a formula of (1000 / 60) * frames. This lets us keep the code readable according to frame numbers without recomputing these values every time.
const FPMS = {
29: 483.33,
60: 1000,
130: 2166.67,
165: 2750,
179: 2983.33,
180: 3000,
228: 3800,
241: 4016.67,
258: 4300,
360: 6000,
}
// 1. QR slide up animation
// Moves QR code box from bottom to top and fades it in
// - Moves QR from y:-64 to y:0 from frame 29 to frame 60
// - Fades in QR from 0 to 1 opacity from frame 29 to frame 60
// - Triggers etching video as a callback when complete
export const qrSlideUpAndFadeInConfig = {
opacity: {
startValue: 0,
endValue: 1,
delay: FPMS[29],
duration: FPMS[60] - FPMS[29],
},
translateY: {
startValue: 64,
endValue: 0,
delay: FPMS[29],
duration: FPMS[60] - FPMS[29],
},
}
// 2. QR scale in animation
// Scales QR code box from 80% to 100% scale from frame 130 to frame 179
const qrScaleInConfig = {
scale: { startValue: 0.8, endValue: 1, delay: FPMS[130], duration: FPMS[179] - FPMS[130] },
}
export const qrScaleIn: EntryExitAnimationFunction = () => {
'worklet'
const animations: StyleProps = {
transform: [
{
scale: withDelay(
qrScaleInConfig.scale.delay,
withTiming(qrScaleInConfig.scale.endValue, {
duration: qrScaleInConfig.scale.duration,
easing: Easing.bezierFn(0.66, 0.0, 0.34, 1.0),
})
),
},
],
}
const initialValues: StyleProps = {
transform: [{ scale: qrScaleInConfig.scale.startValue }],
}
return {
initialValues,
animations,
}
}
// 3. QR inner glow animation
// Animates inner glowing circle of QR code box to slowly fade in
// NOTE: for now, it animates the inner glow's blur amount, as a proxy for its size because to animate its size makes it hard to keep it centered in the canvas. In order to do so, we would need to run the computing of the inner glow's size on the UI thread instead of the JS thread in order to transform the x and y of the Canvas Group the Oval and Blur are inside of by the correct amount as it animates (size / 2).
// NOTE 2: the actual animation had to be kept coupled to the component because it would become too unwieldy to abstract it here.
export const qrInnerBlurConfig = {
opacity: {
startValue: 0,
endValue: 1,
delay: 0,
duration: FPMS[360] - FPMS[180],
},
size: {
startValue: 100,
endValue: 25,
delay: 0,
duration: FPMS[360] - FPMS[180],
},
}
// 4. Flash wipe animation
// Covers the etching video with a flash wipe animation
// - Fades in flash wipe from 80% to 100% scale from frame 130 to frame 179
// - Fades in flash wipe from 0% opacity to 100% opacity from frame 165 to frame 180
// - Fades out flash wipe from 100% to 0% opacity from frame 180 to frame 228
export const flashWipeConfig = {
scale: {
startValue: 0.8,
endValue: 1,
delay: FPMS[130],
duration: FPMS[179] - FPMS[130],
},
opacityIn: {
startValue: 0,
endValue: 1,
delay: FPMS[165],
duration: FPMS[180] - FPMS[165],
},
opacityOut: {
startValue: 1,
endValue: 0,
delay: 0,
duration: FPMS[228] - FPMS[180],
},
}
export const flashWipeAnimation: EntryExitAnimationFunction = () => {
'worklet'
const animations: StyleProps = {
opacity: withSequence(
withDelay(
flashWipeConfig.opacityIn.delay,
withTiming(flashWipeConfig.opacityIn.endValue, {
duration: flashWipeConfig.opacityIn.duration,
easing: Easing.bezierFn(0.4, 0.0, 0.68, 0.06),
})
),
withDelay(
flashWipeConfig.opacityOut.delay,
withTiming(flashWipeConfig.opacityOut.endValue, {
duration: flashWipeConfig.opacityOut.duration,
easing: Easing.bezierFn(0.66, 0.0, 0.34, 1.0),
})
)
),
transform: [
{
scale: withDelay(
flashWipeConfig.scale.delay,
withTiming(flashWipeConfig.scale.endValue, { duration: flashWipeConfig.scale.duration })
),
},
],
}
const initialValues: StyleProps = {
opacity: flashWipeConfig.opacityIn.startValue,
transform: [{ scale: flashWipeConfig.scale.startValue }],
}
return {
initialValues,
animations,
}
}
// 5. Video fade out
// Fades out video after flash wipe has covered etching video
export const videoFadeOut: EntryExitAnimationFunction = () => {
'worklet'
const animations = {
opacity: withDelay(
flashWipeConfig.opacityIn.delay + flashWipeConfig.opacityIn.duration,
withTiming(0, { duration: 1, easing: Easing.bezierFn(0.4, 0.0, 0.68, 0.06) })
),
}
const initialValues = {
opacity: 1,
}
return {
initialValues,
animations,
}
}
// 6. QR top glow fade in
// Fades in glow on top of real QR code and unicon after flash wipe
export const realQrTopGlowFadeIn: EntryExitAnimationFunction = () => {
'worklet'
const animations = {
opacity: withDelay(
flashWipeConfig.opacityIn.delay,
withTiming(flashWipeConfig.opacityIn.endValue, {
duration: flashWipeConfig.opacityIn.duration,
easing: Easing.bezierFn(0.4, 0.0, 0.68, 0.06),
})
),
}
const initialValues = {
opacity: 0,
}
return {
initialValues,
animations,
}
}
// 7. Real QR code fade in
// Show the real QR code and Unicon after the flash wipe
export const realQrFadeIn: EntryExitAnimationFunction = () => {
'worklet'
const animations = {
opacity: withDelay(
flashWipeConfig.opacityIn.delay + flashWipeConfig.opacityIn.duration,
withTiming(1, { duration: 1, easing: Easing.bezierFn(0.4, 0.0, 0.68, 0.06) })
),
}
const initialValues = {
opacity: 0,
}
return {
initialValues,
animations,
}
}
// 8. Text slide up and fade in
const textSlideUpAndFadeInConfig = {
opacityIn: {
startValue: 0,
endValue: 1,
delay: FPMS[241],
duration: FPMS[241],
},
opacityOut: {
startValue: 0,
endValue: 1,
delay: FPMS[241],
duration: FPMS[258] - FPMS[241],
},
translateY: {
startValue: 32,
endValue: 0,
delay: FPMS[228],
duration: FPMS[258] - FPMS[228],
},
}
export const textSlideUpAtEnd: EntryExitAnimationFunction = () => {
'worklet'
const animations: StyleProps = {
opacity: withDelay(
textSlideUpAndFadeInConfig.opacityOut.delay,
withTiming(textSlideUpAndFadeInConfig.opacityOut.endValue, {
duration: textSlideUpAndFadeInConfig.opacityOut.duration,
easing: Easing.bezierFn(0.66, 0.0, 0.34, 1.0),
})
),
transform: [
{
translateY: withDelay(
textSlideUpAndFadeInConfig.translateY.delay,
withTiming(textSlideUpAndFadeInConfig.translateY.endValue, {
duration: textSlideUpAndFadeInConfig.translateY.duration,
easing: Easing.bezierFn(0.66, 0.0, 0.34, 1.0),
})
),
},
],
}
const initialValues: StyleProps = {
transform: [{ translateY: textSlideUpAndFadeInConfig.translateY.startValue }],
opacity: textSlideUpAndFadeInConfig.opacityOut.startValue,
}
return {
initialValues,
animations,
}
}
// 9. Button slide up and fade in
export const letsGoButtonFadeIn: EntryExitAnimationFunction = () => {
'worklet'
const animations = {
opacity: withDelay(
textSlideUpAndFadeInConfig.opacityOut.delay,
withTiming(textSlideUpAndFadeInConfig.opacityOut.endValue, {
duration: textSlideUpAndFadeInConfig.opacityOut.duration,
})
),
}
const initialValues = {
opacity: textSlideUpAndFadeInConfig.opacityOut.startValue,
}
return {
initialValues,
animations,
}
}
// 10. Whole QR container (showing real QR and Unicon now) slides up
const qrSlideUpAtEndConfig = {
translateY: {
startValue: 0,
endValue: -64,
delay: FPMS[228],
duration: FPMS[258] - FPMS[228],
},
}
export const qrSlideUpAtEnd: EntryExitAnimationFunction = () => {
'worklet'
const animations: StyleProps = {
transform: [
{
translateY: withDelay(
qrSlideUpAtEndConfig.translateY.delay,
withTiming(qrSlideUpAtEndConfig.translateY.endValue, {
duration: qrSlideUpAtEndConfig.translateY.duration,
easing: Easing.bezierFn(0.66, 0.0, 0.34, 1.0),
})
),
},
],
}
const initialValues: StyleProps = {
transform: [{ translateY: qrSlideUpAndFadeInConfig.translateY.startValue }],
}
return {
initialValues,
animations,
}
}
...@@ -25,7 +25,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace' ...@@ -25,7 +25,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { ImportType } from 'uniswap/src/types/onboarding' import { ImportType } from 'uniswap/src/types/onboarding'
import { OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { OnboardingScreens } from 'uniswap/src/types/screens/mobile'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'utilities/src/platform'
import { opacify } from 'wallet/src/utils/colors' import { opacify } from 'wallet/src/utils/colors'
import { openSettings } from 'wallet/src/utils/linking' import { openSettings } from 'wallet/src/utils/linking'
......
...@@ -20,7 +20,7 @@ import { ...@@ -20,7 +20,7 @@ import {
setRequiredForTransactions, setRequiredForTransactions,
} from 'src/features/biometrics/slice' } from 'src/features/biometrics/slice'
import { Flex, Text, TouchableArea } from 'ui/src' import { Flex, Text, TouchableArea } from 'ui/src'
import { isAndroid, isIOS } from 'uniswap/src/utils/platform' import { isAndroid, isIOS } from 'utilities/src/platform'
import { Switch } from 'wallet/src/components/buttons/Switch' import { Switch } from 'wallet/src/components/buttons/Switch'
import { openSettings } from 'wallet/src/utils/linking' import { openSettings } from 'wallet/src/utils/linking'
......
...@@ -62,7 +62,7 @@ import { ModalName } from 'uniswap/src/features/telemetry/constants' ...@@ -62,7 +62,7 @@ import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile'
import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'utilities/src/platform'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { useTimeout } from 'utilities/src/time/timing' import { useTimeout } from 'utilities/src/time/timing'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
......
...@@ -17,7 +17,7 @@ import { Button, Flex, Text } from 'ui/src' ...@@ -17,7 +17,7 @@ import { Button, Flex, Text } from 'ui/src'
import { PenLine } from 'ui/src/components/icons' import { PenLine } from 'ui/src/components/icons'
import { fonts } from 'ui/src/theme' import { fonts } from 'ui/src/theme'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'utilities/src/platform'
import { TextInput } from 'wallet/src/components/input/TextInput' import { TextInput } from 'wallet/src/components/input/TextInput'
import { NICKNAME_MAX_LENGTH } from 'wallet/src/constants/accounts' import { NICKNAME_MAX_LENGTH } from 'wallet/src/constants/accounts'
import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks' import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks'
......
...@@ -329,6 +329,7 @@ function TokenDetails({ ...@@ -329,6 +329,7 @@ function TokenDetails({
style={{ marginBottom: insets.bottom }}> style={{ marginBottom: insets.bottom }}>
<TokenDetailsActionButtons <TokenDetailsActionButtons
tokenColor={tokenColor} tokenColor={tokenColor}
userHasBalance={Boolean(currentChainBalance)}
onPressBuy={(): void => onPressSwap(CurrencyField.OUTPUT)} onPressBuy={(): void => onPressSwap(CurrencyField.OUTPUT)}
onPressSell={(): void => onPressSwap(CurrencyField.INPUT)} onPressSell={(): void => onPressSwap(CurrencyField.INPUT)}
/> />
......
/* eslint-disable @typescript-eslint/no-explicit-any */
import { NavigationContainer } from '@react-navigation/native' import { NavigationContainer } from '@react-navigation/native'
import type { EnhancedStore, PreloadedState } from '@reduxjs/toolkit' import type { EnhancedStore, PreloadedState } from '@reduxjs/toolkit'
import { configureStore } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit'
...@@ -78,12 +77,9 @@ type ExtendedRenderHookOptions<P> = RenderHookOptions<P> & { ...@@ -78,12 +77,9 @@ type ExtendedRenderHookOptions<P> = RenderHookOptions<P> & {
store?: AppStore store?: AppStore
} }
type RenderHookWithProvidersResult<R, P extends any[] | undefined = undefined> = Omit< type RenderHookWithProvidersResult<R, P = undefined> = Omit<RenderHookResult<R, P>, 'rerender'> & {
RenderHookResult<R, P>,
'rerender'
> & {
store: EnhancedStore store: EnhancedStore
rerender: P extends any[] ? (args: P) => void : () => void rerender: (args?: P) => void
} }
// Don't require hookOptions if hook doesn't take any arguments // Don't require hookOptions if hook doesn't take any arguments
...@@ -93,8 +89,8 @@ export function renderHookWithProviders<R>( ...@@ -93,8 +89,8 @@ export function renderHookWithProviders<R>(
): RenderHookWithProvidersResult<R> ): RenderHookWithProvidersResult<R>
// Require hookOptions if hook takes arguments // Require hookOptions if hook takes arguments
export function renderHookWithProviders<R, P extends any[]>( export function renderHookWithProviders<R, P>(
hook: (...args: P) => R, hook: (args: P) => R,
hookOptions: ExtendedRenderHookOptions<P> hookOptions: ExtendedRenderHookOptions<P>
): RenderHookWithProvidersResult<R, P> ): RenderHookWithProvidersResult<R, P>
...@@ -105,8 +101,8 @@ export function renderHookWithProviders<R, P extends any[]>( ...@@ -105,8 +101,8 @@ export function renderHookWithProviders<R, P extends any[]>(
* @param preloadedState and store * @param preloadedState and store
* @returns `hook` wrapped with providers * @returns `hook` wrapped with providers
*/ */
export function renderHookWithProviders<P extends any[], R>( export function renderHookWithProviders<P, R>(
hook: (...args: P) => R, hook: (args: P) => R,
hookOptions?: ExtendedRenderHookOptions<P> hookOptions?: ExtendedRenderHookOptions<P>
): RenderHookWithProvidersResult<R, P> { ): RenderHookWithProvidersResult<R, P> {
const { const {
...@@ -140,12 +136,12 @@ export function renderHookWithProviders<P extends any[], R>( ...@@ -140,12 +136,12 @@ export function renderHookWithProviders<P extends any[], R>(
...(renderOptions as RenderHookOptions<P>), ...(renderOptions as RenderHookOptions<P>),
} }
const { rerender, ...rest } = RNRenderHook<R, P>((args: P) => hook(...(args ?? [])), options) const { rerender, ...rest } = RNRenderHook<R, P>((args: P) => hook(args), options)
// Return an object with the store and all of RTL's query functions // Return an object with the store and all of RTL's query functions
return { return {
store, store,
rerender: rerender as P extends any[] ? (args: P) => void : () => void, rerender: rerender as (args?: P) => void,
...rest, ...rest,
} }
} }
...@@ -72,7 +72,7 @@ module.exports = { ...@@ -72,7 +72,7 @@ module.exports = {
message: 'Default import from zustand is deprecated. Import `{ create }` instead.', message: 'Default import from zustand is deprecated. Import `{ create }` instead.',
}, },
{ {
name: 'uniswap/src/utils/platform', name: 'utilities/src/platform',
importNames: ['isIOS', 'isAndroid'], importNames: ['isIOS', 'isAndroid'],
message: message:
'Importing isIOS and isAndroid from platform is not allowed. Use isWebIOS and isWebAndroid instead.', 'Importing isIOS and isAndroid from platform is not allowed. Use isWebIOS and isWebAndroid instead.',
...@@ -82,6 +82,11 @@ module.exports = { ...@@ -82,6 +82,11 @@ module.exports = {
importNames: ['useChainId', 'useAccount'], importNames: ['useChainId', 'useAccount'],
message: 'Import properly typed account data from `hooks/useAccount` instead.', message: 'Import properly typed account data from `hooks/useAccount` instead.',
}, },
{
name: 'wagmi',
importNames: ['useConnect'],
message: 'Import wrapped useConnect util from `hooks/useConnect` instead.',
},
], ],
}, },
], ],
......
...@@ -292,15 +292,7 @@ describe('UniswapX v1', () => { ...@@ -292,15 +292,7 @@ describe('UniswapX v1', () => {
submitUniswapXOrder() submitUniswapXOrder()
cy.get(getTestSelector('confirmation-close-icon')).click() cy.get(getTestSelector('confirmation-close-icon')).click()
cy.intercept(/(?:interface|beta).gateway.uniswap.org\/v1\/graphql/, (req) => { cy.interceptGraphqlOperation('PortfolioBalancesWeb', 'mini-portfolio/tokens.json')
if (req.body.operationName === 'PortfolioBalancesWeb') {
req.alias = 'PortfolioBalancesWeb'
// Reply with a fixture to speed up test
req.reply({ fixture: 'mini-portfolio/tokens.json' })
} else {
req.continue()
}
})
// Expect balances to refetch after filling // Expect balances to refetch after filling
cy.wait('@orderStatusOpen') cy.wait('@orderStatusOpen')
......
...@@ -287,15 +287,7 @@ describe('UniswapX v2', () => { ...@@ -287,15 +287,7 @@ describe('UniswapX v2', () => {
submitUniswapXOrder() submitUniswapXOrder()
cy.get(getTestSelector('confirmation-close-icon')).click() cy.get(getTestSelector('confirmation-close-icon')).click()
cy.intercept(/(?:interface|beta).gateway.uniswap.org\/v1\/graphql/, (req) => { cy.interceptGraphqlOperation('PortfolioBalancesWeb', 'mini-portfolio/tokens.json')
if (req.body.operationName === 'PortfolioBalancesWeb') {
req.alias = 'PortfolioBalancesWeb'
// Reply with a fixture to speed up test
req.reply({ fixture: 'mini-portfolio/tokens.json' })
} else {
req.continue()
}
})
// Expect balances to refetch after filling // Expect balances to refetch after filling
cy.wait('@orderStatusOpen') cy.wait('@orderStatusOpen')
......
...@@ -80,9 +80,10 @@ describe('Token details', () => { ...@@ -80,9 +80,10 @@ describe('Token details', () => {
cy.contains(shortenAddress('0x1eFBB78C8b917f67986BcE54cE575069c0143681')).should('exist') cy.contains(shortenAddress('0x1eFBB78C8b917f67986BcE54cE575069c0143681')).should('exist')
// Warning label should show if relevant ([spec](https://www.notion.so/3f7fce6f93694be08a94a6984d50298e)) // Warning label should show if relevant ([spec](https://www.notion.so/3f7fce6f93694be08a94a6984d50298e))
cy.get('[data-cy="token-safety-message"]') cy.get('[data-cy="token-safety-message"]').contains(/Warning/)
.should('include.text', 'Warning') cy.get('[data-cy="token-safety-description"]').contains(
.and('include.text', "This token isn't traded on leading U.S. centralized exchanges") /This token isn’t traded on leading U.S. centralized exchanges or frequently swapped on Uniswap./
)
}) })
describe('swapping', () => { describe('swapping', () => {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -110,23 +110,23 @@ export default function AuthenticatedHeader({ account, openSettings }: { account ...@@ -110,23 +110,23 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const isUnclaimed = useUserHasAvailableClaim(account) const isUnclaimed = useUserHasAvailableClaim(account)
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM) const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
const [accountDrawerOpen, toggleAccountDrawer] = useAccountDrawer() const accountDrawer = useAccountDrawer()
const navigateToProfile = useCallback(() => { const navigateToProfile = useCallback(() => {
toggleAccountDrawer() accountDrawer.close()
resetSellAssets() resetSellAssets()
setSellPageState(ProfilePageStateType.VIEWING) setSellPageState(ProfilePageStateType.VIEWING)
clearCollectionFilters() clearCollectionFilters()
navigate('/nfts/profile') navigate('/nfts/profile')
closeModal() closeModal()
}, [clearCollectionFilters, closeModal, navigate, resetSellAssets, setSellPageState, toggleAccountDrawer]) }, [clearCollectionFilters, closeModal, navigate, resetSellAssets, setSellPageState, accountDrawer])
const openFiatOnrampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP) const openFiatOnrampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP)
const openFoRModalWithAnalytics = useCallback(() => { const openFoRModalWithAnalytics = useCallback(() => {
toggleAccountDrawer() accountDrawer.close()
sendAnalyticsEvent(InterfaceEventName.FIAT_ONRAMP_WIDGET_OPENED) sendAnalyticsEvent(InterfaceEventName.FIAT_ONRAMP_WIDGET_OPENED)
openFiatOnrampModal() openFiatOnrampModal()
}, [openFiatOnrampModal, toggleAccountDrawer]) }, [openFiatOnrampModal, accountDrawer])
const [shouldCheck, setShouldCheck] = useState(false) const [shouldCheck, setShouldCheck] = useState(false)
const { const {
...@@ -147,7 +147,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account ...@@ -147,7 +147,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
error || (!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) || fiatOnrampAvailabilityLoading error || (!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) || fiatOnrampAvailabilityLoading
) )
const { data: portfolioBalances } = useTokenBalancesQuery({ cacheOnly: !accountDrawerOpen }) const { data: portfolioBalances } = useTokenBalancesQuery({ cacheOnly: !accountDrawer.isOpen })
const portfolio = portfolioBalances?.portfolios?.[0] const portfolio = portfolioBalances?.portfolios?.[0]
const totalBalance = portfolio?.tokensTotalDenominatedValue?.value const totalBalance = portfolio?.tokensTotalDenominatedValue?.value
const absoluteChange = portfolio?.tokensTotalDenominatedValueChange?.absolute?.value const absoluteChange = portfolio?.tokensTotalDenominatedValueChange?.absolute?.value
......
...@@ -47,7 +47,7 @@ function useCancelLimitsDialogContent( ...@@ -47,7 +47,7 @@ function useCancelLimitsDialogContent(
<Plural <Plural
value={orders.length} value={orders.length}
one={t('common.limit.cancel')} one={t('common.limit.cancel')}
other={t(`common.limit.cancel.amount`, { amount: orders.length })} other={t(`common.limit.cancel.amount`, { count: orders.length })}
/> />
), ),
icon: <Slash />, icon: <Slash />,
...@@ -143,7 +143,7 @@ export function CancelLimitsDialog( ...@@ -143,7 +143,7 @@ export function CancelLimitsDialog(
} }
buttonsConfig={{ buttonsConfig={{
left: { left: {
title: <Trans i18nKey="common.nevermind" />, title: <Trans i18nKey="common.neverMind" />,
onClick: onCancel, onClick: onCancel,
textColor: 'neutral1', textColor: 'neutral1',
}, },
......
...@@ -10,7 +10,7 @@ import { ThemedText } from 'theme/components' ...@@ -10,7 +10,7 @@ import { ThemedText } from 'theme/components'
import { OpenLimitOrdersButton } from 'components/AccountDrawer/MiniPortfolio/Limits/OpenLimitOrdersButton' import { OpenLimitOrdersButton } from 'components/AccountDrawer/MiniPortfolio/Limits/OpenLimitOrdersButton'
import { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow' import { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
import { useToggleAccountDrawer } from '../hooks' import { useAccountDrawer } from '../hooks'
import { ActivityRow } from './ActivityRow' import { ActivityRow } from './ActivityRow'
import { useAllActivities } from './hooks' import { useAllActivities } from './hooks'
import { createGroups } from './utils' import { createGroups } from './utils'
...@@ -26,7 +26,7 @@ const OpenLimitOrdersActivityButton = styled(OpenLimitOrdersButton)` ...@@ -26,7 +26,7 @@ const OpenLimitOrdersActivityButton = styled(OpenLimitOrdersButton)`
` `
export function ActivityTab({ account }: { account: string }) { export function ActivityTab({ account }: { account: string }) {
const toggleAccountDrawer = useToggleAccountDrawer() const accountDrawer = useAccountDrawer()
const setMenu = useUpdateAtom(miniPortfolioMenuStateAtom) const setMenu = useUpdateAtom(miniPortfolioMenuStateAtom)
const { activities, loading } = useAllActivities(account) const { activities, loading } = useAllActivities(account)
...@@ -46,7 +46,7 @@ export function ActivityTab({ account }: { account: string }) { ...@@ -46,7 +46,7 @@ export function ActivityTab({ account }: { account: string }) {
return ( return (
<> <>
<OpenLimitOrdersActivityButton openLimitsMenu={() => setMenu(MenuState.LIMITS)} account={account} /> <OpenLimitOrdersActivityButton openLimitsMenu={() => setMenu(MenuState.LIMITS)} account={account} />
<EmptyWalletModule type="activity" onNavigateClick={toggleAccountDrawer} /> <EmptyWalletModule type="activity" onNavigateClick={accountDrawer.close} />
</> </>
) )
} }
......
...@@ -2,7 +2,7 @@ import { useOpenLimitOrders } from 'components/AccountDrawer/MiniPortfolio/Activ ...@@ -2,7 +2,7 @@ import { useOpenLimitOrders } from 'components/AccountDrawer/MiniPortfolio/Activ
import Column from 'components/Column' import Column from 'components/Column'
import { TimeForwardIcon } from 'components/Icons/TimeForward' import { TimeForwardIcon } from 'components/Icons/TimeForward'
import Row from 'components/Row' import Row from 'components/Row'
import { Plural, t, Trans } from 'i18n' import { Plural, Trans, t } from 'i18n'
import { ChevronRight } from 'react-feather' import { ChevronRight } from 'react-feather'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
import { ClickableStyle, ThemedText } from 'theme/components' import { ClickableStyle, ThemedText } from 'theme/components'
......
...@@ -12,7 +12,7 @@ import { ThemedText } from 'theme/components' ...@@ -12,7 +12,7 @@ import { ThemedText } from 'theme/components'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { useToggleAccountDrawer } from '../hooks' import { useAccountDrawer } from '../hooks'
const FloorPrice = styled(Row)` const FloorPrice = styled(Row)`
opacity: 0; opacity: 0;
...@@ -47,12 +47,12 @@ export function NFT({ ...@@ -47,12 +47,12 @@ export function NFT({
mediaShouldBePlaying: boolean mediaShouldBePlaying: boolean
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
}) { }) {
const toggleWalletDrawer = useToggleAccountDrawer() const accountDrawer = useAccountDrawer()
const navigate = useNavigate() const navigate = useNavigate()
const trace = useTrace() const trace = useTrace()
const navigateToNFTDetails = () => { const navigateToNFTDetails = () => {
toggleWalletDrawer() accountDrawer.close()
navigate(detailsHref(asset)) navigate(detailsHref(asset))
} }
......
...@@ -10,7 +10,7 @@ import { useAccountDrawer } from '../hooks' ...@@ -10,7 +10,7 @@ import { useAccountDrawer } from '../hooks'
import { NFT } from './NFTItem' import { NFT } from './NFTItem'
export default function NFTs({ account }: { account: string }) { export default function NFTs({ account }: { account: string }) {
const [walletDrawerOpen, toggleWalletDrawer] = useAccountDrawer() const accountDrawer = useAccountDrawer()
const { walletAssets, loading, hasNext, loadMore } = useNftBalance( const { walletAssets, loading, hasNext, loadMore } = useNftBalance(
account, account,
[], [],
...@@ -19,7 +19,7 @@ export default function NFTs({ account }: { account: string }) { ...@@ -19,7 +19,7 @@ export default function NFTs({ account }: { account: string }) {
undefined, undefined,
undefined, undefined,
undefined, undefined,
!walletDrawerOpen !accountDrawer.isOpen
) )
const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState<string | undefined>() const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState<string | undefined>()
...@@ -33,7 +33,7 @@ export default function NFTs({ account }: { account: string }) { ...@@ -33,7 +33,7 @@ export default function NFTs({ account }: { account: string }) {
} }
if (!walletAssets || walletAssets?.length === 0) { if (!walletAssets || walletAssets?.length === 0) {
return <EmptyWalletModule onNavigateClick={toggleWalletDrawer} /> return <EmptyWalletModule onNavigateClick={accountDrawer.close} />
} }
return ( return (
......
...@@ -17,7 +17,7 @@ import { NumberType, useFormatter } from 'utils/formatNumbers' ...@@ -17,7 +17,7 @@ import { NumberType, useFormatter } from 'utils/formatNumbers'
import { ExpandoRow } from '../ExpandoRow' import { ExpandoRow } from '../ExpandoRow'
import { PortfolioLogo } from '../PortfolioLogo' import { PortfolioLogo } from '../PortfolioLogo'
import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow' import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
import { useToggleAccountDrawer } from '../hooks' import { useAccountDrawer } from '../hooks'
import { PositionInfo } from './cache' import { PositionInfo } from './cache'
import { useFeeValues } from './hooks' import { useFeeValues } from './hooks'
import useMultiChainPositions from './useMultiChainPositions' import useMultiChainPositions from './useMultiChainPositions'
...@@ -64,14 +64,14 @@ export default function Pools({ account }: { account: string }) { ...@@ -64,14 +64,14 @@ export default function Pools({ account }: { account: string }) {
return [openPositions, closedPositions] return [openPositions, closedPositions]
}, [filteredPositions]) }, [filteredPositions])
const toggleWalletDrawer = useToggleAccountDrawer() const accountDrawer = useAccountDrawer()
if (!filteredPositions || loading) { if (!filteredPositions || loading) {
return <PortfolioSkeleton /> return <PortfolioSkeleton />
} }
if (filteredPositions.length === 0) { if (filteredPositions.length === 0) {
return <EmptyWalletModule type="pool" onNavigateClick={toggleWalletDrawer} /> return <EmptyWalletModule type="pool" onNavigateClick={accountDrawer.close} />
} }
return ( return (
...@@ -128,16 +128,16 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) { ...@@ -128,16 +128,16 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
const liquidityValue = calculateLiquidityValue(priceA, priceB, position) const liquidityValue = calculateLiquidityValue(priceA, priceB, position)
const navigate = useNavigate() const navigate = useNavigate()
const toggleWalletDrawer = useToggleAccountDrawer() const accountDrawer = useAccountDrawer()
const { chainId: walletChainId } = useWeb3React() const { chainId: walletChainId } = useWeb3React()
const switchChain = useSwitchChain() const switchChain = useSwitchChain()
const onClick = useCallback(async () => { const onClick = useCallback(async () => {
if (walletChainId !== chainId) { if (walletChainId !== chainId) {
await switchChain(chainId) await switchChain(chainId)
} }
toggleWalletDrawer() accountDrawer.close()
navigate('/pool/' + details.tokenId) navigate('/pool/' + details.tokenId)
}, [walletChainId, chainId, switchChain, toggleWalletDrawer, navigate, details.tokenId]) }, [walletChainId, chainId, switchChain, accountDrawer, navigate, details.tokenId])
const analyticsEventProperties = useMemo( const analyticsEventProperties = useMemo(
() => ({ () => ({
chain_id: chainId, chain_id: chainId,
......
...@@ -19,15 +19,15 @@ import { hideSmallBalancesAtom } from '../../SmallBalanceToggle' ...@@ -19,15 +19,15 @@ import { hideSmallBalancesAtom } from '../../SmallBalanceToggle'
import { ExpandoRow } from '../ExpandoRow' import { ExpandoRow } from '../ExpandoRow'
import { PortfolioLogo } from '../PortfolioLogo' import { PortfolioLogo } from '../PortfolioLogo'
import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow' import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
import { useAccountDrawer, useToggleAccountDrawer } from '../hooks' import { useAccountDrawer } from '../hooks'
export default function Tokens() { export default function Tokens() {
const [accountDrawerOpen, toggleAccountDrawer] = useAccountDrawer() const accountDrawer = useAccountDrawer()
const hideSmallBalances = useAtomValue(hideSmallBalancesAtom) const hideSmallBalances = useAtomValue(hideSmallBalancesAtom)
const hideSpam = useAtomValue(hideSpamAtom) const hideSpam = useAtomValue(hideSpamAtom)
const [showHiddenTokens, setShowHiddenTokens] = useState(false) const [showHiddenTokens, setShowHiddenTokens] = useState(false)
const { data } = useTokenBalancesQuery({ cacheOnly: !accountDrawerOpen }) const { data } = useTokenBalancesQuery({ cacheOnly: !accountDrawer.isOpen })
const tokenBalances = data?.portfolios?.[0]?.tokenBalances const tokenBalances = data?.portfolios?.[0]?.tokenBalances
...@@ -42,7 +42,7 @@ export default function Tokens() { ...@@ -42,7 +42,7 @@ export default function Tokens() {
if (tokenBalances?.length === 0) { if (tokenBalances?.length === 0) {
// TODO: consider launching moonpay here instead of just closing the drawer // TODO: consider launching moonpay here instead of just closing the drawer
return <EmptyWalletModule type="token" onNavigateClick={toggleAccountDrawer} /> return <EmptyWalletModule type="token" onNavigateClick={accountDrawer.close} />
} }
const toggleHiddenTokens = () => setShowHiddenTokens((showHiddenTokens) => !showHiddenTokens) const toggleHiddenTokens = () => setShowHiddenTokens((showHiddenTokens) => !showHiddenTokens)
...@@ -80,12 +80,12 @@ function TokenRow({ ...@@ -80,12 +80,12 @@ function TokenRow({
const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0 const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0
const navigate = useNavigate() const navigate = useNavigate()
const toggleWalletDrawer = useToggleAccountDrawer() const accountDrawer = useAccountDrawer()
const navigateToTokenDetails = useCallback(async () => { const navigateToTokenDetails = useCallback(async () => {
navigate(getTokenDetailsURL({ ...token })) navigate(getTokenDetailsURL({ ...token }))
toggleWalletDrawer() accountDrawer.close()
}, [navigate, token, toggleWalletDrawer]) }, [navigate, token, accountDrawer])
const { formatNumber } = useFormatter() const { formatNumber } = useFormatter()
const currency = gqlToCurrency(token) const currency = gqlToCurrency(token)
......
import { atom, useAtom } from 'jotai' import { atom, useAtom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils' import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useCallback } from 'react' import { useCallback, useMemo } from 'react'
const accountDrawerOpenAtom = atom(false) const accountDrawerOpenAtom = atom(false)
const showMoonpayTextAtom = atom(false) const showMoonpayTextAtom = atom(false)
export function useToggleAccountDrawer() { export function useAccountDrawer() {
const [open, updateAccountDrawerOpen] = useAtom(accountDrawerOpenAtom) const [isOpen, updateAccountDrawerOpen] = useAtom(accountDrawerOpenAtom)
const setShowMoonpayTextInDrawer = useSetShowMoonpayText() const setShowMoonpayTextInDrawer = useSetShowMoonpayText()
return useCallback(() => { const open = useCallback(() => {
updateAccountDrawerOpen(!open) updateAccountDrawerOpen(true)
if (open) { }, [updateAccountDrawerOpen])
setShowMoonpayTextInDrawer(false)
}
}, [open, setShowMoonpayTextInDrawer, updateAccountDrawerOpen])
}
export function useCloseAccountDrawer() {
const updateAccountDrawerOpen = useUpdateAtom(accountDrawerOpenAtom)
return useCallback(() => updateAccountDrawerOpen(false), [updateAccountDrawerOpen])
}
export function useOpenAccountDrawer() { const close = useCallback(() => {
const updateAccountDrawerOpen = useUpdateAtom(accountDrawerOpenAtom) setShowMoonpayTextInDrawer(false)
return useCallback(() => updateAccountDrawerOpen(true), [updateAccountDrawerOpen]) updateAccountDrawerOpen(false)
} }, [setShowMoonpayTextInDrawer, updateAccountDrawerOpen])
export function useAccountDrawer(): [boolean, () => void] { return useMemo(() => ({ isOpen, open, close }), [isOpen, open, close])
const accountDrawerOpen = useAtomValue(accountDrawerOpenAtom)
return [accountDrawerOpen, useToggleAccountDrawer()]
} }
// Only show Moonpay text if the user opens the Account Drawer by clicking 'Buy' // Only show Moonpay text if the user opens the Account Drawer by clicking 'Buy'
......
...@@ -4,14 +4,14 @@ import Modal from 'components/Modal' ...@@ -4,14 +4,14 @@ import Modal from 'components/Modal'
import { RowBetween } from 'components/Row' import { RowBetween } from 'components/Row'
import { useConnectorWithId } from 'components/WalletModal/useOrderedConnections' import { useConnectorWithId } from 'components/WalletModal/useOrderedConnections'
import { CONNECTION } from 'components/Web3Provider/constants' import { CONNECTION } from 'components/Web3Provider/constants'
import { useConnect } from 'hooks/useConnect'
import { Trans } from 'i18n' import { Trans } from 'i18n'
import { QRCodeSVG } from 'qrcode.react' import { QRCodeSVG } from 'qrcode.react'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
import { CloseIcon, ThemedText } from 'theme/components' import { CloseIcon, ThemedText } from 'theme/components'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { isWebAndroid, isWebIOS } from 'uniswap/src/utils/platform' import { isWebAndroid, isWebIOS } from 'utilities/src/platform'
import { useAccountEffect, useConnect, useDisconnect } from 'wagmi'
import uniPng from '../../assets/images/uniwallet_modal_icon.png' import uniPng from '../../assets/images/uniwallet_modal_icon.png'
import { DownloadButton } from './DownloadButton' import { DownloadButton } from './DownloadButton'
...@@ -37,18 +37,12 @@ const Divider = styled.div` ...@@ -37,18 +37,12 @@ const Divider = styled.div`
export default function UniwalletModal() { export default function UniwalletModal() {
const [uri, setUri] = useState<string>() const [uri, setUri] = useState<string>()
const connection = useConnect()
// Displays the modal if not on iOS/Android, a Uniswap Wallet Connection is pending, & qrcode URI is available // Displays the modal if not on iOS/Android, a Uniswap Wallet Connection is pending, & qrcode URI is available
const onLaunchedMobilePlatform = isWebIOS || isWebAndroid const onLaunchedMobilePlatform = isWebIOS || isWebAndroid
const open = !onLaunchedMobilePlatform && !!uri const open = !onLaunchedMobilePlatform && !!uri && connection.isPending
const { disconnect } = useDisconnect()
const { connectors } = useConnect()
useAccountEffect({
onConnect: () => {
setUri(undefined)
},
})
const uniswapWalletConnectConnector = useConnectorWithId(CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, { const uniswapWalletConnectConnector = useConnectorWithId(CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, {
shouldThrow: true, shouldThrow: true,
}) })
...@@ -65,12 +59,12 @@ export default function UniwalletModal() { ...@@ -65,12 +59,12 @@ export default function UniwalletModal() {
return () => { return () => {
uniswapWalletConnectConnector.emitter.off('message', listener) uniswapWalletConnectConnector.emitter.off('message', listener)
} }
}, [connectors, uniswapWalletConnectConnector.emitter]) }, [uniswapWalletConnectConnector.emitter])
const close = useCallback(() => { const close = useCallback(() => {
disconnect() connection?.reset()
setUri(undefined) setUri(undefined)
}, [disconnect]) }, [connection])
useEffect(() => { useEffect(() => {
if (open) { if (open) {
......
...@@ -11,7 +11,7 @@ import { BREAKPOINTS } from 'theme' ...@@ -11,7 +11,7 @@ import { BREAKPOINTS } from 'theme'
import { ClickableStyle } from 'theme/components' import { ClickableStyle } from 'theme/components'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { isMobile } from 'uniswap/src/utils/platform' import { isMobile } from 'utilities/src/platform'
import DefaultMenu from './DefaultMenu' import DefaultMenu from './DefaultMenu'
import { useAccountDrawer } from './MiniPortfolio/hooks' import { useAccountDrawer } from './MiniPortfolio/hooks'
...@@ -152,21 +152,21 @@ const CloseDrawer = styled.div` ...@@ -152,21 +152,21 @@ const CloseDrawer = styled.div`
` `
function AccountDrawer() { function AccountDrawer() {
const [walletDrawerOpen, toggleWalletDrawer] = useAccountDrawer() const accountDrawer = useAccountDrawer()
const wasWalletDrawerOpen = usePrevious(walletDrawerOpen) const wasAccountDrawerOpen = usePrevious(accountDrawer.isOpen)
const scrollRef = useRef<HTMLDivElement>(null) const scrollRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
if (wasWalletDrawerOpen && !walletDrawerOpen) { if (wasAccountDrawerOpen && !accountDrawer.isOpen) {
scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' }) scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' })
} }
}, [walletDrawerOpen, wasWalletDrawerOpen]) }, [accountDrawer, wasAccountDrawerOpen])
// close on escape keypress // close on escape keypress
useEffect(() => { useEffect(() => {
const escapeKeyDownHandler = (event: KeyboardEvent) => { const escapeKeyDownHandler = (event: KeyboardEvent) => {
if (event.key === 'Escape' && walletDrawerOpen) { if (event.key === 'Escape' && accountDrawer.isOpen) {
event.preventDefault() event.preventDefault()
toggleWalletDrawer() accountDrawer.close()
} }
} }
...@@ -175,12 +175,12 @@ function AccountDrawer() { ...@@ -175,12 +175,12 @@ function AccountDrawer() {
return () => { return () => {
document.removeEventListener('keydown', escapeKeyDownHandler) document.removeEventListener('keydown', escapeKeyDownHandler)
} }
}, [walletDrawerOpen, toggleWalletDrawer]) }, [accountDrawer])
// useStates for detecting swipe gestures // useStates for detecting swipe gestures
const [yPosition, setYPosition] = useState(0) const [yPosition, setYPosition] = useState(0)
const [dragStartTop, setDragStartTop] = useState(true) const [dragStartTop, setDragStartTop] = useState(true)
useDisableScrolling(walletDrawerOpen) useDisableScrolling(accountDrawer.isOpen)
// useGesture hook for detecting swipe gestures // useGesture hook for detecting swipe gestures
const bind = useGesture({ const bind = useGesture({
...@@ -194,11 +194,11 @@ function AccountDrawer() { ...@@ -194,11 +194,11 @@ function AccountDrawer() {
} }
} else if ( } else if (
(state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) && (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) &&
walletDrawerOpen && accountDrawer.isOpen &&
dragStartTop dragStartTop
) { ) {
toggleWalletDrawer() accountDrawer.close()
} else if (walletDrawerOpen && dragStartTop && state.movement[1] > 0) { } else if (accountDrawer.isOpen && dragStartTop && state.movement[1] > 0) {
setYPosition(state.movement[1]) setYPosition(state.movement[1])
if (scrollRef.current) { if (scrollRef.current) {
scrollRef.current.style.overflowY = 'hidden' scrollRef.current.style.overflowY = 'hidden'
...@@ -227,16 +227,16 @@ function AccountDrawer() { ...@@ -227,16 +227,16 @@ function AccountDrawer() {
return ( return (
<Container> <Container>
{walletDrawerOpen && ( {accountDrawer.isOpen && (
<Trace logPress eventOnTrigger={InterfaceEventName.MINI_PORTFOLIO_TOGGLED} properties={{ type: 'close' }}> <Trace logPress eventOnTrigger={InterfaceEventName.MINI_PORTFOLIO_TOGGLED} properties={{ type: 'close' }}>
<CloseDrawer onClick={toggleWalletDrawer} data-testid="close-account-drawer"> <CloseDrawer onClick={accountDrawer.close} data-testid="close-account-drawer">
<CloseIcon /> <CloseIcon />
</CloseDrawer> </CloseDrawer>
</Trace> </Trace>
)} )}
<Scrim onClick={toggleWalletDrawer} $open={walletDrawerOpen} /> <Scrim onClick={accountDrawer.close} $open={accountDrawer.isOpen} />
<AccountDrawerWrapper <AccountDrawerWrapper
open={walletDrawerOpen} open={accountDrawer.isOpen}
{...(isMobile {...(isMobile
? { ? {
...bind(), ...bind(),
...@@ -246,7 +246,7 @@ function AccountDrawer() { ...@@ -246,7 +246,7 @@ function AccountDrawer() {
> >
{/* id used for child InfiniteScrolls to reference when it has reached the bottom of the component */} {/* id used for child InfiniteScrolls to reference when it has reached the bottom of the component */}
<AccountDrawerScrollWrapper ref={scrollRef} id="wallet-dropdown-scroll-wrapper"> <AccountDrawerScrollWrapper ref={scrollRef} id="wallet-dropdown-scroll-wrapper">
<DefaultMenu drawerOpen={walletDrawerOpen} /> <DefaultMenu drawerOpen={accountDrawer.isOpen} />
</AccountDrawerScrollWrapper> </AccountDrawerScrollWrapper>
</AccountDrawerWrapper> </AccountDrawerWrapper>
</Container> </Container>
......
...@@ -3,7 +3,7 @@ import Row from 'components/Row' ...@@ -3,7 +3,7 @@ import Row from 'components/Row'
import Tooltip, { TooltipSize } from 'components/Tooltip' import Tooltip, { TooltipSize } from 'components/Tooltip'
import { useScreenSize } from 'hooks/screenSize' import { useScreenSize } from 'hooks/screenSize'
import useCopyClipboard from 'hooks/useCopyClipboard' import useCopyClipboard from 'hooks/useCopyClipboard'
import { t, Trans } from 'i18n' import { Trans, t } from 'i18n'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { Copy } from 'react-feather' import { Copy } from 'react-feather'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
......
...@@ -8,7 +8,7 @@ import { ExternalLink } from 'theme/components' ...@@ -8,7 +8,7 @@ import { ExternalLink } from 'theme/components'
const StyledExternalLink = styled(ExternalLink)` const StyledExternalLink = styled(ExternalLink)`
width: fit-content; width: fit-content;
border-radius: 16px; border-radius: 16px;
padding: 4px 6px; padding: 4px 8px;
font-size: 14px; font-size: 14px;
font-weight: 485; font-weight: 485;
line-height: 20px; line-height: 20px;
......
...@@ -247,7 +247,7 @@ function CandlestickTooltip({ data }: { data: PriceChartData }) { ...@@ -247,7 +247,7 @@ function CandlestickTooltip({ data }: { data: PriceChartData }) {
<div>{formatFiatPrice({ price: data.low })}</div> <div>{formatFiatPrice({ price: data.low })}</div>
</RowBetween> </RowBetween>
<RowBetween gap="sm"> <RowBetween gap="sm">
<Trans i18nKey="chart.price.close" /> <Trans i18nKey="common.close" />
<div>{formatFiatPrice({ price: data.close })}</div> <div>{formatFiatPrice({ price: data.close })}</div>
</RowBetween> </RowBetween>
</TooltipText> </TooltipText>
......
...@@ -14,7 +14,7 @@ describe('ConfirmSwapModal/Error', () => { ...@@ -14,7 +14,7 @@ describe('ConfirmSwapModal/Error', () => {
['limit order', PendingModalError.CONFIRMATION_ERROR, LIMIT_ORDER_TRADE, 'Limit failed'], ['limit order', PendingModalError.CONFIRMATION_ERROR, LIMIT_ORDER_TRADE, 'Limit failed'],
['limit order', PendingModalError.WRAP_ERROR, LIMIT_ORDER_TRADE, 'Wrap failed'], ['limit order', PendingModalError.WRAP_ERROR, LIMIT_ORDER_TRADE, 'Wrap failed'],
])('renders %p correctly, with error= %p', async (testCaseName, errorType, trade, expectedError) => { ])('renders %p correctly, with error= %p', async (testCaseName, errorType, trade, expectedError) => {
const { asFragment } = render(<Error errorType={errorType} trade={trade} onRetry={jest.fn()} />) const { asFragment } = render(<Error errorType={errorType} trade={trade} onRetry={jest.fn()} showTrade={true} />)
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(screen.getByText(expectedError)).toBeInTheDocument() expect(screen.getByText(expectedError)).toBeInTheDocument()
}) })
......
...@@ -4,7 +4,6 @@ import { SwapResult } from 'hooks/useSwapCallback' ...@@ -4,7 +4,6 @@ import { SwapResult } from 'hooks/useSwapCallback'
import { Trans } from 'i18n' import { Trans } from 'i18n'
import { InterfaceTrade, TradeFillType } from 'state/routing/types' import { InterfaceTrade, TradeFillType } from 'state/routing/types'
import { isLimitTrade, isUniswapXTrade } from 'state/routing/utils' import { isLimitTrade, isUniswapXTrade } from 'state/routing/utils'
import { useTheme } from 'styled-components'
import { TradeSummary } from 'components/ConfirmSwapModal/TradeSummary' import { TradeSummary } from 'components/ConfirmSwapModal/TradeSummary'
import { DialogButtonType, DialogContent } from 'components/Dialog/Dialog' import { DialogButtonType, DialogContent } from 'components/Dialog/Dialog'
...@@ -24,6 +23,7 @@ export enum PendingModalError { ...@@ -24,6 +23,7 @@ export enum PendingModalError {
interface ErrorModalContentProps { interface ErrorModalContentProps {
errorType: PendingModalError errorType: PendingModalError
trade?: InterfaceTrade trade?: InterfaceTrade
showTrade?: boolean
swapResult?: SwapResult swapResult?: SwapResult
onRetry: () => void onRetry: () => void
} }
...@@ -49,7 +49,7 @@ function getErrorContent({ errorType, trade }: { errorType: PendingModalError; t ...@@ -49,7 +49,7 @@ function getErrorContent({ errorType, trade }: { errorType: PendingModalError; t
case PendingModalError.XV2_HARD_QUOTE_ERROR: case PendingModalError.XV2_HARD_QUOTE_ERROR:
return { return {
title: <Trans i18nKey="common.swap.failed" />, title: <Trans i18nKey="common.swap.failed" />,
message: <Trans i18nKey="common.swap.failed.uniswapX" />, message: <Trans i18nKey="swap.fail.uniswapX" />,
supportArticleURL: SupportArticleURL.UNISWAP_X_FAILURE, supportArticleURL: SupportArticleURL.UNISWAP_X_FAILURE,
} }
case PendingModalError.CONFIRMATION_ERROR: case PendingModalError.CONFIRMATION_ERROR:
...@@ -61,7 +61,7 @@ function getErrorContent({ errorType, trade }: { errorType: PendingModalError; t ...@@ -61,7 +61,7 @@ function getErrorContent({ errorType, trade }: { errorType: PendingModalError; t
} else { } else {
return { return {
title: <Trans i18nKey="common.swap.failed" />, title: <Trans i18nKey="common.swap.failed" />,
message: <Trans i18nKey="common.swap.failed.message" />, message: <Trans i18nKey="swap.fail.message" />,
supportArticleURL: isUniswapXTrade(trade) supportArticleURL: isUniswapXTrade(trade)
? SupportArticleURL.UNISWAP_X_FAILURE ? SupportArticleURL.UNISWAP_X_FAILURE
: SupportArticleURL.TRANSACTION_FAILURE, : SupportArticleURL.TRANSACTION_FAILURE,
...@@ -81,19 +81,18 @@ function getErrorContent({ errorType, trade }: { errorType: PendingModalError; t ...@@ -81,19 +81,18 @@ function getErrorContent({ errorType, trade }: { errorType: PendingModalError; t
} }
} }
export default function Error({ errorType, trade, swapResult, onRetry }: ErrorModalContentProps) { export default function Error({ errorType, trade, showTrade, swapResult, onRetry }: ErrorModalContentProps) {
const theme = useTheme()
const { title, message, supportArticleURL } = getErrorContent({ errorType, trade }) const { title, message, supportArticleURL } = getErrorContent({ errorType, trade })
return ( return (
<DialogContent <DialogContent
isVisible={true} isVisible={true}
icon={<AlertTriangleFilled data-testid="pending-modal-failure-icon" fill={theme.neutral2} size="24px" />} icon={<AlertTriangleFilled data-testid="pending-modal-failure-icon" size="24px" />}
title={title} title={title}
description={message} description={message}
body={ body={
<ColumnCenter gap="md"> <ColumnCenter gap="sm">
{trade && <TradeSummary trade={trade} />} {showTrade && trade && <TradeSummary trade={trade} />}
{supportArticleURL && ( {supportArticleURL && (
<ExternalLink href={supportArticleURL}> <ExternalLink href={supportArticleURL}>
<Trans i18nKey="common.learnMore.link" /> <Trans i18nKey="common.learnMore.link" />
...@@ -115,7 +114,7 @@ export default function Error({ errorType, trade, swapResult, onRetry }: ErrorMo ...@@ -115,7 +114,7 @@ export default function Error({ errorType, trade, swapResult, onRetry }: ErrorMo
} }
buttonsConfig={{ buttonsConfig={{
left: { left: {
type: DialogButtonType.Accent, type: DialogButtonType.Primary,
title: <Trans i18nKey="common.tryAgain.error" />, title: <Trans i18nKey="common.tryAgain.error" />,
onClick: onRetry, onClick: onRetry,
}, },
......
...@@ -5,7 +5,7 @@ import Row from 'components/Row' ...@@ -5,7 +5,7 @@ import Row from 'components/Row'
import { SupportArticleURL } from 'constants/supportArticles' import { SupportArticleURL } from 'constants/supportArticles'
import { SwapResult } from 'hooks/useSwapCallback' import { SwapResult } from 'hooks/useSwapCallback'
import { useUnmountingAnimation } from 'hooks/useUnmountingAnimation' import { useUnmountingAnimation } from 'hooks/useUnmountingAnimation'
import { t, Trans } from 'i18n' import { Trans, t } from 'i18n'
import { ReactNode, useMemo, useRef } from 'react' import { ReactNode, useMemo, useRef } from 'react'
import { InterfaceTrade, TradeFillType } from 'state/routing/types' import { InterfaceTrade, TradeFillType } from 'state/routing/types'
import { isLimitTrade, isUniswapXTradeType } from 'state/routing/utils' import { isLimitTrade, isUniswapXTradeType } from 'state/routing/utils'
...@@ -26,8 +26,8 @@ import { ...@@ -26,8 +26,8 @@ import {
LoadingIndicatorOverlay, LoadingIndicatorOverlay,
LogoContainer, LogoContainer,
} from '../AccountDrawer/MiniPortfolio/Activity/Logos' } from '../AccountDrawer/MiniPortfolio/Activity/Logos'
import { slideInAnimation, slideOutAnimation } from './animations'
import { TradeSummary } from './TradeSummary' import { TradeSummary } from './TradeSummary'
import { slideInAnimation, slideOutAnimation } from './animations'
const Container = styled(ColumnCenter)` const Container = styled(ColumnCenter)`
margin: 48px 0 8px; margin: 48px 0 8px;
......
...@@ -130,7 +130,7 @@ exports[`ConfirmSwapModal/Head should render correctly for a Limit order 1`] = ` ...@@ -130,7 +130,7 @@ exports[`ConfirmSwapModal/Head should render correctly for a Limit order 1`] = `
width: -moz-fit-content; width: -moz-fit-content;
width: fit-content; width: fit-content;
border-radius: 16px; border-radius: 16px;
padding: 4px 6px; padding: 4px 8px;
font-size: 14px; font-size: 14px;
font-weight: 485; font-weight: 485;
line-height: 20px; line-height: 20px;
...@@ -380,7 +380,7 @@ exports[`ConfirmSwapModal/Head should render correctly for a classic swap 1`] = ...@@ -380,7 +380,7 @@ exports[`ConfirmSwapModal/Head should render correctly for a classic swap 1`] =
width: -moz-fit-content; width: -moz-fit-content;
width: fit-content; width: fit-content;
border-radius: 16px; border-radius: 16px;
padding: 4px 6px; padding: 4px 8px;
font-size: 14px; font-size: 14px;
font-weight: 485; font-weight: 485;
line-height: 20px; line-height: 20px;
......
...@@ -272,19 +272,22 @@ export function ConfirmSwapModal({ ...@@ -272,19 +272,22 @@ export function ConfirmSwapModal({
)} )}
{/* Error screen handles all error types with custom messaging and retry logic */} {/* Error screen handles all error types with custom messaging and retry logic */}
{errorType && showError && ( {errorType && showError && (
<SwapError <Container $padding="16px">
trade={trade} <SwapError
swapResult={swapResult} trade={trade}
errorType={errorType} showTrade={errorType !== PendingModalError.XV2_HARD_QUOTE_ERROR}
onRetry={() => { swapResult={swapResult}
if (errorType === PendingModalError.XV2_HARD_QUOTE_ERROR) { errorType={errorType}
onXV2RetryWithClassic?.() onRetry={() => {
resetToReviewScreen() if (errorType === PendingModalError.XV2_HARD_QUOTE_ERROR) {
} else { onXV2RetryWithClassic?.()
startSwapFlow() resetToReviewScreen()
} } else {
}} startSwapFlow()
/> }
}}
/>
</Container>
)} )}
</SwapModal> </SwapModal>
</ThemeProvider> </ThemeProvider>
......
...@@ -97,12 +97,14 @@ export interface DialogProps { ...@@ -97,12 +97,14 @@ export interface DialogProps {
export function DialogContent({ icon, title, description, body, buttonsConfig }: DialogProps) { export function DialogContent({ icon, title, description, body, buttonsConfig }: DialogProps) {
const { left, right, gap } = buttonsConfig ?? {} const { left, right, gap } = buttonsConfig ?? {}
return ( return (
<> <ColumnCenter gap="lg">
<ColumnCenter gap="md"> <ColumnCenter gap="16px">
<IconContainer>{icon}</IconContainer> <IconContainer>{icon}</IconContainer>
<TitleText>{title}</TitleText> <ColumnCenter gap="sm">
<DescriptionText>{description}</DescriptionText> <TitleText>{title}</TitleText>
{body} <DescriptionText>{description}</DescriptionText>
{body}
</ColumnCenter>
</ColumnCenter> </ColumnCenter>
<Row align="center" justify="center" gap={gap ?? 'md'}> <Row align="center" justify="center" gap={gap ?? 'md'}>
{left && ( {left && (
...@@ -128,7 +130,7 @@ export function DialogContent({ icon, title, description, body, buttonsConfig }: ...@@ -128,7 +130,7 @@ export function DialogContent({ icon, title, description, body, buttonsConfig }:
</StyledButton> </StyledButton>
)} )}
</Row> </Row>
</> </ColumnCenter>
) )
} }
......
...@@ -5,14 +5,11 @@ import { StyledSVG } from './shared' ...@@ -5,14 +5,11 @@ import { StyledSVG } from './shared'
export default function AlertTriangleFilled({ size = '16px', ...rest }: { size?: string; [k: string]: any }) { export default function AlertTriangleFilled({ size = '16px', ...rest }: { size?: string; [k: string]: any }) {
const theme = useTheme() const theme = useTheme()
return ( return (
<StyledSVG <StyledSVG fill={theme.neutral2} viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" size={size} {...rest}>
viewBox="0 0 16 16" <path
fill={theme.deprecated_accentWarning} d="m25.2086 20.0103-7.7081-14.41555c-1.4933-2.793-5.5055-2.793-7 0l-7.70806 14.41555c-1.36966 2.562.49005 5.6559 3.40088 5.6559h15.61438c2.9097 0 4.7706-3.095 3.4009-5.6559zm-12.0831-8.3441c0-.483.392-.875.875-.875s.875.392.875.875v4.6667c0 .483-.392.875-.875.875s-.875-.392-.875-.875zm.8984 9.3333c-.644 0-1.1727-.5226-1.1727-1.1666s.517-1.1667 1.161-1.1667h.0117c.6452 0 1.1667.5227 1.1667 1.1667s-.5227 1.1666-1.1667 1.1666z"
xmlns="http://www.w3.org/2000/svg" fill="#9b9b9b"
size={size} />
{...rest}
>
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2a1 1 0 0 1 0-2z" />
</StyledSVG> </StyledSVG>
) )
} }
...@@ -4,7 +4,7 @@ import { animated, easings, useSpring, useTransition } from 'react-spring' ...@@ -4,7 +4,7 @@ import { animated, easings, useSpring, useTransition } from 'react-spring'
import { useGesture } from 'react-use-gesture' import { useGesture } from 'react-use-gesture'
import styled, { css } from 'styled-components' import styled, { css } from 'styled-components'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import { isMobile } from 'uniswap/src/utils/platform' import { isMobile } from 'utilities/src/platform'
export const MODAL_TRANSITION_DURATION = 200 export const MODAL_TRANSITION_DURATION = 200
......
...@@ -12,7 +12,7 @@ import styled, { useTheme } from 'styled-components' ...@@ -12,7 +12,7 @@ import styled, { useTheme } from 'styled-components'
import { BREAKPOINTS } from 'theme' import { BREAKPOINTS } from 'theme'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import { isWebAndroid, isWebIOS } from 'uniswap/src/utils/platform' import { isWebAndroid, isWebIOS } from 'utilities/src/platform'
import { getWalletMeta } from 'utils/walletMeta' import { getWalletMeta } from 'utils/walletMeta'
const Wrapper = styled.div` const Wrapper = styled.div`
......
...@@ -10,7 +10,7 @@ import useDebounce from 'hooks/useDebounce' ...@@ -10,7 +10,7 @@ import useDebounce from 'hooks/useDebounce'
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes' import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
import { useIsNftPage } from 'hooks/useIsNftPage' import { useIsNftPage } from 'hooks/useIsNftPage'
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useTranslation } from 'i18n' import { useTranslation } from 'i18n/useTranslation'
import { organizeSearchResults } from 'lib/utils/searchBar' import { organizeSearchResults } from 'lib/utils/searchBar'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
......
import { t, Trans } from 'i18n' import { Trans, t } from 'i18n'
import { useOpenModal } from 'state/application/hooks' import { useOpenModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer' import { ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components' import styled from 'styled-components'
......
...@@ -101,19 +101,17 @@ const Navbar = ({ blur }: { blur: boolean }) => { ...@@ -101,19 +101,17 @@ const Navbar = ({ blur }: { blur: boolean }) => {
const isNavSearchInputVisible = useIsNavSearchInputVisible() const isNavSearchInputVisible = useIsNavSearchInputVisible()
const { account } = useWeb3React() const { account } = useWeb3React()
const [accountDrawerOpen, toggleAccountDrawer] = useAccountDrawer() const accountDrawer = useAccountDrawer()
const handleUniIconClick = useCallback(() => { const handleUniIconClick = useCallback(() => {
if (account) { if (account) {
return return
} }
if (accountDrawerOpen) { accountDrawer.close()
toggleAccountDrawer()
}
navigate({ navigate({
pathname: '/', pathname: '/',
search: '?intro=true', search: '?intro=true',
}) })
}, [account, accountDrawerOpen, navigate, toggleAccountDrawer]) }, [account, accountDrawer, navigate])
return ( return (
<> <>
......
...@@ -3,7 +3,7 @@ import SettingsTab from 'components/Settings' ...@@ -3,7 +3,7 @@ import SettingsTab from 'components/Settings'
import { Trans } from 'i18n' import { Trans } from 'i18n'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { Link, useNavigate } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
import { Box } from 'rebass' import { Box } from 'rebass'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { resetMintState } from 'state/mint/actions' import { resetMintState } from 'state/mint/actions'
...@@ -68,11 +68,13 @@ export function AddRemoveTabs({ ...@@ -68,11 +68,13 @@ export function AddRemoveTabs({
adding, adding,
creating, creating,
autoSlippage, autoSlippage,
positionID,
children, children,
}: { }: {
adding: boolean adding: boolean
creating: boolean creating: boolean
autoSlippage: Percent autoSlippage: Percent
positionID?: string
showBackLink?: boolean showBackLink?: boolean
children?: ReactNode children?: ReactNode
}) { }) {
...@@ -80,17 +82,23 @@ export function AddRemoveTabs({ ...@@ -80,17 +82,23 @@ export function AddRemoveTabs({
const theme = useTheme() const theme = useTheme()
// reset states on back // reset states on back
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const navigate = useNavigate() const { state } = useLocation()
const location = useLocation()
// detect if back should redirect to v3 or v2 pool page
const poolLink = location.pathname.includes('add/v2')
? '/pools/v2'
: '/pools' + (positionID ? `/${positionID.toString()}` : '')
// If the 'from' state is set by the previous page route back to the previous page, if not, route back to base pool
const target = state?.from ?? poolLink
return ( return (
<Tabs> <Tabs>
<RowBetween style={{ padding: '1rem 1rem 0 1rem' }} align="center"> <RowBetween style={{ padding: '1rem 1rem 0 1rem' }} align="center">
<StyledLink <StyledLink
to=".." to={target}
onClick={(e) => { onClick={() => {
e.preventDefault()
navigate(-1)
if (adding) { if (adding) {
// not 100% sure both of these are needed // not 100% sure both of these are needed
dispatch(resetMintState()) dispatch(resetMintState())
......
...@@ -60,14 +60,18 @@ interface InputProps extends Omit<React.HTMLProps<HTMLInputElement>, 'ref' | 'on ...@@ -60,14 +60,18 @@ interface InputProps extends Omit<React.HTMLProps<HTMLInputElement>, 'ref' | 'on
maxDecimals?: number maxDecimals?: number
} }
export function isInputGreaterThanDecimals(value: string, maxDecimals?: number): boolean {
const decimalGroups = value.split('.')
return !!maxDecimals && decimalGroups.length > 1 && decimalGroups[1].length > maxDecimals
}
const Input = forwardRef<HTMLInputElement, InputProps>( const Input = forwardRef<HTMLInputElement, InputProps>(
({ value, onUserInput, placeholder, prependSymbol, maxDecimals, ...rest }: InputProps, ref) => { ({ value, onUserInput, placeholder, prependSymbol, maxDecimals, ...rest }: InputProps, ref) => {
const { formatterLocale } = useFormatterLocales() const { formatterLocale } = useFormatterLocales()
const enforcer = (nextUserInput: string) => { const enforcer = (nextUserInput: string) => {
if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) { if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
const decimalGroups = nextUserInput.split('.') if (isInputGreaterThanDecimals(nextUserInput, maxDecimals)) {
if (maxDecimals && decimalGroups.length > 1 && decimalGroups[1].length > maxDecimals) {
return return
} }
......
...@@ -54,15 +54,18 @@ const StyledChart: typeof Chart = styled(Chart)` ...@@ -54,15 +54,18 @@ const StyledChart: typeof Chart = styled(Chart)`
const PDPChartTypeSelector = ({ const PDPChartTypeSelector = ({
chartType, chartType,
onChartTypeChange, onChartTypeChange,
disabledOption,
}: { }: {
chartType: PoolsDetailsChartType chartType: PoolsDetailsChartType
onChartTypeChange: (c: PoolsDetailsChartType) => void onChartTypeChange: (c: PoolsDetailsChartType) => void
disabledOption?: PoolsDetailsChartType
}) => ( }) => (
<ChartTypeSelectorContainer> <ChartTypeSelectorContainer>
<ChartTypeDropdown <ChartTypeDropdown
options={PDP_CHART_SELECTOR_OPTIONS} options={PDP_CHART_SELECTOR_OPTIONS}
currentChartType={chartType} currentChartType={chartType}
onSelectOption={onChartTypeChange} onSelectOption={onChartTypeChange}
disabledOption={disabledOption}
/> />
</ChartTypeSelectorContainer> </ChartTypeSelectorContainer>
) )
...@@ -196,11 +199,17 @@ export default function ChartSection(props: ChartSectionProps) { ...@@ -196,11 +199,17 @@ export default function ChartSection(props: ChartSectionProps) {
return DEFAULT_PILL_TIME_SELECTOR_OPTIONS return DEFAULT_PILL_TIME_SELECTOR_OPTIONS
}, [activeQuery.chartType, setTimePeriod, timePeriod]) }, [activeQuery.chartType, setTimePeriod, timePeriod])
const disabledChartOption = props.poolData?.protocolVersion === ProtocolVersion.V2 ? ChartType.LIQUIDITY : undefined
return ( return (
<div data-testid="pdp-chart-container"> <div data-testid="pdp-chart-container">
{ChartBody} {ChartBody}
<ChartActionsContainer> <ChartActionsContainer>
<PDPChartTypeSelector chartType={activeQuery.chartType} onChartTypeChange={setChartType} /> <PDPChartTypeSelector
chartType={activeQuery.chartType}
onChartTypeChange={setChartType}
disabledOption={disabledChartOption}
/>
{activeQuery.chartType !== ChartType.LIQUIDITY && ( {activeQuery.chartType !== ChartType.LIQUIDITY && (
<TimePeriodSelectorContainer> <TimePeriodSelectorContainer>
<PillMultiToggle <PillMultiToggle
......
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