ci(release): publish latest release

parent 4fdcca60
This source diff could not be displayed because it is too large. You can view the blob instead.
IPFS hash of the deployment: IPFS hash of the deployment:
- CIDv0: `QmbvpQZnmawF3Fj7MkSmPaiDXGaEgbnHUsHPcdphF2zdQS` - CIDv0: `QmV8dNhZZhwARbFbRwX84c1qD5g1ByxuGSxegFdt4iLGWD`
- CIDv1: `bafybeigj5csihxv2gcyb4r2jaceaco7bmkltzh6krxveckivxp7hzto6s4` - CIDv1: `bafybeide5vkgujdy6vmvh2hzpkepm3lvyo3b7ed4vahlsf5nemuzjc3n4i`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
...@@ -10,66 +10,47 @@ You can also access the Uniswap Interface from an IPFS gateway. ...@@ -10,66 +10,47 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs. Your Uniswap settings are never remembered across different URLs.
IPFS gateways: IPFS gateways:
- https://bafybeigj5csihxv2gcyb4r2jaceaco7bmkltzh6krxveckivxp7hzto6s4.ipfs.dweb.link/ - https://bafybeide5vkgujdy6vmvh2hzpkepm3lvyo3b7ed4vahlsf5nemuzjc3n4i.ipfs.dweb.link/
- https://bafybeigj5csihxv2gcyb4r2jaceaco7bmkltzh6krxveckivxp7hzto6s4.ipfs.cf-ipfs.com/ - https://bafybeide5vkgujdy6vmvh2hzpkepm3lvyo3b7ed4vahlsf5nemuzjc3n4i.ipfs.cf-ipfs.com/
- [ipfs://QmbvpQZnmawF3Fj7MkSmPaiDXGaEgbnHUsHPcdphF2zdQS/](ipfs://QmbvpQZnmawF3Fj7MkSmPaiDXGaEgbnHUsHPcdphF2zdQS/) - [ipfs://QmV8dNhZZhwARbFbRwX84c1qD5g1ByxuGSxegFdt4iLGWD/](ipfs://QmV8dNhZZhwARbFbRwX84c1qD5g1ByxuGSxegFdt4iLGWD/)
## 5.18.0 (2024-03-13) ## 5.19.0 (2024-03-18)
### Features ### Features
* **web:** [info] correctly identify buy/sell txs on TDP (#6715) 2c2f316 * **web:** add CurrencyInfo type to web (#6810) ffc3f51
* **web:** [info] correctly sort 1 day % change at different time intervals (#6703) 28e9f57 * **web:** add realtime feature flag (#6374) 7038b33
* **web:** [info] Unwrap Natives on Info Pages (#6655) 7f4164d * **web:** Add Unicons V2 to web behind flag (#5968) dc40653
* **web:** [info] wrap error text in charts (#6705) c02ab49 * **web:** gql token lists feature flag and boilerplate (#6857) 539001c
* **web:** add more limits disclaimers (#6737) fb50849 * **web:** link PDP to LP positions page (#6838) 116eaf3
* **web:** add trace.now() (#6782) 4384e96 * **web:** reinstate on-chain polling configuration (#6841) b020958
* **web:** add zone.js (#6697) 8b47c08 * **web:** remove mobile app promo banner (#6917) (#6954) 0c1b9c3
* **web:** Hide 1H time option on PDP price chart (#6807) c6f84a3 * **web:** update AppJsonRpcProvider to only use exp decay on fails (#6890) 37862f2
* **web:** trace permit ops (#6784) 6417afd
* **web:** trace requests (#6698) 37eddbd
* **web:** trace tx ops (#6785) 7dd8d9c
* **web:** trace wallet ops (#6783) b7d6793
* **web:** update Sentry tracing integration (#6696) e3872d2
### Bug Fixes ### Bug Fixes
* **web:** [charts] use neutral3 dot grid (#6678) deb3486 * **web:** avoid polling current block timestamp (#6843) 5c65ef9
* **web:** [info] handle null priceHistory (#6806) 231d331 * **web:** broken token logos (#6935) 522f176
* **web:** [info] Update PDP TX Table on token order switch (#6772) 371b6b4 * **web:** configure Sentry source maps for rebased branches (#6923) e2b0872
* **web:** adjust disconnect button hover state (#6788) 7df2c12 * **web:** downgrade zone.js to fix iOS 16 (#6920) e0266b7
* **web:** avoid navigator.getBattery call on web (#6708) bdd8f91 * **web:** fetch HMR paths (#6862) 734d772
* **web:** dedupe and use shared version of useAsyncData hook (#6734) 31275df * **web:** fix isMobile to not pick up desktop, and fix service worker checks (#6936) 2daff12
* **web:** fiatCurrency is undefined (#6811) 3c8bea8 * **web:** fix miniflare after apollo/client upgrade (#6913) f152713
* **web:** fix failing snapshot test for OffchainActivityModal (#6719) dedc87b * **web:** LimitsMenu overflowing cancel button bug (#6854) acf2f01
* **web:** fix grammar in limits disclaimer (#6704) c197a86 * **web:** only poll for gas price when needed (#6845) e15f3c5
* **web:** fix incorrect label in OffchainActivityModal (#6694) 0215c87 * **web:** patch zone.js to allow wc modal (#6864) 5d595b2
* **web:** fix mouseover remnant (#6725) 7fe49b8 * **web:** reduce polling for health to 5m (#6842) 49391c8
* **web:** fix token explore cypress tests (#6764) 2a5e945 * **web:** should copy checksummed address (#6757) bbc2aca
* **web:** fixes for Limits Menu on mobile layout (#6706) dc67454 * **web:** show correct v2 fee (#6925) bad29f9
* **web:** limits cancellation gas estimates (#6716) 0406324 * **web:** stop requesting order statuses w/ invalid request body (#6820) 29ea4cd
* **web:** navigation cypress test (#6765) 889f5a3 * **web:** stop updating displayed quote after trade is submitted (#6819) c0ed816
* **web:** patch zone.js to allow wc modal (#6865) c2166a8 * **web:** use apollo provider w/o realtime (#6914) 47db20e
* **web:** remove NFT Airdrop claim (#6754) 6235a76
* **web:** swap flow e2e tests (#6767) 8248b9e
* **web:** universal search cypress test (#6766) dbeb2c2
### Documentation
* **web:** Sentry readme (#5501) 1f3f75e
### Code Refactoring ### Code Refactoring
* **web:** use NATIVE_CHAIN_ID over static string (#6770) 94c020a * **web:** wrap apollo Provider (#6375) 50d4d64
### Tests
* **web:** fix token-explore-filter e2e test (#6763) 006dd59
* **web:** squelch flushSync error (#6781) c9eecd7
web/5.18.0 web/5.19.0
\ No newline at end of file \ No newline at end of file
...@@ -4,9 +4,6 @@ jest.config.js ...@@ -4,9 +4,6 @@ jest.config.js
metro.config.js metro.config.js
node_modules node_modules
generated*.ts
__generated__/
storybook-static storybook-static
coverage coverage
ios ios
android android
src/abis/types .eslintrc.js
generated*.ts
__generated__/
.eslintrc.js
\ No newline at end of file
...@@ -131,17 +131,17 @@ android { ...@@ -131,17 +131,17 @@ android {
dev { dev {
isDefault(true) isDefault(true)
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
versionName "1.23" versionName "1.24"
dimension "variant" dimension "variant"
} }
beta { beta {
applicationIdSuffix ".beta" applicationIdSuffix ".beta"
versionName "1.23" versionName "1.24"
dimension "variant" dimension "variant"
} }
prod { prod {
dimension "variant" dimension "variant"
versionName "1.23" versionName "1.24"
} }
} }
......
import { CreateNewWallet } from './usecases/CreateNewWallet' import { CreateNewWallet } from 'e2e/usecases/onboarding/CreateNewWallet'
import { ImportWallet } from 'e2e/usecases/onboarding/ImportWallet'
import { WatchWallet } from 'e2e/usecases/onboarding/WatchWallet'
describe('Onboarding', () => { describe('Onboarding', () => {
beforeAll(async () => { beforeEach(async () => {
await device.launchApp({ newInstance: true }) await device.launchApp({ newInstance: true })
await device.reloadReactNative()
}) })
describe(CreateNewWallet, CreateNewWallet) afterEach(async () => {
await device.uninstallApp()
await device.installApp()
})
it('creates a new wallet', CreateNewWallet)
it('watches wallet', WatchWallet)
it('imports a testing wallet using recovery phrase', ImportWallet)
}) })
import { WatchWallet } from 'e2e/usecases/onboarding/WatchWallet'
import { SwapBasicInteractions } from 'e2e/usecases/swap/SwapBasicInteractions'
describe('Swap', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true })
await WatchWallet()
})
it('tests swap screen interactions', SwapBasicInteractions)
})
import { by, element, expect } from 'detox'
import { TestWallet } from 'e2e/utils/fixtures'
import { ElementName } from 'wallet/src/telemetry/constants'
export async function CreateNewWallet(): Promise<void> {
// Selects "Create a new wallet" option on the landing screen
await element(by.id(ElementName.CreateAccount)).tap()
// Skips unitag flow
await element(by.id(ElementName.Skip)).tap()
// Taps "Let's keep it safe" on QRAnimation screen
await element(by.id(ElementName.Next)).tap()
// Check is both manual and cloud backup options are available on BackupScreen
await expect(element(by.id(ElementName.AddCloudBackup))).toBeVisible()
await expect(element(by.id(ElementName.AddManualBackup))).toBeVisible()
// Picks "Manual backup" option
await element(by.id(ElementName.AddManualBackup)).tap()
// Checks if ManualBackupScreen warning displays and taps "I'm ready" button
await expect(element(by.id(ElementName.Confirm))).toBeVisible()
await element(by.id(ElementName.Confirm)).tap()
// Taps continue on ManualBackupScreen
await element(by.id(ElementName.Next)).tap()
// Taps continue on manual backup confirmation screen. It is replaced by mock because detox
// can't interact with native screens
await element(by.id(ElementName.Continue)).tap()
// Skips notification setup by tapping "Maybe later" button
await element(by.id(ElementName.Skip)).tap()
// Skips biometrics setup by tapping "Maybe later" button
await element(by.id(ElementName.Skip)).tap()
// Confirms by tapping "Skip" on warning modal
await element(by.id(ElementName.Confirm)).tap()
// Confirms if user successfuly finished create new wallet flow by checking if provided wallet name is
// displayed and other
await expect(element(by.text(TestWallet.name))).toBeVisible()
await expect(element(by.id(ElementName.Swap))).toBeVisible()
await expect(element(by.id(ElementName.SearchTokensAndWallets))).toBeVisible()
}
import { by, element, expect } from 'detox'
import { TestWallet } from 'e2e/utils/fixtures'
import { ElementName } from 'wallet/src/telemetry/constants'
export async function ImportWallet(): Promise<void> {
// Selects "Add an existing wallet" option on the landing screen
await element(by.id(ElementName.ImportAccount)).tap()
// Picks Import a wallet by recovery phase option
await element(by.id(ElementName.OnboardingImportSeedPhrase)).tap()
// Checks if recovery phase input is in focus and types recovery phrase in
await expect(element(by.id(ElementName.ImportAccountInput))).toBeFocused()
await element(by.id(ElementName.ImportAccountInput)).typeText(TestWallet.recoveryPhrase)
// Taps continue navigating to SelectWalletScreen
await element(by.id(ElementName.Continue)).tap()
// Taps continue on SelectWalletScreen
await waitFor(element(by.id(`${ElementName.WalletCard}-1`)))
.toBeVisible()
.withTimeout(10000)
await element(by.id(ElementName.Next)).tap()
// Skips cloud backup step on BackupScreen by clicking "Maybe later"
await expect(element(by.id(ElementName.AddCloudBackup))).toBeVisible()
await element(by.id(ElementName.Next)).tap()
// Skips notification setup by tapping "Maybe later" button
await element(by.id(ElementName.Skip)).tap()
// Skips biometrics setup by tapping "Maybe later" button
await element(by.id(ElementName.Skip)).tap()
// Confirms by tapping "Skip" on warning modal
await element(by.id(ElementName.Confirm)).tap()
// Confirms if user successfuly finished create new wallet flow by checking if provided wallet name is
// displayed and other
await expect(element(by.text(TestWallet.name))).toBeVisible()
await expect(element(by.id(ElementName.Swap))).toBeVisible()
await expect(element(by.id(ElementName.SearchTokensAndWallets))).toBeVisible()
}
import { by, element, expect } from 'detox'
import { TestWatchedWallet } from 'e2e/utils/fixtures'
import { ElementName } from 'wallet/src/telemetry/constants'
export async function WatchWallet(): Promise<void> {
// Selects "Add an existing wallet" option on the landing screen
await element(by.id(ElementName.ImportAccount)).tap()
// Picks Watch a wallet option on ImportMethodScreen
await element(by.id(ElementName.WatchWallet)).tap()
// Checks if wallet name is in focus and types recovery phrase in
await expect(element(by.id(ElementName.ImportAccountInput))).toBeFocused()
await element(by.id(ElementName.ImportAccountInput)).typeText(TestWatchedWallet.ens)
// Confirms the entered wallet name by tapping "continue"
await element(by.id(ElementName.Next)).tap()
// Checks if Home screen is displayed with a proper user name
await expect(element(by.text(TestWatchedWallet.displayName))).toBeVisible()
await expect(element(by.id(ElementName.Swap))).toBeVisible()
await expect(element(by.id(ElementName.SearchTokensAndWallets))).toBeVisible()
}
import { by, element, expect } from 'detox'
import { TestWatchedWallet } from 'e2e/utils/fixtures'
import { ElementName } from 'wallet/src/telemetry/constants'
export async function SwapBasicInteractions(): Promise<void> {
// Navigate to swap screen
await element(by.id(ElementName.Swap)).tap()
// Checks if currency input is selected
await expect(element(by.id(ElementName.AmountInputIn))).toBeFocused()
// Checks if "Max" button is available
await expect(element(by.id(ElementName.SetMaxInput))).toBeVisible()
// Opens token selector modal on Swap screen
await element(by.id(ElementName.ChooseOutputToken)).tap()
// Picks usdc output token
await element(by.text('USDC')).atIndex(0).tap()
// Taps 1234567890 number into swap input
await element(by.id('decimal-pad-1')).tap()
await element(by.id('decimal-pad-2')).tap()
await element(by.id('decimal-pad-3')).tap()
await element(by.id('decimal-pad-4')).tap()
await element(by.id('decimal-pad-5')).tap()
await element(by.id('decimal-pad-6')).tap()
await element(by.id('decimal-pad-7')).tap()
await element(by.id('decimal-pad-8')).tap()
await element(by.id('decimal-pad-.')).tap()
await element(by.id('decimal-pad-0')).tap()
await element(by.id('decimal-pad-9')).tap()
await element(by.id('decimal-pad-1')).tap()
await element(by.id('decimal-pad-backspace')).tap()
// Checks if expected input expected value: "12345678.09"
await expect(element(by.id(ElementName.AmountInputIn))).toHaveValue('12345678.09')
// Checks if expected error is displayed
await expect(element(by.text('You don’t have enough ETH'))).toBeVisible()
// Checks if expected output expected value: "0"
await expect(element(by.id(ElementName.AmountInputOut))).not.toHaveValue('0')
// Swaps input and output currencies
await element(by.id(ElementName.SwitchCurrenciesButton)).tap()
// Checks if expected input expected value: "0"
await expect(element(by.id(ElementName.AmountInputIn))).toHaveValue('0')
// Checks if expected error is displayed
await expect(element(by.text('Not enough liquidity'))).toBeVisible()
// Checks if expected output expected value: "12345678.09"
await expect(element(by.id(ElementName.AmountInputOut))).toHaveValue('12345678.09')
// Swaps input and output currencies
await element(by.id(ElementName.SwitchCurrenciesButton)).tap()
// Selects currency output
await element(by.id(ElementName.AmountInputOut)).tap()
// Clears the output field
await element(by.id(ElementName.AmountInputOut)).clearText()
await element(by.id('decimal-pad-1')).tap()
await element(by.id('decimal-pad-2')).tap()
await element(by.id('decimal-pad-3')).tap()
// Checks if output has expected value: "123"
await expect(element(by.id(ElementName.AmountInputOut))).toHaveValue('123')
// Checks if expected input value to be cleared
await expect(element(by.id(ElementName.AmountInputIn))).not.toHaveValue('0')
// Checks dollar value to be visible
await expect(element(by.text('$123.00'))).toBeVisible()
// Swipes swap modal by dragging down SwapFormHeader
await element(by.id(ElementName.SwapFormHeader)).swipe('down', 'fast', 0.75)
// Checks if Home screen is visible and not covered
await expect(element(by.text(TestWatchedWallet.displayName))).toBeVisible()
await expect(element(by.id(ElementName.Swap))).toBeVisible()
await expect(element(by.id(ElementName.SearchTokensAndWallets))).toBeVisible()
}
export const TestWallet = { export const TestWallet = {
name: 'Wallet 1', name: 'Wallet 1',
recoveryPhrase:
'oak reduce strong borrow control funny library disagree radio clarify degree pistol',
}
export const TestWatchedWallet = {
ens: 'Spenciefy',
displayName: 'spencer',
} }
import { by, device, element } from 'detox'
import { Accounts } from 'src/e2e/utils/fixtures'
import { sleep } from 'utilities/src/time/timing'
import { ElementName } from 'wallet/src/telemetry/constants'
/** Opens Account page and imports a managed account */
export async function quickOnboarding() {
await device.setBiometricEnrollment(true)
// open app, import existing account
await element(by.id(ElementName.OnboardingImportSeedPhrase)).tap()
// enter address / eth
await element(by.id('import_account_form/input')).typeText(Accounts.managed.seedPhrase)
await sleep(500)
await element(by.id(ElementName.Next)).tap()
await element(by.id(ElementName.WalletCard + '-1')).tap()
await element(by.id(ElementName.Next)).tap()
// skip notifs
await element(by.id(ElementName.Skip)).tap()
// Face ID
await element(by.id(ElementName.Enable)).tap()
await device.matchFace()
// Outro
await element(by.id(ElementName.Next)).tap()
}
export async function maybeDismissTokenWarning() {
try {
await element(by.id(ElementName.TokenWarningAccept)).tap()
} catch (e) {
// no-op
}
}
...@@ -2450,7 +2450,7 @@ ...@@ -2450,7 +2450,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -2496,7 +2496,7 @@ ...@@ -2496,7 +2496,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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;
...@@ -2542,7 +2542,7 @@ ...@@ -2542,7 +2542,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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.dev.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
...@@ -2588,7 +2588,7 @@ ...@@ -2588,7 +2588,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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;
...@@ -2630,7 +2630,7 @@ ...@@ -2630,7 +2630,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -2673,7 +2673,7 @@ ...@@ -2673,7 +2673,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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;
...@@ -2716,7 +2716,7 @@ ...@@ -2716,7 +2716,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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.dev.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
...@@ -2759,7 +2759,7 @@ ...@@ -2759,7 +2759,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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;
...@@ -2795,7 +2795,7 @@ ...@@ -2795,7 +2795,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -2833,7 +2833,7 @@ ...@@ -2833,7 +2833,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3003,7 +3003,7 @@ ...@@ -3003,7 +3003,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -3047,7 +3047,7 @@ ...@@ -3047,7 +3047,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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;
...@@ -3143,7 +3143,7 @@ ...@@ -3143,7 +3143,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3214,7 +3214,7 @@ ...@@ -3214,7 +3214,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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;
...@@ -3310,7 +3310,7 @@ ...@@ -3310,7 +3310,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3381,7 +3381,7 @@ ...@@ -3381,7 +3381,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.23; MARKETING_VERSION = 1.24;
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.dev.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
...@@ -7,10 +7,10 @@ ...@@ -7,10 +7,10 @@
"operationSearchPaths": [ "operationSearchPaths": [
"../../../apps/mobile/src/components/PriceExplorer/TokenPriceHistory.graphql", "../../../apps/mobile/src/components/PriceExplorer/TokenPriceHistory.graphql",
"../../../apps/mobile/src/components/explore/search/SearchPopularTokens.graphql", "../../../apps/mobile/src/components/explore/search/SearchPopularTokens.graphql",
"../../../packages/wallet/src/data/queries.graphql" "../../../packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql"
], ],
"schemaSearchPaths": [ "schemaSearchPaths": [
"../../../packages/wallet/src/data/schema.graphql" "../../../packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql"
] ]
}, },
"output": { "output": {
......
...@@ -100,6 +100,7 @@ ...@@ -100,6 +100,7 @@
"expo-barcode-scanner": "12.7.0", "expo-barcode-scanner": "12.7.0",
"expo-blur": "12.6.0", "expo-blur": "12.6.0",
"expo-camera": "13.4.4", "expo-camera": "13.4.4",
"expo-clipboard": "4.1.2",
"expo-haptics": "12.0.1", "expo-haptics": "12.0.1",
"expo-linear-gradient": "12.3.0", "expo-linear-gradient": "12.3.0",
"expo-linking": "4.0.1", "expo-linking": "4.0.1",
......
...@@ -45,18 +45,24 @@ import { flexStyles, useIsDarkMode } from 'ui/src' ...@@ -45,18 +45,24 @@ import { flexStyles, useIsDarkMode } from 'ui/src'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import { isDetoxBuild } from 'utilities/src/environment'
import { registerConsoleOverrides } from 'utilities/src/logger/console' import { registerConsoleOverrides } from 'utilities/src/logger/console'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks' import { useAsyncData } from 'utilities/src/react/hooks'
import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext' import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext'
import { initFirebaseAppCheck } from 'wallet/src/features/appCheck' import { initFirebaseAppCheck } from 'wallet/src/features/appCheck'
import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks' import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks'
import { EXPERIMENT_NAMES, FEATURE_FLAGS } from 'wallet/src/features/experiments/constants' import {
DUMMY_STATSIG_SDK_KEY,
EXPERIMENT_NAMES,
FEATURE_FLAGS,
} from 'wallet/src/features/experiments/constants'
import { selectFavoriteTokens } from 'wallet/src/features/favorites/selectors' 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 { updateLanguage } from 'wallet/src/features/language/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'
import { WalletContextProvider } from 'wallet/src/features/wallet/context' import { WalletContextProvider } from 'wallet/src/features/wallet/context'
...@@ -75,10 +81,7 @@ if (__DEV__) { ...@@ -75,10 +81,7 @@ if (__DEV__) {
// Construct a new instrumentation instance. This is needed to communicate between the integration and React // Construct a new instrumentation instance. This is needed to communicate between the integration and React
const routingInstrumentation = new Sentry.ReactNavigationInstrumentation() const routingInstrumentation = new Sentry.ReactNavigationInstrumentation()
// Dummy key since we use the reverse proxy will handle the real key if (!__DEV__ && !isDetoxBuild) {
const DUMMY_STATSIG_SDK_KEY = 'client-0000000000000000000000000000000000000000000'
if (!__DEV__ && !process.env.DETOX_MODE) {
Sentry.init({ Sentry.init({
environment: getSentryEnvironment(), environment: getSentryEnvironment(),
dsn: config.sentryDsn, dsn: config.sentryDsn,
...@@ -105,7 +108,7 @@ if (!__DEV__ && !process.env.DETOX_MODE) { ...@@ -105,7 +108,7 @@ if (!__DEV__ && !process.env.DETOX_MODE) {
// Log boxes on simulators can block detox tap event when they cover buttons placed at // Log boxes on simulators can block detox tap event when they cover buttons placed at
// the bottom of the screen and cause tests to fail. // the bottom of the screen and cause tests to fail.
if (process.env.DETOX_MODE) { if (isDetoxBuild) {
LogBox.ignoreAllLogs() LogBox.ignoreAllLogs()
} }
...@@ -259,6 +262,7 @@ function AppInner(): JSX.Element { ...@@ -259,6 +262,7 @@ function AppInner(): JSX.Element {
}, [allowAnalytics]) }, [allowAnalytics])
useEffect(() => { useEffect(() => {
dispatch(clearNotificationQueue()) // clear all in-app toasts on app start
dispatch(updateLanguage(null)) dispatch(updateLanguage(null))
}, [dispatch]) }, [dispatch])
......
...@@ -58,6 +58,7 @@ import { ...@@ -58,6 +58,7 @@ import {
v56Schema, v56Schema,
v57Schema, v57Schema,
v58Schema, v58Schema,
v59Schema,
v5Schema, v5Schema,
v6Schema, v6Schema,
v7Schema, v7Schema,
...@@ -69,7 +70,7 @@ import { ScannerModalState } from 'src/components/QRCodeScanner/constants' ...@@ -69,7 +70,7 @@ import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { initialBiometricsSettingsState } from 'src/features/biometrics/slice' import { initialBiometricsSettingsState } from 'src/features/biometrics/slice'
import { initialCloudBackupState } from 'src/features/CloudBackup/cloudBackupSlice' import { initialCloudBackupState } from 'src/features/CloudBackup/cloudBackupSlice'
import { initialPasswordLockoutState } from 'src/features/CloudBackup/passwordLockoutSlice' import { initialPasswordLockoutState } from 'src/features/CloudBackup/passwordLockoutSlice'
import { initialModalState } from 'src/features/modals/modalSlice' import { initialModalsState } from 'src/features/modals/modalSlice'
import { initialTelemetryState } from 'src/features/telemetry/slice' import { initialTelemetryState } from 'src/features/telemetry/slice'
import { initialTweaksState } from 'src/features/tweaks/slice' import { initialTweaksState } from 'src/features/tweaks/slice'
import { initialWalletConnectState } from 'src/features/walletConnect/walletConnectSlice' import { initialWalletConnectState } from 'src/features/walletConnect/walletConnectSlice'
...@@ -177,7 +178,7 @@ describe('Redux state migrations', () => { ...@@ -177,7 +178,7 @@ describe('Redux state migrations', () => {
favorites: initialFavoritesState, favorites: initialFavoritesState,
fiatCurrencySettings: initialFiatCurrencyState, fiatCurrencySettings: initialFiatCurrencyState,
languageSettings: initialLanguageState, languageSettings: initialLanguageState,
modals: initialModalState, modals: initialModalsState,
notifications: initialNotificationsState, notifications: initialNotificationsState,
passwordLockout: initialPasswordLockoutState, passwordLockout: initialPasswordLockoutState,
behaviorHistory: initialBehaviorHistoryState, behaviorHistory: initialBehaviorHistoryState,
...@@ -1325,4 +1326,11 @@ describe('Redux state migrations', () => { ...@@ -1325,4 +1326,11 @@ describe('Redux state migrations', () => {
expect(v59.behaviorHistory.hasCompletedUnitagsIntroModal).toBe(false) expect(v59.behaviorHistory.hasCompletedUnitagsIntroModal).toBe(false)
}) })
it('migrates from v59 to 60', () => {
const v59Stub = { ...v59Schema }
const v60 = migrations[60](v59Stub)
expect(v60.behaviorHistory.hasViewedUniconV2IntroModal).toBe(false)
})
}) })
...@@ -804,4 +804,15 @@ export const migrations = { ...@@ -804,4 +804,15 @@ export const migrations = {
return newState return newState
}, },
60: function addUniconV2IntroModalBoolean(state: any) {
const newState = { ...state }
newState.behaviorHistory = {
...state.behaviorHistory,
hasViewedUniconV2IntroModal: false,
}
return newState
},
} }
import React from 'react' import React from 'react'
import { act } from 'react-test-renderer'
import { PreloadedState } from 'redux'
import { AccountSwitcher } from 'src/app/modals/AccountSwitcherModal' import { AccountSwitcher } from 'src/app/modals/AccountSwitcherModal'
import { MobileState } from 'src/app/reducer' import { preloadedMobileState, preloadedModalsState } from 'src/test/fixtures'
import { initialModalState } from 'src/features/modals/modalSlice' import { cleanup, render } from 'src/test/test-utils'
import { render } from 'src/test/test-utils'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { ACCOUNT } from 'wallet/src/test/fixtures' import { ACCOUNT } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState, noOpFunction } from 'wallet/src/test/mocks' import { noOpFunction } from 'wallet/src/test/mocks'
const preloadedState = { const preloadedState = preloadedMobileState({
...mockWalletPreloadedState(ACCOUNT), account: ACCOUNT,
modals: { modals: preloadedModalsState({
...initialModalState,
[ModalName.AccountSwitcher]: { isOpen: true }, [ModalName.AccountSwitcher]: { isOpen: true },
}, }),
} as unknown as PreloadedState<MobileState> })
// TODO [MOB-259]: Figure out how to do snapshot tests when there is a BottomSheetModal // TODO [MOB-259]: Figure out how to do snapshot tests when there is a BottomSheetModal
describe(AccountSwitcher, () => { describe(AccountSwitcher, () => {
it('renders correctly', async () => { it('renders correctly', async () => {
const tree = render(<AccountSwitcher onClose={noOpFunction} />, { preloadedState }) const tree = render(<AccountSwitcher onClose={noOpFunction} />, { preloadedState })
await act(async () => {
// Wait until the component is rendered
})
expect(tree.toJSON()).toMatchSnapshot() expect(tree.toJSON()).toMatchSnapshot()
cleanup()
}) })
}) })
import React from 'react'
import { useTranslation } from 'react-i18next'
import 'react-native-reanimated'
import { useAppDispatch } from 'src/app/hooks'
import {
Button,
Flex,
Icons,
Text,
Unicon,
UniconV2,
useDeviceInsets,
useIsDarkMode,
useUniconColors,
} from 'ui/src'
import { spacing } from 'ui/src/theme'
import { UniconGradient } from 'wallet/src/components/accounts/AccountIcon'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { setHasViewedUniconV2IntroModal } from 'wallet/src/features/behaviorHistory/slice'
import { ModalName } from 'wallet/src/telemetry/constants'
interface UniconsV2ModalProps {
address: string
}
const UNICON_HEADER_SIZE = 52
const UNICON_SIZE = 42
const UNICON_PADDING = 14
export function UniconsV2Modal({ address }: UniconsV2ModalProps): JSX.Element {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const isDarkMode = useIsDarkMode()
const { gradientEnd: uniconColor } = useUniconColors(address)
const { top } = useDeviceInsets()
const onCloseModal = (): void => {
dispatch(setHasViewedUniconV2IntroModal(true))
}
return (
<>
<BottomSheetModal hideScrim name={ModalName.UniconsV2} onClose={onCloseModal}>
<Flex gap="$spacing24" px="$spacing24" py="$spacing16">
<Flex centered row gap="$spacing8">
<Flex centered height={UNICON_SIZE} width={UNICON_SIZE}>
<Unicon address={address} size={UNICON_SIZE - UNICON_PADDING} />
<UniconGradient color={uniconColor} size={UNICON_SIZE} />
</Flex>
<Icons.RightArrow color="$neutral3" size="$icon.16" />
<UniconV2 address={address} size={UNICON_SIZE} />
</Flex>
<Flex alignItems="center" gap="$spacing12">
<Text variant="subheading1">{t('unicons.banner.title')}</Text>
<Text color="$neutral2" textAlign="center" variant="body2">
{t('unicons.banner.subtitle')}
</Text>
</Flex>
<Button theme="secondary" onPress={onCloseModal}>
{t('unicons.banner.button')}
</Button>
</Flex>
</BottomSheetModal>
<Flex
backgroundColor={isDarkMode ? '$surface3' : '$surface1'}
borderRadius="$roundedFull"
bottom={0}
height={UNICON_HEADER_SIZE}
left={spacing.spacing24}
mt="$spacing8"
position="absolute"
right={0}
top={top}
width={UNICON_HEADER_SIZE}
zIndex="$tooltip">
<UniconV2 address={address} size={UNICON_HEADER_SIZE} />
</Flex>
</>
)
}
...@@ -50,6 +50,7 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -50,6 +50,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
"position": "relative", "position": "relative",
} }
} }
testID="account-icon"
> >
<View <View
style={ style={
......
import { PreloadedState } from '@reduxjs/toolkit'
import React from 'react' import React from 'react'
import { Text } from 'react-native' import { Text } from 'react-native'
import { LazyModalRenderer } from 'src/app/modals/utils' import { LazyModalRenderer } from 'src/app/modals/utils'
import { MobileState } from 'src/app/reducer' import { preloadedMobileState, preloadedModalsState } from 'src/test/fixtures'
import { initialModalState } from 'src/features/modals/modalSlice'
import { renderWithProviders } from 'src/test/render' import { renderWithProviders } from 'src/test/render'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
const preloadedState = {
...mockWalletPreloadedState,
modals: initialModalState,
} as unknown as PreloadedState<MobileState>
describe(LazyModalRenderer, () => { describe(LazyModalRenderer, () => {
it('renders null when modal is not open', () => { it('renders null when modal is not open', () => {
...@@ -19,26 +11,24 @@ describe(LazyModalRenderer, () => { ...@@ -19,26 +11,24 @@ describe(LazyModalRenderer, () => {
<LazyModalRenderer name={ModalName.Experiments}> <LazyModalRenderer name={ModalName.Experiments}>
<Text>Rendered</Text> <Text>Rendered</Text>
</LazyModalRenderer>, </LazyModalRenderer>,
{ preloadedState } { preloadedState: preloadedMobileState() }
) )
expect(tree.toJSON()).toBeNull() expect(tree.toJSON()).toBeNull()
}) })
it('renders modal when modal is open', () => { it('renders modal when modal is open', () => {
const state = {
...preloadedState,
modals: {
...initialModalState,
[ModalName.Experiments]: { isOpen: true },
},
}
const tree = renderWithProviders( const tree = renderWithProviders(
<LazyModalRenderer name={ModalName.Experiments}> <LazyModalRenderer name={ModalName.Experiments}>
<Text>Rendered</Text> <Text>Rendered</Text>
</LazyModalRenderer>, </LazyModalRenderer>,
{ preloadedState: state } {
preloadedState: preloadedMobileState({
modals: preloadedModalsState({
[ModalName.Experiments]: { isOpen: true },
}),
}),
}
) )
expect(tree.toJSON()).toMatchInlineSnapshot(` expect(tree.toJSON()).toMatchInlineSnapshot(`
......
...@@ -4,7 +4,7 @@ import { navigate as rootNavigate } from 'src/app/navigation/rootNavigation' ...@@ -4,7 +4,7 @@ import { navigate as rootNavigate } from 'src/app/navigation/rootNavigation'
import { useAppStackNavigation, useExploreStackNavigation } from 'src/app/navigation/types' import { useAppStackNavigation, useExploreStackNavigation } from 'src/app/navigation/types'
import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex' import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { useTransactionListLazyQuery } from 'wallet/src/data/__generated__/types-and-hooks' import { useTransactionListLazyQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
/** /**
* Utility hook to simplify navigating to Activity screen. * Utility hook to simplify navigating to Activity screen.
......
import { PersistState } from 'redux-persist' import { PersistState } from 'redux-persist'
import { apolloClient } from 'src/data/usePersistedApolloClient' import { apolloClientRef } from 'src/data/usePersistedApolloClient'
import { appRatingWatcherSaga } from 'src/features/appRating/saga' import { appRatingWatcherSaga } from 'src/features/appRating/saga'
import { cloudBackupsManagerSaga } from 'src/features/CloudBackup/saga' import { cloudBackupsManagerSaga } from 'src/features/CloudBackup/saga'
import { deepLinkWatcher } from 'src/features/deepLinking/handleDeepLinkSaga' import { deepLinkWatcher } from 'src/features/deepLinking/handleDeepLinkSaga'
...@@ -9,7 +9,7 @@ import { telemetrySaga } from 'src/features/telemetry/saga' ...@@ -9,7 +9,7 @@ import { telemetrySaga } from 'src/features/telemetry/saga'
import { restoreMnemonicCompleteWatcher } from 'src/features/wallet/saga' import { restoreMnemonicCompleteWatcher } from 'src/features/wallet/saga'
import { walletConnectSaga } from 'src/features/walletConnect/saga' import { walletConnectSaga } from 'src/features/walletConnect/saga'
import { signWcRequestSaga } from 'src/features/walletConnect/signWcRequestSaga' import { signWcRequestSaga } from 'src/features/walletConnect/signWcRequestSaga'
import { delay, select, spawn } from 'typed-redux-saga' import { call, delay, select, spawn } from 'typed-redux-saga'
import { appLanguageWatcherSaga } from 'wallet/src/features/language/saga' import { appLanguageWatcherSaga } from 'wallet/src/features/language/saga'
import { import {
swapActions, swapActions,
...@@ -115,6 +115,8 @@ export function* mobileSaga() { ...@@ -115,6 +115,8 @@ export function* mobileSaga() {
yield* spawn(s) yield* spawn(s)
} }
const apolloClient = yield* call(apolloClientRef.onReady)
yield* spawn(transactionWatcher, { apolloClient }) yield* spawn(transactionWatcher, { apolloClient })
for (const m of Object.values(monitoredSagas)) { for (const m of Object.values(monitoredSagas)) {
......
...@@ -442,6 +442,14 @@ export const v59Schema = { ...@@ -442,6 +442,14 @@ export const v59Schema = {
hasCompletedUnitagsIntroModal: false, hasCompletedUnitagsIntroModal: false,
}, },
} }
export const v60Schema = {
...v59Schema,
behaviorHistory: {
...v59Schema.behaviorHistory,
hasViewedUniconV2IntroModal: false,
},
}
// TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer // TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer
// export const getSchema = (): RootState => v0Schema // export const getSchema = (): RootState => v0Schema
export const getSchema = (): typeof v57Schema => v57Schema export const getSchema = (): typeof v59Schema => v59Schema
...@@ -75,7 +75,7 @@ export const persistConfig = { ...@@ -75,7 +75,7 @@ export const persistConfig = {
key: 'root', key: 'root',
storage: reduxStorage, storage: reduxStorage,
whitelist, whitelist,
version: 59, version: 60,
migrate: createMigrate(migrations), migrate: createMigrate(migrations),
} }
......
import { Flex, UniconV2 } from 'ui/src'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { ModalName } from 'wallet/src/telemetry/constants'
const generateRandomEthereumAddresses = (numberOfAddresses: number): string[] => {
const addresses = []
for (let i = 0; i < numberOfAddresses; i++) {
const randomHex = [...Array(40)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
addresses.push(`0x${randomHex}`)
}
return addresses
}
export const UniconSampleSheet = ({ onClose }: { onClose: () => void }): JSX.Element => {
return (
<BottomSheetModal
blurredBackground
backgroundColor="$surface1"
name={ModalName.UniconsDevModal}
onClose={onClose}>
<Flex centered height="100%" width="100%">
<Flex row alignItems="center" flexWrap="wrap" justifyContent="center" width="100%">
{generateRandomEthereumAddresses(80).map((address) => {
return (
<Flex>
<UniconV2 key={address} address={address} size={42} />
</Flex>
)
})}
</Flex>
</Flex>
</BottomSheetModal>
)
}
...@@ -65,7 +65,7 @@ export const AnimatedDecimalNumber = memo(function AnimatedDecimalNumber( ...@@ -65,7 +65,7 @@ export const AnimatedDecimalNumber = memo(function AnimatedDecimalNumber(
return { return {
color: number.value.value < decimalThreshold ? wholePartColor : decimalPartColor, color: number.value.value < decimalThreshold ? wholePartColor : decimalPartColor,
} }
}, [decimalThreshold, wholePartColor, decimalPartColor]) }, [number.value, decimalThreshold, wholePartColor, decimalPartColor])
const fontSize = fonts[variant].fontSize * fontScale const fontSize = fonts[variant].fontSize * fontScale
// Choose the arbitrary value that looks good for the font used // Choose the arbitrary value that looks good for the font used
......
...@@ -14,7 +14,7 @@ import { Loader } from 'src/components/loading' ...@@ -14,7 +14,7 @@ import { Loader } from 'src/components/loading'
import { invokeImpact } from 'src/utils/haptic' import { invokeImpact } from 'src/utils/haptic'
import { Flex } from 'ui/src' import { Flex } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { HistoryDuration } from 'wallet/src/data/__generated__/types-and-hooks' import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks' import { useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext' import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { CurrencyId } from 'wallet/src/utils/currencyId' import { CurrencyId } from 'wallet/src/utils/currencyId'
......
...@@ -135,7 +135,7 @@ const RollNumber = ({ ...@@ -135,7 +135,7 @@ const RollNumber = ({
const char = chars.value[index - (commaIndex - decimalPlace.value)] const char = chars.value[index - (commaIndex - decimalPlace.value)]
const number = char ? parseFloat(char) : undefined const number = char ? parseFloat(char) : undefined
return Number.isNaN(number) ? undefined : number return Number.isNaN(number) ? undefined : number
}, [chars]) }, [chars, commaIndex, decimalPlace, index])
const animatedFontStyle = useAnimatedStyle(() => { const animatedFontStyle = useAnimatedStyle(() => {
return { return {
...@@ -156,7 +156,7 @@ const RollNumber = ({ ...@@ -156,7 +156,7 @@ const RollNumber = ({
restSpeedThreshold: 2, restSpeedThreshold: 2,
}) })
: endValue : endValue
}, [shouldAnimate]) }, [animatedDigit, shouldAnimate])
const animatedWrapperStyle = useAnimatedStyle(() => { const animatedWrapperStyle = useAnimatedStyle(() => {
const digitWidth = const digitWidth =
...@@ -261,7 +261,7 @@ const Numbers = ({ ...@@ -261,7 +261,7 @@ const Numbers = ({
const decimalPlace = useDerivedValue(() => { const decimalPlace = useDerivedValue(() => {
return price.formatted.value.indexOf(currency.decimalSeparator) return price.formatted.value.indexOf(currency.decimalSeparator)
}, [price]) }, [currency.decimalSeparator, price.formatted])
const commaIndex = numberOfDigits.left + Math.floor((numberOfDigits.left - 1) / 3) const commaIndex = numberOfDigits.left + Math.floor((numberOfDigits.left - 1) / 3)
......
...@@ -10,7 +10,7 @@ import { TIME_RANGES } from 'src/components/PriceExplorer/constants' ...@@ -10,7 +10,7 @@ import { TIME_RANGES } from 'src/components/PriceExplorer/constants'
import { useChartDimensions } from 'src/components/PriceExplorer/useChartDimensions' import { useChartDimensions } from 'src/components/PriceExplorer/useChartDimensions'
import Trace from 'src/components/Trace/Trace' import Trace from 'src/components/Trace/Trace'
import { AnimatedFlex, AnimatedText, Flex, TouchableArea, useSporeColors } from 'ui/src' import { AnimatedFlex, AnimatedText, Flex, TouchableArea, useSporeColors } from 'ui/src'
import { HistoryDuration } from 'wallet/src/data/__generated__/types-and-hooks' import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
interface Props { interface Props {
label: string label: string
...@@ -70,7 +70,7 @@ export function TimeRangeGroup({ ...@@ -70,7 +70,7 @@ export function TimeRangeGroup({
}, },
], ],
}), }),
[adjustedLabelWidth] [adjustedLabelWidth, buttonWidth, currentIndex, isRTL]
) )
return ( return (
......
import { HistoryDuration } from 'wallet/src/data/__generated__/types-and-hooks' import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import i18n from 'wallet/src/i18n/i18n' import i18n from 'wallet/src/i18n/i18n'
import { ElementName } from 'wallet/src/telemetry/constants' import { ElementName } from 'wallet/src/telemetry/constants'
......
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
HistoryDuration, HistoryDuration,
TimestampedAmount, TimestampedAmount,
TokenProject as TokenProjectType, TokenProject as TokenProjectType,
} from 'wallet/src/data/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { import {
SAMPLE_CURRENCY_ID_1, SAMPLE_CURRENCY_ID_1,
getLatestPrice, getLatestPrice,
......
...@@ -2,13 +2,13 @@ import { maxBy } from 'lodash' ...@@ -2,13 +2,13 @@ import { maxBy } from 'lodash'
import { Dispatch, SetStateAction, useCallback, useMemo, useRef, useState } from 'react' import { Dispatch, SetStateAction, useCallback, useMemo, useRef, useState } from 'react'
import { SharedValue } from 'react-native-reanimated' import { SharedValue } from 'react-native-reanimated'
import { TLineChartData } from 'react-native-wagmi-charts' import { TLineChartData } from 'react-native-wagmi-charts'
import { GqlResult } from 'uniswap/src/data/types'
import { PollingInterval } from 'wallet/src/constants/misc'
import { import {
HistoryDuration, HistoryDuration,
TimestampedAmount, TimestampedAmount,
useTokenPriceHistoryQuery, useTokenPriceHistoryQuery,
} from 'wallet/src/data/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { GqlResult } from 'uniswap/src/data/types'
import { PollingInterval } from 'wallet/src/constants/misc'
import { isError, isNonPollingRequestInFlight } from 'wallet/src/data/utils' import { isError, isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils' import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext' import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
......
...@@ -4,10 +4,10 @@ import QRCode from 'src/components/QRCodeScanner/custom-qr-code-generator' ...@@ -4,10 +4,10 @@ import QRCode from 'src/components/QRCodeScanner/custom-qr-code-generator'
import { import {
ColorTokens, ColorTokens,
Flex, Flex,
getUniconV2Colors,
useIsDarkMode, useIsDarkMode,
useSporeColors, useSporeColors,
useUniconColors, useUniconColors,
useUniconV2Colors,
} from 'ui/src' } from 'ui/src'
import { borderRadii } from 'ui/src/theme' import { borderRadii } from 'ui/src/theme'
...@@ -39,7 +39,7 @@ const useColorProps = (address: Address, color?: string): ColorProps => { ...@@ -39,7 +39,7 @@ const useColorProps = (address: Address, color?: string): ColorProps => {
const gradientData = useUniconColors(address) const gradientData = useUniconColors(address)
const isUniconsV2Enabled = useFeatureFlag(FEATURE_FLAGS.UniconsV2) const isUniconsV2Enabled = useFeatureFlag(FEATURE_FLAGS.UniconsV2)
const isDarkMode = useIsDarkMode() const isDarkMode = useIsDarkMode()
const uniconV2Color = useUniconV2Colors(address, isDarkMode) as { color: string } const uniconV2Color = getUniconV2Colors(address, isDarkMode) as { color: string }
const { avatar, loading: avatarLoading } = useAvatar(address) const { avatar, loading: avatarLoading } = useAvatar(address)
const { colors: avatarColors } = useExtractedColors(avatar) as { colors: AvatarColors } const { colors: avatarColors } = useExtractedColors(avatar) as { colors: AvatarColors }
const hasAvatar = !!avatar && !avatarLoading const hasAvatar = !!avatar && !avatarLoading
......
import { BarCodeScanner, BarCodeScannerResult } from 'expo-barcode-scanner' import { BarCodeScanner } from 'expo-barcode-scanner'
import { Camera, CameraType } from 'expo-camera' import { BarCodeScanningResult, Camera, CameraType } from 'expo-camera'
import { PermissionStatus } from 'expo-modules-core' import { PermissionStatus } from 'expo-modules-core'
import React, { memo, useCallback, useMemo, useState } from 'react' import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Alert, LayoutChangeEvent, LayoutRectangle, StyleSheet } from 'react-native' import { Alert, LayoutChangeEvent, LayoutRectangle, StyleSheet } from 'react-native'
import { launchImageLibrary } from 'react-native-image-picker' import { launchImageLibrary } from 'react-native-image-picker'
...@@ -19,7 +19,7 @@ import { ...@@ -19,7 +19,7 @@ import {
} from 'ui/src' } from 'ui/src'
import CameraScan from 'ui/src/assets/icons/camera-scan.svg' import CameraScan from 'ui/src/assets/icons/camera-scan.svg'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import { useAsyncData } from 'utilities/src/react/hooks' import { Sentry } from 'utilities/src/logger/Sentry'
import PasteButton from 'wallet/src/components/buttons/PasteButton' import PasteButton from 'wallet/src/components/buttons/PasteButton'
import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader' import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader'
import { openSettings } from 'wallet/src/utils/linking' import { openSettings } from 'wallet/src/utils/linking'
...@@ -60,7 +60,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -60,7 +60,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
const [bottomLayout, setBottomLayout] = useState<LayoutRectangle | null>() const [bottomLayout, setBottomLayout] = useState<LayoutRectangle | null>()
const handleBarCodeScanned = useCallback( const handleBarCodeScanned = useCallback(
(result: BarCodeScannerResult): void => { (result: BarCodeScanningResult): void => {
if (shouldFreezeCamera) { if (shouldFreezeCamera) {
return return
} }
...@@ -103,10 +103,18 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -103,10 +103,18 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
handleBarCodeScanned(result) handleBarCodeScanned(result)
}, [handleBarCodeScanned, isReadingImageFile, t]) }, [handleBarCodeScanned, isReadingImageFile, t])
// Check for camera permissions, handle cases where not granted or undetermined useEffect(() => {
const getPermissionStatuses = useCallback(async (): Promise<void> => { Sentry.addBreadCrumb({
level: 'info',
category: 'camera',
message: 'QRCodeScannera camera permission status',
data: {
permissionStatus,
},
})
if (permissionStatus === PermissionStatus.UNDETERMINED) { if (permissionStatus === PermissionStatus.UNDETERMINED) {
await requestPermissionResponse() requestPermissionResponse().catch(() => {})
} }
if (permissionStatus === PermissionStatus.DENIED) { if (permissionStatus === PermissionStatus.DENIED) {
...@@ -119,8 +127,6 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -119,8 +127,6 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
} }
}, [permissionStatus, requestPermissionResponse, t]) }, [permissionStatus, requestPermissionResponse, t])
useAsyncData(getPermissionStatuses)
const overlayWidth = (overlayLayout?.height ?? 0) / CAMERA_ASPECT_RATIO const overlayWidth = (overlayLayout?.height ?? 0) / CAMERA_ASPECT_RATIO
const scannerSize = Math.min(overlayWidth, dimensions.fullWidth) * SCAN_ICON_WIDTH_RATIO const scannerSize = Math.min(overlayWidth, dimensions.fullWidth) * SCAN_ICON_WIDTH_RATIO
...@@ -143,7 +149,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -143,7 +149,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
}} }}
style={StyleSheet.absoluteFillObject} style={StyleSheet.absoluteFillObject}
type={CameraType.back} type={CameraType.back}
onBarCodeScanned={shouldFreezeCamera ? undefined : handleBarCodeScanned} onBarCodeScanned={handleBarCodeScanned}
/> />
)} )}
</Flex> </Flex>
......
...@@ -2,9 +2,9 @@ import React, { useMemo } from 'react' ...@@ -2,9 +2,9 @@ import React, { useMemo } from 'react'
import { ScrollView, StyleSheet } from 'react-native' import { ScrollView, StyleSheet } from 'react-native'
import { Flex, Text, useDeviceDimensions } from 'ui/src' import { Flex, Text, useDeviceDimensions } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { AccountListQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { AccountListQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { useAccountList } from 'wallet/src/features/accounts/hooks' import { useAccountList } from 'wallet/src/features/accounts/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext' import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { Account } from 'wallet/src/features/wallet/accounts/types' import { Account } from 'wallet/src/features/wallet/accounts/types'
......
...@@ -7,7 +7,7 @@ import AlertTriangleIcon from 'ui/src/assets/icons/alert-triangle.svg' ...@@ -7,7 +7,7 @@ import AlertTriangleIcon from 'ui/src/assets/icons/alert-triangle.svg'
import TrashIcon from 'ui/src/assets/icons/trash.svg' import TrashIcon from 'ui/src/assets/icons/trash.svg'
import WalletIcon from 'ui/src/assets/icons/wallet-filled.svg' import WalletIcon from 'ui/src/assets/icons/wallet-filled.svg'
import { ThemeNames } from 'ui/src/theme' import { ThemeNames } from 'ui/src/theme'
import { getCloudProviderName } from 'uniswap/src/utils/platform' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types' import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types'
import { useDisplayName } from 'wallet/src/features/wallet/hooks' import { useDisplayName } from 'wallet/src/features/wallet/hooks'
......
import React from 'react' import React from 'react'
import { Flex, flexStyles, Text, TouchableArea } from 'ui/src' import { Flex, flexStyles, Text, TouchableArea } from 'ui/src'
import { iconSizes, imageSizes } from 'ui/src/theme' import { iconSizes, imageSizes } from 'ui/src/theme'
import {
SafetyLevel,
TokenDetailsScreenQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import WarningIcon from 'wallet/src/components/icons/WarningIcon' import WarningIcon from 'wallet/src/components/icons/WarningIcon'
import { SafetyLevel, TokenDetailsScreenQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils' import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
export interface TokenDetailsHeaderProps { export interface TokenDetailsHeaderProps {
......
...@@ -5,8 +5,8 @@ import { getBlockExplorerIcon } from 'src/components/icons/BlockExplorerIcon' ...@@ -5,8 +5,8 @@ import { getBlockExplorerIcon } from 'src/components/icons/BlockExplorerIcon'
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import GlobeIcon from 'ui/src/assets/icons/globe-filled.svg' import GlobeIcon from 'ui/src/assets/icons/globe-filled.svg'
import TwitterIcon from 'ui/src/assets/icons/x-twitter.svg' import TwitterIcon from 'ui/src/assets/icons/x-twitter.svg'
import { TokenDetailsScreenQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains' import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains'
import { TokenDetailsScreenQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { ElementName } from 'wallet/src/telemetry/constants' import { ElementName } from 'wallet/src/telemetry/constants'
import { import {
currencyIdToAddress, currencyIdToAddress,
......
...@@ -4,11 +4,11 @@ import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' ...@@ -4,11 +4,11 @@ import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
import { LongText } from 'src/components/text/LongText' import { LongText } from 'src/components/text/LongText'
import { Flex, Icons, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, Icons, Text, TouchableArea, useSporeColors } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { TokenDetailsScreenQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { TokenDetailsScreenQuery } from 'wallet/src/data/__generated__/types-and-hooks' import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { Language } from 'wallet/src/features/language/constants' import { Language } from 'wallet/src/features/language/constants'
import { useCurrentLanguage, useCurrentLanguageInfo } from 'wallet/src/features/language/hooks' import { useCurrentLanguage, useCurrentLanguageInfo } from 'wallet/src/features/language/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
function StatsRow({ function StatsRow({
label, label,
......
import { useCrossChainBalances, useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks' import { useCrossChainBalances, useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { preloadedMobileState } from 'src/test/fixtures'
import { act, renderHook, waitFor } from 'src/test/test-utils' import { act, renderHook, waitFor } from 'src/test/test-utils'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils' import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
import { import {
...@@ -10,7 +11,6 @@ import { ...@@ -10,7 +11,6 @@ import {
usdcArbitrumToken, usdcArbitrumToken,
usdcBaseToken, usdcBaseToken,
} from 'wallet/src/test/fixtures' } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
import { queryResolvers } from 'wallet/src/test/utils' import { queryResolvers } from 'wallet/src/test/utils'
const mockedNavigation = { const mockedNavigation = {
...@@ -33,7 +33,7 @@ describe(useCrossChainBalances, () => { ...@@ -33,7 +33,7 @@ describe(useCrossChainBalances, () => {
describe('currentChainBalance', () => { describe('currentChainBalance', () => {
it('returns null if there are no balances for the specified currency', async () => { it('returns null if there are no balances for the specified currency', async () => {
const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), { const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), {
preloadedState: mockWalletPreloadedState(), preloadedState: preloadedMobileState(),
}) })
await act(() => undefined) await act(() => undefined)
...@@ -54,7 +54,7 @@ describe(useCrossChainBalances, () => { ...@@ -54,7 +54,7 @@ describe(useCrossChainBalances, () => {
const { result } = renderHook( const { result } = renderHook(
() => useCrossChainBalances(currentChainBalance.currencyInfo.currencyId, null), () => useCrossChainBalances(currentChainBalance.currencyInfo.currencyId, null),
{ {
preloadedState: mockWalletPreloadedState(), preloadedState: preloadedMobileState(),
resolvers, resolvers,
} }
) )
...@@ -72,7 +72,7 @@ describe(useCrossChainBalances, () => { ...@@ -72,7 +72,7 @@ describe(useCrossChainBalances, () => {
describe('otherChainBalances', () => { describe('otherChainBalances', () => {
it('returns null if there are no bridged currencies', async () => { it('returns null if there are no bridged currencies', async () => {
const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), { const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), {
preloadedState: mockWalletPreloadedState(), preloadedState: preloadedMobileState(),
}) })
await act(() => undefined) await act(() => undefined)
...@@ -105,7 +105,7 @@ describe(useCrossChainBalances, () => { ...@@ -105,7 +105,7 @@ describe(useCrossChainBalances, () => {
const { result } = renderHook( const { result } = renderHook(
() => useCrossChainBalances(currentChainBalance!.currencyInfo.currencyId, bridgeInfo), () => useCrossChainBalances(currentChainBalance!.currencyInfo.currencyId, bridgeInfo),
{ {
preloadedState: mockWalletPreloadedState(), preloadedState: preloadedMobileState(),
resolvers, resolvers,
} }
) )
......
...@@ -5,7 +5,7 @@ import { Screens } from 'src/screens/Screens' ...@@ -5,7 +5,7 @@ import { Screens } from 'src/screens/Screens'
import { import {
Chain, Chain,
useTokenDetailsScreenLazyQuery, useTokenDetailsScreenLazyQuery,
} from 'wallet/src/data/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils' import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { PortfolioBalance } from 'wallet/src/features/dataApi/types' import { PortfolioBalance } from 'wallet/src/features/dataApi/types'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils' import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
......
...@@ -28,7 +28,9 @@ export function HeaderText({ ...@@ -28,7 +28,9 @@ export function HeaderText({
return readablePermitAmount ? ( return readablePermitAmount ? (
<Text textAlign="center" variant="heading3"> <Text textAlign="center" variant="heading3">
<Trans <Trans
components={{ highlight: <Text fontWeight="bold" /> }} // `variant` prop must be first
// eslint-disable-next-line react/jsx-sort-props
components={{ highlight: <Text variant="heading3" fontWeight="bold" /> }}
i18nKey="qrScanner.request.withAmount" i18nKey="qrScanner.request.withAmount"
values={{ values={{
dappName: dapp.name, dappName: dapp.name,
...@@ -40,7 +42,9 @@ export function HeaderText({ ...@@ -40,7 +42,9 @@ export function HeaderText({
) : ( ) : (
<Text textAlign="center" variant="heading3"> <Text textAlign="center" variant="heading3">
<Trans <Trans
components={{ highlight: <Text fontWeight="bold" /> }} // `variant` prop must be first
// eslint-disable-next-line react/jsx-sort-props
components={{ highlight: <Text variant="heading3" fontWeight="bold" /> }}
i18nKey="qrScanner.request.withoutAmount" i18nKey="qrScanner.request.withoutAmount"
values={{ values={{
dappName: dapp.name, dappName: dapp.name,
...@@ -56,30 +60,12 @@ export function HeaderText({ ...@@ -56,30 +60,12 @@ export function HeaderText({
case EthMethod.PersonalSign: case EthMethod.PersonalSign:
case EthMethod.EthSign: case EthMethod.EthSign:
case EthMethod.SignTypedData: case EthMethod.SignTypedData:
return ( return <Trans i18nKey="qrScanner.request.method.signature" values={{ dappNameOrUrl }} />
<Trans
components={{ highlight: <Text fontWeight="bold" /> }}
i18nKey="qrScanner.request.method.signature"
values={{ dappNameOrUrl }}
/>
)
case EthMethod.EthSendTransaction: case EthMethod.EthSendTransaction:
return ( return <Trans i18nKey="qrScanner.request.method.transaction" values={{ dappNameOrUrl }} />
<Trans
components={{ highlight: <Text fontWeight="bold" /> }}
i18nKey="qrScanner.request.method.transaction"
values={{ dappNameOrUrl }}
/>
)
} }
return ( return <Trans i18nKey="qrScanner.request.method.default" values={{ dappNameOrUrl }} />
<Trans
components={{ highlight: <Text fontWeight="bold" /> }}
i18nKey="qrScanner.request.method.default"
values={{ dappNameOrUrl }}
/>
)
} }
return ( return (
......
...@@ -178,7 +178,7 @@ function TransactionDetails({ ...@@ -178,7 +178,7 @@ function TransactionDetails({
px="$spacing8" px="$spacing8"
py="$spacing4"> py="$spacing4">
<Text color="$neutral1" loading={isLoading} variant="monospace"> <Text color="$neutral1" loading={isLoading} variant="monospace">
{{ functionName: parsedData ? parsedData.name : t('common.text.unknown') }} {parsedData ? parsedData.name : t('common.text.unknown')}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -44,9 +44,9 @@ export function SpendingDetails({ ...@@ -44,9 +44,9 @@ export function SpendingDetails({
</Text> </Text>
<Flex row alignItems="center" gap="$spacing4"> <Flex row alignItems="center" gap="$spacing4">
<CurrencyLogo currencyInfo={nativeCurrencyInfo} size={iconSizes.icon16} /> <CurrencyLogo currencyInfo={nativeCurrencyInfo} size={iconSizes.icon16} />
<Text variant="subheading2">{{ tokenAmountWithSymbol }}</Text> <Text variant="subheading2">{tokenAmountWithSymbol}</Text>
<Text color="$neutral2" loading={!usdValue} variant="subheading2"> <Text color="$neutral2" loading={!usdValue} variant="subheading2">
({{ fiatAmount }}) {fiatAmount}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -137,7 +137,24 @@ export function WalletConnectModal({ ...@@ -137,7 +137,24 @@ export function WalletConnectModal({
} }
if (supportedURI.type === URIType.Scantastic) { if (supportedURI.type === URIType.Scantastic) {
const { pubKey, uuid, vendor, model, browser } = parseScantasticParams(supportedURI.value) const params = parseScantasticParams(supportedURI.value)
if (!params) {
setShouldFreezeCamera(true)
Alert.alert(
t('walletConnect.error.scantastic.title'),
t('walletConnect.error.scantastic.message'),
[
{
text: t('common.button.ok'),
onPress: (): void => {
setShouldFreezeCamera(false)
},
},
]
)
return
}
setShouldFreezeCamera(true) setShouldFreezeCamera(true)
dispatch(closeAllModals()) dispatch(closeAllModals())
...@@ -145,11 +162,7 @@ export function WalletConnectModal({ ...@@ -145,11 +162,7 @@ export function WalletConnectModal({
openModal({ openModal({
name: ModalName.Scantastic, name: ModalName.Scantastic,
initialState: { initialState: {
pubKey, params,
uuid,
vendor,
model,
browser,
}, },
}) })
) )
......
...@@ -5,7 +5,8 @@ import { ...@@ -5,7 +5,8 @@ import {
UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM, UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM,
UNISWAP_WALLETCONNECT_URL, UNISWAP_WALLETCONNECT_URL,
} from 'src/features/deepLinking/handleDeepLinkSaga' } from 'src/features/deepLinking/handleDeepLinkSaga'
import { ScantasticModalState } from 'src/features/scantastic/ScantasticModalState' import { logger } from 'utilities/src/logger/logger'
import { ScantasticParams, ScantasticParamsSchema } from 'wallet/src/features/scantastic/types'
import { UwULinkRequest } from 'wallet/src/features/walletConnect/types' import { UwULinkRequest } from 'wallet/src/features/walletConnect/types'
import { getValidAddress } from 'wallet/src/utils/addresses' import { getValidAddress } from 'wallet/src/utils/addresses'
...@@ -167,12 +168,53 @@ function getScantasticAddress(uri: string): Nullable<string> { ...@@ -167,12 +168,53 @@ function getScantasticAddress(uri: string): Nullable<string> {
return uriParts[1] || null return uriParts[1] || null
} }
const PARAM_PUB_KEY = 'pubKey'
const PARAM_UUID = 'uuid'
const PARAM_VENDOR = 'vendor'
const PARAM_MODEL = 'model'
const PARAM_BROWSER = 'browser'
/** parses scantastic params for a valid scantastic URI. */ /** parses scantastic params for a valid scantastic URI. */
export function parseScantasticParams(uri: string): ScantasticModalState { export function parseScantasticParams(uri: string): ScantasticParams | undefined {
const pubKey = new URLSearchParams(uri).get('pubKey') || '' const uriParams = new URLSearchParams(uri)
const uuid = new URLSearchParams(uri).get('uuid') || '' const paramKeys = [PARAM_PUB_KEY, PARAM_UUID, PARAM_VENDOR, PARAM_MODEL, PARAM_BROWSER]
const vendor = new URLSearchParams(uri).get('vendor') || ''
const model = new URLSearchParams(uri).get('model') || '' // Validate all keys are unique for security
const browser = new URLSearchParams(uri).get('browser') || '' for (const paramKey of paramKeys) {
return { pubKey, uuid, vendor, model, browser } if (uriParams.getAll(paramKey).length > 1) {
logger.error(new Error('Invalid scantastic params due to duplicate keys'), {
tags: {
file: 'util.ts',
function: 'parseScantasticParams',
},
extra: { uri },
})
return
}
}
const publicKey = uriParams.get(PARAM_PUB_KEY)
const uuid = uriParams.get(PARAM_UUID)
const vendor = uriParams.get(PARAM_VENDOR)
const model = uriParams.get(PARAM_MODEL)
const browser = uriParams.get(PARAM_BROWSER)
try {
return ScantasticParamsSchema.parse({
publicKey: publicKey ? JSON.parse(publicKey) : undefined,
uuid: uuid ? decodeURIComponent(uuid) : undefined,
vendor: vendor ? decodeURIComponent(vendor) : undefined,
model: model ? decodeURIComponent(model) : undefined,
browser: browser ? decodeURIComponent(browser) : undefined,
})
} catch (e) {
const wrappedError = new Error('Invalid scantastic params')
wrappedError.cause = e
logger.error(wrappedError, {
tags: {
file: 'util.ts',
function: 'parseScantasticParams',
},
})
}
} }
import { fireEvent, render, screen, waitFor } from 'src/test/test-utils'
import * as hooks from 'wallet/src/features/accounts/hooks'
import {
ON_PRESS_EVENT_PAYLOAD,
SAMPLE_SEED_ADDRESS_1,
amount,
portfolio,
} from 'wallet/src/test/fixtures'
import { queryResolvers } from 'wallet/src/test/utils'
import { AccountCardItem } from './AccountCardItem'
describe(AccountCardItem, () => {
beforeEach(() => {
jest.spyOn(hooks, 'useAccountList').mockReturnValue({
data: undefined,
loading: false,
networkStatus: 7,
refetch: jest.fn(),
startPolling: jest.fn(),
stopPolling: jest.fn(),
})
})
afterEach(() => {
jest.restoreAllMocks()
})
const defaultProps = {
address: SAMPLE_SEED_ADDRESS_1,
isPortfolioValueLoading: false,
portfolioValue: 100,
isViewOnly: false,
onPress: jest.fn(),
}
it('renders correctly', () => {
const tree = render(<AccountCardItem {...defaultProps} />)
expect(tree).toMatchSnapshot()
})
it('calls onPress when address is pressed', () => {
const onPress = jest.fn()
render(<AccountCardItem {...defaultProps} onPress={onPress} />)
const address = screen.getByTestId(`account-item/${SAMPLE_SEED_ADDRESS_1}`)
fireEvent.press(address, ON_PRESS_EVENT_PAYLOAD)
expect(onPress).toHaveBeenCalledTimes(1)
})
describe('portfolio value', () => {
it('displays loading shimmmer when portfolio value is loading', () => {
const { rerender } = render(
<AccountCardItem
{...defaultProps}
isPortfolioValueLoading={true}
portfolioValue={undefined}
/>
)
// Select shimmer placeholder because the actual shimmer is rendered after onLayout
// is fired and this logic is not a part of this test
expect(screen.queryByTestId('shimmer-placeholder')).toBeTruthy()
rerender(
<AccountCardItem
{...defaultProps}
isPortfolioValueLoading={false}
portfolioValue={undefined}
/>
)
expect(screen.queryByTestId('shimmer-placeholder')).toBeFalsy()
})
it('shows current portfolio value when available', () => {
render(<AccountCardItem {...defaultProps} portfolioValue={100} />)
expect(screen.queryByText('$100.00')).toBeTruthy()
})
it('shows placeholder text when portfolio value is not available', () => {
render(<AccountCardItem {...defaultProps} portfolioValue={undefined} />)
expect(screen.queryByText('N/A')).toBeTruthy()
})
it('shows cached portfolio value when not provided explicitly in props', async () => {
// We don't want to use the mocked query response for this test as we want to
// test if the cached value (returned by the query) is used when value is not provided
jest.restoreAllMocks()
const { resolvers: resolversWithPortfolioValue } = queryResolvers({
portfolios: () => [portfolio({ tokensTotalDenominatedValue: amount({ value: 200 }) })],
})
render(<AccountCardItem {...defaultProps} portfolioValue={undefined} />, {
resolvers: resolversWithPortfolioValue,
})
await waitFor(() => {
expect(screen.queryByText('$200.00')).toBeTruthy()
})
})
})
describe('view only accounts', () => {
it('renders view only badge when account is view only', () => {
render(<AccountCardItem {...defaultProps} isViewOnly={true} />)
const badge = screen.queryByTestId('account-icon/view-only-badge')
expect(badge).toBeTruthy()
})
it('does not render view only badge when account is not view only', () => {
render(<AccountCardItem {...defaultProps} isViewOnly={false} />)
const badge = screen.queryByTestId('account-icon/view-only-badge')
expect(badge).toBeFalsy()
})
})
})
...@@ -131,7 +131,7 @@ export function AccountCardItem({ ...@@ -131,7 +131,7 @@ export function AccountCardItem({
px="$spacing24" px="$spacing24"
onLongPress={disableOnPress} onLongPress={disableOnPress}
onPress={(): void => onPress(address)}> onPress={(): void => onPress(address)}>
<Flex row alignItems="flex-start" gap="$spacing16" testID={`account_item/${address}`}> <Flex row alignItems="flex-start" gap="$spacing16" testID={`account-item/${address}`}>
<Flex fill> <Flex fill>
<AddressDisplay <AddressDisplay
address={address} address={address}
......
import React from 'react' import * as ExpoClipboard from 'expo-clipboard'
import { navigationRef } from 'src/app/navigation/NavigationContainer'
import { MobileState } from 'src/app/reducer'
import { AccountHeader } from 'src/components/accounts/AccountHeader' import { AccountHeader } from 'src/components/accounts/AccountHeader'
import { render } from 'src/test/test-utils' import { Screens } from 'src/screens/Screens'
import { ACCOUNT } from 'wallet/src/test/fixtures' import { fireEvent, render, screen, waitFor, within } from 'src/test/test-utils'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks' import { ModalName } from 'wallet/src/telemetry/constants'
import {
ACCOUNT,
ON_PRESS_EVENT_PAYLOAD,
preloadedSharedState,
signerMnemonicAccount,
} from 'wallet/src/test/fixtures'
import { sanitizeAddressText, shortenAddress } from 'wallet/src/utils/addresses'
const preloadedState = preloadedSharedState({ account: ACCOUNT })
const address = ACCOUNT.address
const shortenedAddress = sanitizeAddressText(shortenAddress(address))!
const isModalOpen = (state: MobileState): boolean => {
const modalState = state.modals[ModalName.AccountSwitcher]
return modalState.isOpen
}
describe(AccountHeader, () => { describe(AccountHeader, () => {
it('renders without error', () => { it('renders correctly', () => {
const tree = render(<AccountHeader />, { preloadedState: mockWalletPreloadedState(ACCOUNT) }) const tree = render(<AccountHeader />, { preloadedState })
expect(tree.toJSON()).toMatchSnapshot() expect(tree.toJSON()).toMatchSnapshot()
}) })
describe('when wallet has no display name', () => {
const accountWithoutName = signerMnemonicAccount({ name: undefined, address })
const stateWithoutName = preloadedSharedState({
account: accountWithoutName,
})
it('renders shortened address within section address without name section', () => {
render(<AccountHeader />, { preloadedState: stateWithoutName })
const addressSection = screen.getByTestId('account-header/address-only')
const addressText = within(addressSection).queryByText(shortenedAddress)
expect(addressText).toBeTruthy()
})
it('copies wallet address to clipboard when address section is pressed', async () => {
const setStringAsync = jest.fn()
jest.spyOn(ExpoClipboard, 'setStringAsync').mockImplementation(setStringAsync)
render(<AccountHeader />, { preloadedState: stateWithoutName })
const addressSection = screen.getByTestId('account-header/address-only')
fireEvent.press(addressSection, ON_PRESS_EVENT_PAYLOAD)
await waitFor(() => {
expect(setStringAsync).toHaveBeenCalledTimes(1)
expect(setStringAsync).toHaveBeenCalledWith(address)
})
})
})
describe('when wallet has a display name', () => {
it('renders section with display name and address', () => {
render(<AccountHeader />, { preloadedState })
const displayNameSection = screen.getByTestId('account-header/display-name')
const displayNameText = within(displayNameSection).queryByText(ACCOUNT.name)
const addressText = within(displayNameSection).queryByText(shortenedAddress)
expect(displayNameText).toBeTruthy()
expect(addressText).toBeTruthy()
})
it('opens account switcher modal when account name is pressed', () => {
const { store } = render(<AccountHeader />, { preloadedState })
const displayNameText = within(screen.getByTestId('account-header/display-name')).getByText(
ACCOUNT.name
)
expect(isModalOpen(store.getState())).toBe(false)
fireEvent.press(displayNameText, ON_PRESS_EVENT_PAYLOAD)
expect(isModalOpen(store.getState())).toBe(true)
})
})
it('opens account switcher modal when account avatar is pressed', () => {
const { store } = render(<AccountHeader />, { preloadedState })
const avatar = screen.getByTestId('account-icon')
expect(isModalOpen(store.getState())).toBe(false)
fireEvent.press(avatar, ON_PRESS_EVENT_PAYLOAD)
expect(isModalOpen(store.getState())).toBe(true)
})
it('opens settings screen when settings button is pressed', async () => {
const navigate = jest.fn()
jest.spyOn(navigationRef, 'isReady').mockImplementation(() => true)
jest.spyOn(navigationRef, 'navigate').mockImplementation(navigate)
render(<AccountHeader />, { preloadedState })
const settingsButton = screen.getByTestId('account-header/settings-button')
fireEvent.press(settingsButton, ON_PRESS_EVENT_PAYLOAD)
await waitFor(() => {
expect(navigate).toHaveBeenCalledTimes(1)
expect(navigate).toHaveBeenCalledWith(Screens.SettingsStack, { screen: Screens.Settings })
})
})
}) })
...@@ -96,12 +96,21 @@ export function AccountHeader(): JSX.Element { ...@@ -96,12 +96,21 @@ export function AccountHeader(): JSX.Element {
size={iconSize} size={iconSize}
/> />
</TouchableArea> </TouchableArea>
<TouchableArea hapticFeedback hitSlop={20} onPress={onPressSettings}> <TouchableArea
hapticFeedback
hitSlop={20}
testID="account-header/settings-button"
onPress={onPressSettings}>
<Icons.Settings color="$neutral2" opacity={0.8} size="$icon.28" /> <Icons.Settings color="$neutral2" opacity={0.8} size="$icon.28" />
</TouchableArea> </TouchableArea>
</Flex> </Flex>
{walletHasName ? ( {walletHasName ? (
<Flex row alignItems="center" gap="$spacing8" justifyContent="space-between"> <Flex
row
alignItems="center"
gap="$spacing8"
justifyContent="space-between"
testID="account-header/display-name">
<TouchableArea <TouchableArea
hapticFeedback hapticFeedback
flexShrink={1} flexShrink={1}
...@@ -111,7 +120,11 @@ export function AccountHeader(): JSX.Element { ...@@ -111,7 +120,11 @@ export function AccountHeader(): JSX.Element {
</TouchableArea> </TouchableArea>
</Flex> </Flex>
) : ( ) : (
<TouchableArea hapticFeedback hitSlop={20} onPress={onPressCopyAddress}> <TouchableArea
hapticFeedback
hitSlop={20}
testID="account-header/address-only"
onPress={onPressCopyAddress}>
<Flex centered row shrink gap="$spacing4"> <Flex centered row shrink gap="$spacing4">
<Text <Text
adjustsFontSizeToFit adjustsFontSizeToFit
......
import { fireEvent } from '@testing-library/react-native'
import React from 'react'
import { AccountList } from 'src/components/accounts/AccountList' import { AccountList } from 'src/components/accounts/AccountList'
import { render, screen } from 'src/test/test-utils' import { cleanup, fireEvent, render, screen } from 'src/test/test-utils'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks' import {
import { ACCOUNT, ON_PRESS_EVENT_PAYLOAD, amounts } from 'wallet/src/test/fixtures' ACCOUNT,
ON_PRESS_EVENT_PAYLOAD,
amounts,
portfolio,
readOnlyAccount,
signerMnemonicAccount,
} from 'wallet/src/test/fixtures'
import { mockLocalizedFormatter } from 'wallet/src/test/mocks' import { mockLocalizedFormatter } from 'wallet/src/test/mocks'
import { createArray, queryResolvers } from 'wallet/src/test/utils'
import { sanitizeAddressText, shortenAddress } from 'wallet/src/utils/addresses'
const resolvers: Resolvers = { const tokensTotalDenominatedValue = amounts.md()
Portfolio: { const { resolvers } = queryResolvers({
tokensTotalDenominatedValue: () => amounts.md(), portfolios: () => [portfolio({ tokensTotalDenominatedValue })],
}, })
}
describe(AccountList, () => { describe(AccountList, () => {
afterEach(cleanup)
it('renders without error', async () => { it('renders without error', async () => {
const tree = render(<AccountList accounts={[ACCOUNT]} onPress={jest.fn()} />, { resolvers }) const tree = render(<AccountList accounts={[ACCOUNT]} onPress={jest.fn()} />, { resolvers })
expect( expect(
await screen.findByText( await screen.findByText(
mockLocalizedFormatter.formatNumberOrString({ mockLocalizedFormatter.formatNumberOrString({
value: amounts.md().value, value: tokensTotalDenominatedValue.value,
type: NumberType.PortfolioBalance, type: NumberType.PortfolioBalance,
currencyCode: 'usd', currencyCode: 'usd',
}) })
...@@ -38,15 +45,65 @@ describe(AccountList, () => { ...@@ -38,15 +45,65 @@ describe(AccountList, () => {
expect( expect(
await screen.findByText( await screen.findByText(
mockLocalizedFormatter.formatNumberOrString({ mockLocalizedFormatter.formatNumberOrString({
value: amounts.md().value, value: tokensTotalDenominatedValue.value,
type: NumberType.PortfolioBalance, type: NumberType.PortfolioBalance,
currencyCode: 'usd', currencyCode: 'usd',
}) })
) )
).toBeDefined() ).toBeDefined()
fireEvent.press(screen.getByTestId(`account_item/${ACCOUNT.address}`), ON_PRESS_EVENT_PAYLOAD) fireEvent.press(screen.getByTestId(`account-item/${ACCOUNT.address}`), ON_PRESS_EVENT_PAYLOAD)
expect(onPressSpy).toHaveBeenCalledTimes(1) expect(onPressSpy).toHaveBeenCalledTimes(1)
}) })
describe('signer accounts', () => {
it('renders signer accounts section if there are signer accounts', () => {
const signerAccounts = createArray(3, signerMnemonicAccount)
render(<AccountList accounts={signerAccounts} onPress={jest.fn()} />, { resolvers })
expect(screen.queryByText('Your other wallets')).toBeTruthy()
signerAccounts.forEach((account) => {
const address = sanitizeAddressText(shortenAddress(account.address))
if (address) {
expect(screen.queryByText(address)).toBeTruthy()
}
})
cleanup()
})
it('does not render signer accounts section if there are no signer accounts', () => {
render(<AccountList accounts={[readOnlyAccount()]} onPress={jest.fn()} />, { resolvers })
expect(screen.queryByText('Your other wallets')).toBeFalsy()
cleanup()
})
})
describe('view only accounts', () => {
it('renders view only accounts section if there are view only accounts', () => {
const viewOnlyAccounts = createArray(3, readOnlyAccount)
render(<AccountList accounts={viewOnlyAccounts} onPress={jest.fn()} />, { resolvers })
expect(screen.queryByText('View only wallets')).toBeTruthy()
viewOnlyAccounts.forEach((account) => {
const address = sanitizeAddressText(shortenAddress(account.address))
if (address) {
expect(screen.queryByText(address)).toBeTruthy()
}
})
cleanup()
})
it('does not render view only accounts section if there are no view only accounts', () => {
render(<AccountList accounts={[signerMnemonicAccount()]} onPress={jest.fn()} />, {
resolvers,
})
expect(screen.queryByText('View only wallets')).toBeFalsy()
// cleanup()
})
})
}) })
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AccountHeader renders without error 1`] = ` exports[`AccountHeader renders correctly 1`] = `
<View <View
style={ style={
{ {
...@@ -81,6 +81,7 @@ exports[`AccountHeader renders without error 1`] = ` ...@@ -81,6 +81,7 @@ exports[`AccountHeader renders without error 1`] = `
"position": "relative", "position": "relative",
} }
} }
testID="account-icon"
> >
<View <View
style={ style={
...@@ -347,6 +348,7 @@ exports[`AccountHeader renders without error 1`] = ` ...@@ -347,6 +348,7 @@ exports[`AccountHeader renders without error 1`] = `
], ],
} }
} }
testID="account-header/settings-button"
> >
<RNSVGSvgView <RNSVGSvgView
align="xMidYMid" align="xMidYMid"
...@@ -418,6 +420,7 @@ exports[`AccountHeader renders without error 1`] = ` ...@@ -418,6 +420,7 @@ exports[`AccountHeader renders without error 1`] = `
"justifyContent": "space-between", "justifyContent": "space-between",
} }
} }
testID="account-header/display-name"
> >
<View <View
cancelable={true} cancelable={true}
......
...@@ -145,7 +145,7 @@ exports[`AccountList renders without error 1`] = ` ...@@ -145,7 +145,7 @@ exports[`AccountList renders without error 1`] = `
"gap": 16, "gap": 16,
} }
} }
testID="account_item/0x82D56A352367453f74FC0dC7B071b311da373Fa6" testID="account-item/0x82D56A352367453f74FC0dC7B071b311da373Fa6"
> >
<View <View
style={ style={
...@@ -193,6 +193,7 @@ exports[`AccountList renders without error 1`] = ` ...@@ -193,6 +193,7 @@ exports[`AccountList renders without error 1`] = `
"position": "relative", "position": "relative",
} }
} }
testID="account-icon"
> >
<View <View
style={ style={
......
import React from 'react' import React from 'react'
import { BackButton } from 'src/components/buttons/BackButton'
import { fireEvent, render, screen } from 'src/test/test-utils' import { fireEvent, render, screen } from 'src/test/test-utils'
import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/fixtures' import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/fixtures'
import { BackButton } from './BackButton'
const mockedGoBack = jest.fn() const mockedGoBack = jest.fn()
jest.mock('@react-navigation/native', () => { jest.mock('@react-navigation/native', () => {
...@@ -17,8 +17,9 @@ jest.mock('@react-navigation/native', () => { ...@@ -17,8 +17,9 @@ jest.mock('@react-navigation/native', () => {
describe(BackButton, () => { describe(BackButton, () => {
it('renders without error', async () => { it('renders without error', async () => {
render(<BackButton showButtonLabel />) const tree = render(<BackButton showButtonLabel />)
expect(tree).toMatchSnapshot()
expect(await screen.findByText('Back')).toBeDefined() expect(await screen.findByText('Back')).toBeDefined()
}) })
......
import { fireEvent, render } from 'src/test/test-utils'
import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/fixtures'
import { CloseButton } from './CloseButton'
describe(CloseButton, () => {
it('renders without error', () => {
const tree = render(<CloseButton onPress={jest.fn()} />)
expect(tree).toMatchSnapshot()
})
it('calls onPress when pressed', async () => {
const onPress = jest.fn()
const { getByTestId } = render(<CloseButton onPress={onPress} />)
const button = getByTestId('buttons/close-button')
fireEvent.press(button, ON_PRESS_EVENT_PAYLOAD)
expect(onPress).toHaveBeenCalledTimes(1)
})
})
...@@ -10,7 +10,7 @@ type Props = { ...@@ -10,7 +10,7 @@ type Props = {
export function CloseButton({ onPress, size, strokeWidth, color, ...rest }: Props): JSX.Element { export function CloseButton({ onPress, size, strokeWidth, color, ...rest }: Props): JSX.Element {
return ( return (
<TouchableArea onPress={onPress} {...rest}> <TouchableArea onPress={onPress} {...rest} testID="buttons/close-button">
<Icons.X color={color} size={size ?? '$icon.20'} strokeWidth={strokeWidth ?? 2} /> <Icons.X color={color} size={size ?? '$icon.20'} strokeWidth={strokeWidth ?? 2} />
</TouchableArea> </TouchableArea>
) )
......
import { act, fireEvent, render } from 'src/test/test-utils'
import { setClipboard } from 'wallet/src/utils/clipboard'
import { CopyTextButton } from './CopyTextButton'
jest.mock('wallet/src/utils/clipboard')
describe(CopyTextButton, () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
it('renders without error', () => {
const tree = render(<CopyTextButton copyText="copy text" />)
expect(tree).toMatchSnapshot()
})
it('copies text when pressed', async () => {
const { getByText } = render(<CopyTextButton copyText="copy text" />)
const button = getByText('Copy')
await act(async () => {
fireEvent.press(button)
})
expect(setClipboard).toHaveBeenCalledWith('copy text')
})
it('changes button text when text is copied and brings back original text after timeout', async () => {
const { queryByText, getByText } = render(<CopyTextButton copyText="copy text" />)
const button = getByText('Copy')
await act(async () => {
fireEvent.press(button)
})
expect(queryByText('Copied')).toBeTruthy()
await act(async () => {
jest.advanceTimersByTime(2000)
})
expect(queryByText('Copy')).toBeTruthy()
})
})
import React from 'react'
import { TapGestureHandler, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'
import {
cancelAnimation,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import { AnimatedFlex, useIsDarkMode, useSporeColors } from 'ui/src'
import HeartIcon from 'ui/src/assets/icons/heart.svg'
interface FavoriteButtonProps {
isFavorited: boolean
size: number
onPress: () => void
}
export const FavoriteButton = ({
isFavorited,
size,
onPress,
}: FavoriteButtonProps): JSX.Element => {
const colors = useSporeColors()
const isDarkMode = useIsDarkMode()
const unfilledColor = isDarkMode ? colors.neutral3.val : colors.surface3.val
const color = isFavorited ? colors.accent1.val : unfilledColor
const scale = useSharedValue(1)
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }), [scale])
const animationConfig = { duration: 150 }
const onGestureEvent = useAnimatedGestureHandler<TapGestureHandlerGestureEvent>({
onStart: () => {
cancelAnimation(scale)
scale.value = withTiming(0, animationConfig)
},
onFinish: () => {
runOnJS(onPress)()
scale.value = withTiming(1, animationConfig)
},
})
return (
<TapGestureHandler onGestureEvent={onGestureEvent}>
<AnimatedFlex style={animatedStyle}>
<HeartIcon color={color} height={size} width={size} />
</AnimatedFlex>
</TapGestureHandler>
)
}
import { fireEvent, render } from 'src/test/test-utils'
import { ON_PRESS_EVENT_PAYLOAD } from 'wallet/src/test/fixtures'
import { LinkButton } from './LinkButton'
jest.mock('wallet/src/utils/linking')
describe(LinkButton, () => {
it('renders without error', () => {
const tree = render(<LinkButton label="link text" url="https://example.com" />)
expect(tree).toMatchSnapshot()
})
it('renders button with specified label', async () => {
const { queryByText } = render(<LinkButton label="link text" url="https://example.com" />)
expect(queryByText('link text')).toBeDefined()
})
describe('when pressed', () => {
const cases = [
{ openExternalBrowser: false, isSafeUri: false },
{ openExternalBrowser: true, isSafeUri: false },
{ openExternalBrowser: false, isSafeUri: true },
{ openExternalBrowser: true, isSafeUri: true },
]
it.each(cases)('calls openUri with %p', async ({ openExternalBrowser, isSafeUri }) => {
const { getByText } = render(
<LinkButton
isSafeUri={isSafeUri}
label="link text"
openExternalBrowser={openExternalBrowser}
url="https://example.com"
/>
)
const button = getByText('link text')
fireEvent.press(button, ON_PRESS_EVENT_PAYLOAD)
expect(require('wallet/src/utils/linking').openUri).toHaveBeenCalledWith(
'https://example.com',
openExternalBrowser,
isSafeUri
)
})
})
})
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BackButton renders without error 1`] = `
<View
cancelable={true}
disabled={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
style={
{
"alignItems": "center",
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
testID="buttons/back-button"
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"gap": 8,
}
}
>
<View
style={
{
"alignItems": "center",
"borderBottomLeftRadius": 999999,
"borderBottomRightRadius": 999999,
"borderTopLeftRadius": 999999,
"borderTopRightRadius": 999999,
"flexDirection": "column",
"justifyContent": "center",
"transform": [
{
"rotate": "0deg",
},
],
}
}
>
<RNSVGSvgView
align="xMidYMid"
bbHeight="24"
bbWidth="24"
fill="none"
focusable={false}
meetOrSlice={0}
minX={0}
minY={0}
stroke="currentColor"
strokeWidth={8}
style={
[
{
"backgroundColor": "transparent",
"borderWidth": 0,
},
{
"color": "#7D7D7D",
"height": 24,
"width": 24,
},
{
"flex": 0,
"height": 24,
"width": 24,
},
]
}
tintColor="#7D7D7D"
vbHeight={24}
vbWidth={24}
>
<RNSVGGroup
fill={null}
propList={
[
"fill",
"stroke",
"strokeWidth",
]
}
stroke={
{
"type": 2,
}
}
strokeWidth="8"
>
<RNSVGPath
d="M15 6L9 12L15 18"
fill={
{
"payload": 4278190080,
"type": 0,
}
}
propList={
[
"stroke",
"strokeWidth",
"strokeLinecap",
"strokeLinejoin",
]
}
stroke={
{
"type": 2,
}
}
strokeLinecap={1}
strokeLinejoin={1}
strokeWidth="3"
/>
</RNSVGGroup>
</RNSVGSvgView>
</View>
<Text
allowFontScaling={true}
maxFontSizeMultiplier={1.4}
style={
{
"color": "#7D7D7D",
"fontFamily": "Basel-Book",
"fontSize": 19,
"lineHeight": 24,
}
}
suppressHighlighting={true}
>
Back
</Text>
</View>
</View>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CloseButton renders without error 1`] = `
<View
cancelable={true}
disabled={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
testID="buttons/close-button"
>
<RNSVGSvgView
align="xMidYMid"
bbHeight="20"
bbWidth="20"
fill="none"
focusable={false}
meetOrSlice={0}
minX={0}
minY={0}
strokeWidth={2}
style={
[
{
"backgroundColor": "transparent",
"borderWidth": 0,
},
{
"height": 20,
"width": 20,
},
{
"flex": 0,
"height": 20,
"width": 20,
},
]
}
vbHeight={16}
vbWidth={16}
>
<RNSVGGroup
fill={null}
propList={
[
"fill",
"strokeWidth",
]
}
strokeWidth="2"
>
<RNSVGPath
d="M12.5303 4.53033C12.8232 4.23744 12.8232 3.76256 12.5303 3.46967C12.2374 3.17678 11.7626 3.17678 11.4697 3.46967L12.5303 4.53033ZM3.46967 11.4697C3.17678 11.7626 3.17678 12.2374 3.46967 12.5303C3.76256 12.8232 4.23744 12.8232 4.53033 12.5303L3.46967 11.4697ZM4.53033 3.46967C4.23744 3.17678 3.76256 3.17678 3.46967 3.46967C3.17678 3.76256 3.17678 4.23744 3.46967 4.53033L4.53033 3.46967ZM11.4697 12.5303C11.7626 12.8232 12.2374 12.8232 12.5303 12.5303C12.8232 12.2374 12.8232 11.7626 12.5303 11.4697L11.4697 12.5303ZM11.4697 3.46967L3.46967 11.4697L4.53033 12.5303L12.5303 4.53033L11.4697 3.46967ZM3.46967 4.53033L11.4697 12.5303L12.5303 11.4697L4.53033 3.46967L3.46967 4.53033Z"
fill={
{
"type": 2,
}
}
propList={
[
"fill",
]
}
/>
</RNSVGGroup>
</RNSVGSvgView>
</View>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CopyTextButton renders without error 1`] = `
<View
cancelable={true}
disabled={false}
focusable={true}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
style={
{
"alignItems": "center",
"backgroundColor": "#2222220D",
"borderBottomColor": "transparent",
"borderBottomLeftRadius": 16,
"borderBottomRightRadius": 16,
"borderBottomWidth": 1,
"borderLeftColor": "transparent",
"borderLeftWidth": 1,
"borderRightColor": "transparent",
"borderRightWidth": 1,
"borderStyle": "solid",
"borderTopColor": "transparent",
"borderTopLeftRadius": 16,
"borderTopRightRadius": 16,
"borderTopWidth": 1,
"flexDirection": "row",
"gap": 8,
"height": "auto",
"justifyContent": "center",
"opacity": 1,
"paddingBottom": 12,
"paddingLeft": 12,
"paddingRight": 12,
"paddingTop": 12,
}
}
>
<svg
color="#222222"
data-file-name="SvgCopySheets"
height={20}
size={20}
width={20}
/>
<Text
maxFontSizeMultiplier={1.2}
style={
{
"color": "#222222",
"fontFamily": "Basel-Medium",
"fontSize": 19,
"fontWeight": "500",
"lineHeight": 24,
}
}
suppressHighlighting={true}
>
Copy
</Text>
</View>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LinkButton renders without error 1`] = `
<View
cancelable={true}
disabled={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onPress={[Function]}
onPressIn={[Function]}
onPressOut={[Function]}
style={
{
"flexDirection": "column",
"opacity": 1,
"transform": [
{
"scale": 1,
},
],
}
}
>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"gap": 4,
"justifyContent": "center",
}
}
>
<Text
allowFontScaling={true}
style={
{
"color": "#222222",
"fontFamily": "Basel-Book",
}
}
suppressHighlighting={true}
>
link text
</Text>
<svg
color="#FC72FF"
data-file-name="SvgExternalLink"
height={20}
strokeWidth={1.5}
width={20}
/>
</View>
</View>
`;
...@@ -7,7 +7,7 @@ import { CloseButton } from 'src/components/buttons/CloseButton' ...@@ -7,7 +7,7 @@ import { CloseButton } from 'src/components/buttons/CloseButton'
import { CarouselContext } from 'src/components/carousel/Carousel' import { CarouselContext } from 'src/components/carousel/Carousel'
import { OnboardingScreens } from 'src/screens/Screens' import { OnboardingScreens } from 'src/screens/Screens'
import { Flex, Text, useDeviceDimensions } from 'ui/src' import { Flex, Text, useDeviceDimensions } from 'ui/src'
import { getCloudProviderName } from 'uniswap/src/utils/platform' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
function Page({ function Page({
text, text,
......
...@@ -17,15 +17,15 @@ import { ...@@ -17,15 +17,15 @@ import {
} from 'src/features/explore/utils' } from 'src/features/explore/utils'
import { usePollOnFocusOnly } from 'src/utils/hooks' import { usePollOnFocusOnly } from 'src/utils/hooks'
import { Flex, Loader, Text, useDeviceInsets } from 'ui/src' import { Flex, Loader, Text, useDeviceInsets } from 'ui/src'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { getWrappedNativeAddress } from 'wallet/src/constants/addresses'
import { ChainId } from 'wallet/src/constants/chains'
import { PollingInterval } from 'wallet/src/constants/misc'
import { import {
Chain, Chain,
ExploreTokensTabQuery, ExploreTokensTabQuery,
useExploreTokensTabQuery, useExploreTokensTabQuery,
} from 'wallet/src/data/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { getWrappedNativeAddress } from 'wallet/src/constants/addresses'
import { ChainId } from 'wallet/src/constants/chains'
import { PollingInterval } from 'wallet/src/constants/misc'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils' import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { usePersistedError } from 'wallet/src/features/dataApi/utils' import { usePersistedError } from 'wallet/src/features/dataApi/utils'
import { import {
......
...@@ -12,13 +12,13 @@ import { disableOnPress } from 'src/utils/disableOnPress' ...@@ -12,13 +12,13 @@ import { disableOnPress } from 'src/utils/disableOnPress'
import { usePollOnFocusOnly } from 'src/utils/hooks' import { usePollOnFocusOnly } from 'src/utils/hooks'
import { AnimatedFlex, AnimatedTouchableArea, Flex, Text } from 'ui/src' import { AnimatedFlex, AnimatedTouchableArea, Flex, Text } from 'ui/src'
import { borderRadii, imageSizes } from 'ui/src/theme' import { borderRadii, imageSizes } from 'ui/src/theme'
import { useFavoriteTokenCardQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard' import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import { RelativeChange } from 'wallet/src/components/text/RelativeChange' import { RelativeChange } from 'wallet/src/components/text/RelativeChange'
import { ChainId } from 'wallet/src/constants/chains' import { ChainId } from 'wallet/src/constants/chains'
import { PollingInterval } from 'wallet/src/constants/misc' import { PollingInterval } from 'wallet/src/constants/misc'
import { useFavoriteTokenCardQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { isNonPollingRequestInFlight } from 'wallet/src/data/utils' import { isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils' import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils' import { currencyIdToContractInput } from 'wallet/src/features/dataApi/utils'
......
...@@ -11,8 +11,8 @@ import { MobileEventName } from 'src/features/telemetry/constants' ...@@ -11,8 +11,8 @@ import { MobileEventName } from 'src/features/telemetry/constants'
import { disableOnPress } from 'src/utils/disableOnPress' import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, Icons, Text, TouchableArea, useIsDarkMode } from 'ui/src' import { Flex, Icons, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { TokenSortableField } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { TokenSortableField } from 'wallet/src/data/__generated__/types-and-hooks'
import { setTokensOrderBy } from 'wallet/src/features/wallet/slice' import { setTokensOrderBy } from 'wallet/src/features/wallet/slice'
import { ClientTokensOrderBy, TokensOrderBy } from 'wallet/src/features/wallet/types' import { ClientTokensOrderBy, TokensOrderBy } from 'wallet/src/features/wallet/types'
interface FilterGroupProps { interface FilterGroupProps {
......
...@@ -4,7 +4,7 @@ import { act } from 'react-test-renderer' ...@@ -4,7 +4,7 @@ import { act } from 'react-test-renderer'
import configureMockStore from 'redux-mock-store' import configureMockStore from 'redux-mock-store'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks' import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { renderHookWithProviders } from 'src/test/render' import { renderHookWithProviders } from 'src/test/render'
import { Resolvers } from 'wallet/src/data/__generated__/types-and-hooks' import { Resolvers } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { FavoritesState } from 'wallet/src/features/favorites/slice' import { FavoritesState } from 'wallet/src/features/favorites/slice'
import { CurrencyField } from 'wallet/src/features/transactions/transactionState/types' import { CurrencyField } from 'wallet/src/features/transactions/transactionState/types'
import { SectionName } from 'wallet/src/telemetry/constants' import { SectionName } from 'wallet/src/telemetry/constants'
......
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
gqlNFTToNFTCollectionSearchResult, gqlNFTToNFTCollectionSearchResult,
} from 'src/components/explore/search/utils' } from 'src/components/explore/search/utils'
import { Inset, Loader } from 'ui/src' import { Inset, Loader } from 'ui/src'
import { useSearchPopularNftCollectionsQuery } from 'wallet/src/data/__generated__/types-and-hooks' import { useSearchPopularNftCollectionsQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { import {
NFTCollectionSearchResult, NFTCollectionSearchResult,
SearchResultType, SearchResultType,
......
...@@ -17,10 +17,13 @@ import { ...@@ -17,10 +17,13 @@ import {
getSearchResultId, getSearchResultId,
} from 'src/components/explore/search/utils' } from 'src/components/explore/search/utils'
import { AnimatedFlex, Flex, Text } from 'ui/src' import { AnimatedFlex, Flex, Text } from 'ui/src'
import {
SafetyLevel,
useExploreSearchQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard' import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains' import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains'
import { SafetyLevel, useExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { SearchContext } from 'wallet/src/features/search/SearchContext' import { SearchContext } from 'wallet/src/features/search/SearchContext'
import { import {
NFTCollectionSearchResult, NFTCollectionSearchResult,
...@@ -185,8 +188,9 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): ...@@ -185,8 +188,9 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }):
<AnimatedFlex entering={FadeIn} exiting={FadeOut} gap="$spacing8" mx="$spacing8"> <AnimatedFlex entering={FadeIn} exiting={FadeOut} gap="$spacing8" mx="$spacing8">
<Text color="$neutral2" variant="body1"> <Text color="$neutral2" variant="body1">
<Trans <Trans
components={{ highlight: <Text color="$neutral1" /> }} components={{ highlight: <Text color="$neutral1" variant="body1" /> }}
i18nKey="explore.search.empty.full" i18nKey="explore.search.empty.full"
values={{ searchQuery }}
/> />
</Text> </Text>
</AnimatedFlex> </AnimatedFlex>
......
...@@ -2,19 +2,19 @@ import { ImpactFeedbackStyle } from 'expo-haptics' ...@@ -2,19 +2,19 @@ import { ImpactFeedbackStyle } from 'expo-haptics'
import { default as React } from 'react' import { default as React } from 'react'
import ContextMenu from 'react-native-context-menu-view' import ContextMenu from 'react-native-context-menu-view'
import { useAppDispatch } from 'src/app/hooks' import { useAppDispatch } from 'src/app/hooks'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks' import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { useExploreTokenContextMenu } from 'src/components/explore/hooks'
import { sendMobileAnalyticsEvent } from 'src/features/telemetry' import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { MobileEventName } from 'src/features/telemetry/constants' import { MobileEventName } from 'src/features/telemetry/constants'
import { disableOnPress } from 'src/utils/disableOnPress' import { disableOnPress } from 'src/utils/disableOnPress'
import { Flex, Text, TouchableArea, useIsDarkMode } from 'ui/src' import { Flex, Text, TouchableArea, useIsDarkMode } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import WarningIcon from 'wallet/src/components/icons/WarningIcon' import WarningIcon from 'wallet/src/components/icons/WarningIcon'
import { SafetyLevel } from 'wallet/src/data/__generated__/types-and-hooks'
import { SearchContext } from 'wallet/src/features/search/SearchContext' import { SearchContext } from 'wallet/src/features/search/SearchContext'
import { addToSearchHistory } from 'wallet/src/features/search/searchHistorySlice'
import { SearchResultType, TokenSearchResult } from 'wallet/src/features/search/SearchResult' import { SearchResultType, TokenSearchResult } from 'wallet/src/features/search/SearchResult'
import { addToSearchHistory } from 'wallet/src/features/search/searchHistorySlice'
import { ElementName, SectionName } from 'wallet/src/telemetry/constants' import { ElementName, SectionName } from 'wallet/src/telemetry/constants'
import { shortenAddress } from 'wallet/src/utils/addresses' import { shortenAddress } from 'wallet/src/utils/addresses'
import { buildCurrencyId, buildNativeCurrencyId } from 'wallet/src/utils/currencyId' import { buildCurrencyId, buildNativeCurrencyId } from 'wallet/src/utils/currencyId'
......
...@@ -4,7 +4,10 @@ import { ...@@ -4,7 +4,10 @@ import {
formatTokenSearchResults, formatTokenSearchResults,
gqlNFTToNFTCollectionSearchResult, gqlNFTToNFTCollectionSearchResult,
} from 'src/components/explore/search/utils' } from 'src/components/explore/search/utils'
import { Chain, ExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks' import {
Chain,
ExploreSearchQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils' import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { SearchResultType } from 'wallet/src/features/search/SearchResult' import { SearchResultType } from 'wallet/src/features/search/SearchResult'
import { import {
......
import { SEARCH_RESULT_HEADER_KEY } from 'src/components/explore/search/constants' import { SEARCH_RESULT_HEADER_KEY } from 'src/components/explore/search/constants'
import { SearchResultOrHeader } from 'src/components/explore/search/types' import { SearchResultOrHeader } from 'src/components/explore/search/types'
import { Chain, ExploreSearchQuery } from 'wallet/src/data/__generated__/types-and-hooks' import {
Chain,
ExploreSearchQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils' import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { import {
NFTCollectionSearchResult, NFTCollectionSearchResult,
......
...@@ -13,8 +13,8 @@ import { useBiometricAppSettings, useBiometricPrompt } from 'src/features/biomet ...@@ -13,8 +13,8 @@ import { useBiometricAppSettings, useBiometricPrompt } from 'src/features/biomet
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice' import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, useDeviceInsets, useSporeColors } from 'ui/src' import { Flex, useDeviceInsets, useSporeColors } from 'ui/src'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'uniswap/src/utils/platform'
import { GQLQueries } from 'wallet/src/data/queries'
import { useActivityData } from 'wallet/src/features/activity/useActivityData' import { useActivityData } from 'wallet/src/features/activity/useActivityData'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
......
...@@ -12,9 +12,9 @@ import { openModal } from 'src/features/modals/modalSlice' ...@@ -12,9 +12,9 @@ import { openModal } from 'src/features/modals/modalSlice'
import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice' import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, Text, useDeviceInsets, useSporeColors } from 'ui/src' import { Flex, Text, useDeviceInsets, useSporeColors } from 'ui/src'
import { NoTransactions } from 'ui/src/components/icons/NoTransactions' import { NoTransactions } from 'ui/src/components/icons/NoTransactions'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'uniswap/src/utils/platform'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard' import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { GQLQueries } from 'wallet/src/data/queries'
import { useFormattedTransactionDataForFeed } from 'wallet/src/features/activity/hooks' import { useFormattedTransactionDataForFeed } from 'wallet/src/features/activity/hooks'
import { selectWatchedAddressSet } from 'wallet/src/features/favorites/selectors' import { selectWatchedAddressSet } from 'wallet/src/features/favorites/selectors'
import TransactionSummaryLayout from 'wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout' import TransactionSummaryLayout from 'wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout'
......
import { FlashList } from '@shopify/flash-list' import { FlashList } from '@shopify/flash-list'
import React, { forwardRef, memo, useCallback, useMemo } from 'react' import React, { forwardRef, memo, useCallback, useMemo } from 'react'
import { RefreshControl } from 'react-native' import { RefreshControl } from 'react-native'
import { useAppDispatch } from 'src/app/hooks'
import { useAppStackNavigation } from 'src/app/navigation/types' import { useAppStackNavigation } from 'src/app/navigation/types'
import { NftView } from 'src/components/NFT/NftView' import { NftView } from 'src/components/NFT/NftView'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { useAdaptiveFooter } from 'src/components/home/hooks' import { useAdaptiveFooter } from 'src/components/home/hooks'
import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers' import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers'
import { openModal } from 'src/features/modals/modalSlice'
import { removePendingSession } from 'src/features/walletConnect/walletConnectSlice'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
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 { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'uniswap/src/utils/platform'
import { NftsList } from 'wallet/src/components/nfts/NftsList' import { NftsList } from 'wallet/src/components/nfts/NftsList'
import { GQLQueries } from 'wallet/src/data/queries'
import { NFTItem } from 'wallet/src/features/nfts/types' import { NFTItem } from 'wallet/src/features/nfts/types'
import { ModalName } from 'wallet/src/telemetry/constants'
export const NFTS_TAB_DATA_DEPENDENCIES = [GQLQueries.NftsTab] export const NFTS_TAB_DATA_DEPENDENCIES = [GQLQueries.NftsTab]
...@@ -34,7 +29,6 @@ export const NftsTab = memo( ...@@ -34,7 +29,6 @@ export const NftsTab = memo(
ref ref
) { ) {
const colors = useSporeColors() const colors = useSporeColors()
const dispatch = useAppDispatch()
const insets = useDeviceInsets() const insets = useDeviceInsets()
const navigation = useAppStackNavigation() const navigation = useAppStackNavigation()
...@@ -42,14 +36,6 @@ export const NftsTab = memo( ...@@ -42,14 +36,6 @@ export const NftsTab = memo(
containerProps?.contentContainerStyle containerProps?.contentContainerStyle
) )
const onPressScan = (): void => {
// in case we received a pending session from a previous scan after closing modal
dispatch(removePendingSession())
dispatch(
openModal({ name: ModalName.WalletConnectScan, initialState: ScannerModalState.WalletQr })
)
}
const renderNFTItem = useCallback( const renderNFTItem = useCallback(
(item: NFTItem) => { (item: NFTItem) => {
const onPressNft = (): void => { const onPressNft = (): void => {
...@@ -95,7 +81,6 @@ export const NftsTab = memo( ...@@ -95,7 +81,6 @@ export const NftsTab = memo(
renderNFTItem={renderNFTItem} renderNFTItem={renderNFTItem}
renderedInModal={renderedInModal} renderedInModal={renderedInModal}
onContentSizeChange={onContentSizeChange} onContentSizeChange={onContentSizeChange}
onPressEmptyState={onPressScan}
onRefresh={onRefresh} onRefresh={onRefresh}
onScroll={scrollHandler} onScroll={scrollHandler}
{...containerProps} {...containerProps}
......
...@@ -3,17 +3,17 @@ import React, { forwardRef, memo, useCallback, useMemo } from 'react' ...@@ -3,17 +3,17 @@ import React, { forwardRef, memo, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FlatList } from 'react-native' import { FlatList } from 'react-native'
import { useAppDispatch } from 'src/app/hooks' import { useAppDispatch } from 'src/app/hooks'
import { WalletEmptyState } from 'src/components/home/WalletEmptyState'
import { NoTokens } from 'src/components/icons/NoTokens'
import { TabContentProps, TabProps } from 'src/components/layout/TabHelpers'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants' import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { TokenBalanceList } from 'src/components/TokenBalanceList/TokenBalanceList' import { TokenBalanceList } from 'src/components/TokenBalanceList/TokenBalanceList'
import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks' import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks'
import { WalletEmptyState } from 'src/components/home/WalletEmptyState'
import { NoTokens } from 'src/components/icons/NoTokens'
import { TabContentProps, TabProps } from 'src/components/layout/TabHelpers'
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
import { Screens } from 'src/screens/Screens' import { Screens } from 'src/screens/Screens'
import { Flex } from 'ui/src' import { Flex } from 'ui/src'
import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard' import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { GQLQueries } from 'wallet/src/data/queries'
import { TokenBalanceListRow } from 'wallet/src/features/portfolio/TokenBalanceListContext' import { TokenBalanceListRow } from 'wallet/src/features/portfolio/TokenBalanceListContext'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { CurrencyId } from 'wallet/src/utils/currencyId' import { CurrencyId } from 'wallet/src/utils/currencyId'
......
...@@ -36,6 +36,7 @@ export const Favorite = ({ isFavorited, size }: FavoriteButtonProps): JSX.Elemen ...@@ -36,6 +36,7 @@ export const Favorite = ({ isFavorited, size }: FavoriteButtonProps): JSX.Elemen
const scale = useDerivedValue(() => { const scale = useDerivedValue(() => {
return withSequence(withTiming(0, ANIMATION_CONFIG), withTiming(1, ANIMATION_CONFIG)) return withSequence(withTiming(0, ANIMATION_CONFIG), withTiming(1, ANIMATION_CONFIG))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isFavorited]) }, [isFavorited])
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }), [scale]) const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }), [scale])
......
import { ImageLibraryOptions, launchImageLibrary } from 'react-native-image-picker' import { ImageLibraryOptions, launchImageLibrary } from 'react-native-image-picker'
import { useNftsTabQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { NUM_FIRST_NFTS } from 'wallet/src/components/nfts/NftsList' import { NUM_FIRST_NFTS } from 'wallet/src/components/nfts/NftsList'
import { useNftsTabQuery } from 'wallet/src/data/__generated__/types-and-hooks'
import { formatNftItems } from 'wallet/src/features/nfts/utils' import { formatNftItems } from 'wallet/src/features/nfts/utils'
// Selected image will be shrunk to max width/height // Selected image will be shrunk to max width/height
......
...@@ -21,7 +21,50 @@ import { ...@@ -21,7 +21,50 @@ import {
import { FEATURE_FLAGS } from 'wallet/src/features/experiments/constants' import { FEATURE_FLAGS } from 'wallet/src/features/experiments/constants'
import { useFeatureFlag } from 'wallet/src/features/experiments/hooks' import { useFeatureFlag } from 'wallet/src/features/experiments/hooks'
export let apolloClient: ApolloClient<NormalizedCacheObject> | null = null type ApolloClientRef = {
current: ApolloClient<NormalizedCacheObject> | null
onReady: () => Promise<ApolloClient<NormalizedCacheObject>>
}
// This object allows us to get access to the apollo client in places outside of React where we can't use hooks.
export const apolloClientRef: ApolloClientRef = ((): ApolloClientRef => {
let apolloClient: ApolloClient<NormalizedCacheObject> | null = null
const listeners: Array<
(
value: ApolloClient<NormalizedCacheObject> | PromiseLike<ApolloClient<NormalizedCacheObject>>
) => void
> = []
const ref: ApolloClientRef = {
get current() {
return apolloClient
},
set current(newApolloClient) {
if (!newApolloClient) {
throw new Error("Can't set `apolloClient` to `null`")
}
if (apolloClient) {
throw new Error('`apolloClient` should not be updated after it has already been set')
}
apolloClient = newApolloClient
listeners.forEach((resolve) => resolve(newApolloClient))
},
onReady: async (): Promise<ApolloClient<NormalizedCacheObject>> => {
if (apolloClient) {
return Promise.resolve(apolloClient)
}
return new Promise<ApolloClient<NormalizedCacheObject>>((resolve) => listeners.push(resolve))
},
}
return ref
})()
const mmkv = new MMKV() const mmkv = new MMKV()
if (isNonJestDev) { if (isNonJestDev) {
...@@ -80,7 +123,7 @@ export const usePersistedApolloClient = (): ApolloClient<NormalizedCacheObject> ...@@ -80,7 +123,7 @@ export const usePersistedApolloClient = (): ApolloClient<NormalizedCacheObject>
}, },
}) })
apolloClient = newClient apolloClientRef.current = newClient
setClient(newClient) setClient(newClient)
// Ensure this callback only is computed once even if apolloLink changes, // Ensure this callback only is computed once even if apolloLink changes,
......
...@@ -9,17 +9,13 @@ import { useDebounce } from 'utilities/src/time/timing' ...@@ -9,17 +9,13 @@ import { useDebounce } from 'utilities/src/time/timing'
import { ElementName } from 'wallet/src/telemetry/constants' import { ElementName } from 'wallet/src/telemetry/constants'
import { import {
PASSWORD_VALIDATION_DEBOUNCE_MS, PASSWORD_VALIDATION_DEBOUNCE_MS,
PasswordErrors,
PasswordStrength, PasswordStrength,
getPasswordStrength, getPasswordStrength,
getPasswordStrengthTextAndColor, getPasswordStrengthTextAndColor,
isPasswordStrongEnough, isPasswordStrongEnough,
} from 'wallet/src/utils/password' } from 'wallet/src/utils/password'
export enum PasswordErrors {
WeakPassword = 'WeakPassword',
PasswordsDoNotMatch = 'PasswordsDoNotMatch',
}
export type CloudBackupPasswordProps = { export type CloudBackupPasswordProps = {
navigateToNextScreen: ({ password }: { password: string }) => void navigateToNextScreen: ({ password }: { password: string }) => void
isConfirmation?: boolean isConfirmation?: boolean
......
...@@ -9,7 +9,7 @@ import { backupMnemonicToCloudStorage } from 'src/features/CloudBackup/RNCloudSt ...@@ -9,7 +9,7 @@ import { backupMnemonicToCloudStorage } from 'src/features/CloudBackup/RNCloudSt
import { OnboardingScreens, Screens } from 'src/screens/Screens' import { OnboardingScreens, Screens } from 'src/screens/Screens'
import { Flex, Text, useSporeColors } from 'ui/src' import { Flex, Text, useSporeColors } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { getCloudProviderName } from 'uniswap/src/utils/platform' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { promiseMinDelay } from 'utilities/src/time/timing' import { promiseMinDelay } from 'utilities/src/time/timing'
......
...@@ -8,14 +8,14 @@ import { ...@@ -8,14 +8,14 @@ import {
} from 'wallet/src/features/transactions/types' } from 'wallet/src/features/transactions/types'
import { RootState } from 'wallet/src/state' import { RootState } from 'wallet/src/state'
import { signerMnemonicAccount } from 'wallet/src/test/fixtures' import { signerMnemonicAccount } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks' import { preloadedWalletState } from 'wallet/src/test/fixtures/wallet/redux'
const account = signerMnemonicAccount() const account = signerMnemonicAccount()
const MOCK_DATE_PROMPTED = Date.now() const MOCK_DATE_PROMPTED = Date.now()
const state = { const state = {
...mockWalletPreloadedState(), ...preloadedWalletState(),
wallet: { wallet: {
appRatingProvidedMs: MOCK_DATE_PROMPTED, appRatingProvidedMs: MOCK_DATE_PROMPTED,
}, },
......
import { preloadedMobileState } from 'src/test/fixtures'
import { act, renderHook, waitFor } from 'src/test/test-utils' import { act, renderHook, waitFor } from 'src/test/test-utils'
import { SAMPLE_CURRENCY_ID_1, portfolio, portfolioBalances } from 'wallet/src/test/fixtures' import { SAMPLE_CURRENCY_ID_1, portfolio, portfolioBalances } from 'wallet/src/test/fixtures'
import { mockWalletPreloadedState } from 'wallet/src/test/mocks'
import { queryResolvers } from 'wallet/src/test/utils' import { queryResolvers } from 'wallet/src/test/utils'
import { useBalances } from './balances' import { useBalances } from './balances'
const preloadedState = mockWalletPreloadedState() const preloadedState = preloadedMobileState()
describe(useBalances, () => { describe(useBalances, () => {
it('returns null if no currency was specified', async () => { it('returns null if no currency was specified', async () => {
......
import { TokenItemData } from 'src/components/explore/TokenItem' import { TokenItemData } from 'src/components/explore/TokenItem'
import { AppTFunction } from 'ui/src/i18n/types' import { AppTFunction } from 'ui/src/i18n/types'
import { TokenSortableField } from 'wallet/src/data/__generated__/types-and-hooks' import { TokenSortableField } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { import {
ClientTokensOrderBy, ClientTokensOrderBy,
TokenMetadataDisplayType, TokenMetadataDisplayType,
......
...@@ -19,10 +19,10 @@ import { ...@@ -19,10 +19,10 @@ import {
ScrollView, ScrollView,
Text, Text,
TouchableArea, TouchableArea,
getUniconV2Colors,
useIsDarkMode, useIsDarkMode,
useSporeColors, useSporeColors,
useUniconColors, useUniconColors,
useUniconV2Colors,
} from 'ui/src' } from 'ui/src'
import { ENS_LOGO } from 'ui/src/assets' import { ENS_LOGO } from 'ui/src/assets'
import { iconSizes, imageSizes } from 'ui/src/theme' import { iconSizes, imageSizes } from 'ui/src/theme'
...@@ -86,7 +86,7 @@ export const ProfileHeader = memo(function ProfileHeader({ ...@@ -86,7 +86,7 @@ export const ProfileHeader = memo(function ProfileHeader({
useUniconColors(address) useUniconColors(address)
// UniconV2 colors // UniconV2 colors
const { color } = useUniconV2Colors(address) const { color } = getUniconV2Colors(address)
// Wait for avatar, then render avatar extracted colors or unicon colors if no avatar // Wait for avatar, then render avatar extracted colors or unicon colors if no avatar
const fixedGradientColors: [string, string] = useMemo(() => { const fixedGradientColors: [string, string] = useMemo(() => {
......
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useAppDispatch } from 'src/app/hooks'
import Trace from 'src/components/Trace/Trace'
import { openModal } from 'src/features/modals/modalSlice'
import { MobileEventName } from 'src/features/telemetry/constants'
import { Flex, Icons, Text, TouchableArea, TouchableAreaProps, useSporeColors } from 'ui/src'
import FiatOnRampBackground from 'ui/src/assets/backgrounds/fiat-onramp-banner.svg'
import { iconSizes } from 'ui/src/theme'
import { ModalName } from 'wallet/src/telemetry/constants'
export function FiatOnRampBanner(props: TouchableAreaProps): JSX.Element {
const { t } = useTranslation()
const colors = useSporeColors()
const dispatch = useAppDispatch()
return (
<Trace logPress pressEvent={MobileEventName.FiatOnRampBannerPressed}>
<TouchableArea
backgroundColor="$DEP_fiatBanner"
borderRadius="$rounded12"
overflow="hidden"
p="$spacing12"
onPress={(): void => {
dispatch(openModal({ name: ModalName.FiatOnRamp }))
}}
{...props}
hapticFeedback>
<Flex fill position="absolute" right={0} top={0}>
<FiatOnRampBackground color={colors.sporeWhite.val} />
</Flex>
<Flex gap="$spacing4">
<Flex row justifyContent="space-between">
<Text color="$sporeWhite" variant="buttonLabel2">
{t('fiatOnRamp.banner.title')}
</Text>
<Icons.RotatableChevron color="$sporeWhite" direction="end" width={iconSizes.icon20} />
</Flex>
<Text color="$sporeWhite" opacity={0.72} variant="subheading2">
{t('fiatOnRamp.banner.subtitle')}
</Text>
</Flex>
</TouchableArea>
</Trace>
)
}
...@@ -22,7 +22,7 @@ import { useDebounce } from 'utilities/src/time/timing' ...@@ -22,7 +22,7 @@ import { useDebounce } from 'utilities/src/time/timing'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { useBottomSheetFocusHook } from 'wallet/src/components/modals/hooks' import { useBottomSheetFocusHook } from 'wallet/src/components/modals/hooks'
import { useFiatOnRampAggregatorCountryListQuery } from 'wallet/src/features/fiatOnRamp/api' import { useFiatOnRampAggregatorCountryListQuery } from 'wallet/src/features/fiatOnRamp/api'
import { FORSupportedCountry } from 'wallet/src/features/fiatOnRamp/types' import { FORCountry } from 'wallet/src/features/fiatOnRamp/types'
import { getCountryFlagSvgUrl } from 'wallet/src/features/fiatOnRamp/utils' import { getCountryFlagSvgUrl } from 'wallet/src/features/fiatOnRamp/utils'
import { SearchTextInput } from 'wallet/src/features/search/SearchTextInput' import { SearchTextInput } from 'wallet/src/features/search/SearchTextInput'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
...@@ -30,11 +30,11 @@ import { ModalName } from 'wallet/src/telemetry/constants' ...@@ -30,11 +30,11 @@ import { ModalName } from 'wallet/src/telemetry/constants'
const ICON_SIZE = 32 // design prefers a custom value here const ICON_SIZE = 32 // design prefers a custom value here
interface CountrySelectorProps { interface CountrySelectorProps {
onSelectCountry: (country: FORSupportedCountry) => void onSelectCountry: (country: FORCountry) => void
countryCode: string countryCode: string
} }
function key(item: FORSupportedCountry): string { function key(item: FORCountry): string {
return item.countryCode return item.countryCode
} }
...@@ -52,7 +52,7 @@ function CountrySelectorContent({ ...@@ -52,7 +52,7 @@ function CountrySelectorContent({
const debouncedSearchText = useDebounce(searchText) const debouncedSearchText = useDebounce(searchText)
const filteredData: FORSupportedCountry[] = useMemo(() => { const filteredData: FORCountry[] = useMemo(() => {
if (!data) { if (!data) {
return [] return []
} }
...@@ -64,7 +64,7 @@ function CountrySelectorContent({ ...@@ -64,7 +64,7 @@ function CountrySelectorContent({
}, [countryCode, data, debouncedSearchText]) }, [countryCode, data, debouncedSearchText])
const renderItem = useCallback( const renderItem = useCallback(
({ item }: ListRenderItemInfo<FORSupportedCountry>): JSX.Element => { ({ item }: ListRenderItemInfo<FORCountry>): JSX.Element => {
const countryFlagUrl = getCountryFlagSvgUrl(item.countryCode) const countryFlagUrl = getCountryFlagSvgUrl(item.countryCode)
return ( return (
......
...@@ -12,9 +12,6 @@ import { ...@@ -12,9 +12,6 @@ import {
import { useMoonpayFiatOnRamp, useMoonpaySupportedTokens } from 'src/features/fiatOnRamp/hooks' import { useMoonpayFiatOnRamp, useMoonpaySupportedTokens } from 'src/features/fiatOnRamp/hooks'
import { FiatOnRampCurrency } from 'src/features/fiatOnRamp/types' import { FiatOnRampCurrency } from 'src/features/fiatOnRamp/types'
import { closeModal } from 'src/features/modals/modalSlice' import { closeModal } from 'src/features/modals/modalSlice'
import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { MobileEventName } from 'src/features/telemetry/constants'
import { MobileEventProperties } from 'src/features/telemetry/types'
import { AnimatedFlex, Flex, Text, useDeviceInsets, useSporeColors } from 'ui/src' import { AnimatedFlex, Flex, Text, useDeviceInsets, useSporeColors } from 'ui/src'
import MoonpayLogo from 'ui/src/assets/logos/svg/moonpay.svg' import MoonpayLogo from 'ui/src/assets/logos/svg/moonpay.svg'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
...@@ -29,7 +26,9 @@ import { ChainId } from 'wallet/src/constants/chains' ...@@ -29,7 +26,9 @@ import { ChainId } from 'wallet/src/constants/chains'
import { useMoonpayFiatCurrencySupportInfo } from 'wallet/src/features/fiatOnRamp/hooks' import { useMoonpayFiatCurrencySupportInfo } from 'wallet/src/features/fiatOnRamp/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext' import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { useCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo' import { useCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo'
import { ModalName } from 'wallet/src/telemetry/constants' import { sendWalletAnalyticsEvent } from 'wallet/src/telemetry'
import { FiatOnRampEventName, ModalName } from 'wallet/src/telemetry/constants'
import { WalletEventProperties } from 'wallet/src/telemetry/types'
import { buildCurrencyId } from 'wallet/src/utils/currencyId' import { buildCurrencyId } from 'wallet/src/utils/currencyId'
import { openUri } from 'wallet/src/utils/linking' import { openUri } from 'wallet/src/utils/linking'
import { FiatOnRampTokenSelectorModal } from './FiatOnRampTokenSelector' import { FiatOnRampTokenSelectorModal } from './FiatOnRampTokenSelector'
...@@ -126,6 +125,7 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element { ...@@ -126,6 +125,7 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
} = useMoonpayFiatOnRamp({ } = useMoonpayFiatOnRamp({
baseCurrencyAmount: value, baseCurrencyAmount: value,
quoteCurrencyCode: currency.moonpayCurrencyCode, quoteCurrencyCode: currency.moonpayCurrencyCode,
quoteChainId: currency.currencyInfo?.currency.chainId ?? ChainId.Mainnet,
}) })
useTimeout( useTimeout(
...@@ -144,9 +144,9 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element { ...@@ -144,9 +144,9 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
!isLoading && (!eligible || (!isError && fiatOnRampHostUrl && quoteCurrencyAmountReady)) !isLoading && (!eligible || (!isError && fiatOnRampHostUrl && quoteCurrencyAmountReady))
const onChangeValue = const onChangeValue =
(source: MobileEventProperties[MobileEventName.FiatOnRampAmountEntered]['source']) => (source: WalletEventProperties[FiatOnRampEventName.FiatOnRampAmountEntered]['source']) =>
(newAmount: string): void => { (newAmount: string): void => {
sendMobileAnalyticsEvent(MobileEventName.FiatOnRampAmountEntered, { sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampAmountEntered, {
source, source,
}) })
setValue(newAmount) setValue(newAmount)
...@@ -175,6 +175,16 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element { ...@@ -175,6 +175,16 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
const insets = useDeviceInsets() const insets = useDeviceInsets()
const onSelectCurrency = (newCurrency: FiatOnRampCurrency): void => {
setCurrency(newCurrency)
setShowTokenSelector(false)
if (newCurrency.currencyInfo?.currency.symbol) {
sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampTokenSelected, {
token: newCurrency.currencyInfo.currency.symbol.toLowerCase(),
})
}
}
return ( return (
<Flex grow pt={showConnectingToMoonpayScreen ? undefined : insets.top}> <Flex grow pt={showConnectingToMoonpayScreen ? undefined : insets.top}>
{!showConnectingToMoonpayScreen && ( {!showConnectingToMoonpayScreen && (
...@@ -258,10 +268,7 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element { ...@@ -258,10 +268,7 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element {
loading={supportedTokensLoading} loading={supportedTokensLoading}
onClose={(): void => setShowTokenSelector(false)} onClose={(): void => setShowTokenSelector(false)}
onRetry={supportedTokensRefetch} onRetry={supportedTokensRefetch}
onSelectCurrency={(newCurrency: FiatOnRampCurrency): void => { onSelectCurrency={onSelectCurrency}
setCurrency(newCurrency)
setShowTokenSelector(false)
}}
/> />
)} )}
</AnimatedFlex> </AnimatedFlex>
......
...@@ -4,11 +4,14 @@ import { useCallback, useMemo, useRef } from 'react' ...@@ -4,11 +4,14 @@ import { useCallback, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useAppDispatch } from 'src/app/hooks' import { useAppDispatch } from 'src/app/hooks'
import { Delay } from 'src/components/layout/Delayed' import { Delay } from 'src/components/layout/Delayed'
import { FiatOnRampCurrency } from 'src/features/fiatOnRamp/types'
import { ColorTokens, useSporeColors } from 'ui/src' import { ColorTokens, useSporeColors } from 'ui/src'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'uniswap/src/utils/platform'
import { logger } from 'utilities/src/logger/logger'
import { useDebounce } from 'utilities/src/time/timing' import { useDebounce } from 'utilities/src/time/timing'
import { useAllCommonBaseCurrencies } from 'wallet/src/components/TokenSelector/hooks' import { useAllCommonBaseCurrencies } from 'wallet/src/components/TokenSelector/hooks'
import { BRIDGED_BASE_ADDRESSES } from 'wallet/src/constants/addresses'
import { ChainId } from 'wallet/src/constants/chains' import { ChainId } from 'wallet/src/constants/chains'
import { fromMoonpayNetwork } from 'wallet/src/features/chains/utils' import { fromMoonpayNetwork } from 'wallet/src/features/chains/utils'
import { CurrencyInfo } from 'wallet/src/features/dataApi/types' import { CurrencyInfo } from 'wallet/src/features/dataApi/types'
...@@ -32,9 +35,13 @@ import { ...@@ -32,9 +35,13 @@ import {
} from 'wallet/src/features/transactions/types' } from 'wallet/src/features/transactions/types'
import { createTransactionId } from 'wallet/src/features/transactions/utils' import { createTransactionId } from 'wallet/src/features/transactions/utils'
import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks'
import { areAddressesEqual } from 'wallet/src/utils/addresses'
import { getFormattedCurrencyAmount } from 'wallet/src/utils/currency' import { getFormattedCurrencyAmount } from 'wallet/src/utils/currency'
import { ValueType } from 'wallet/src/utils/getCurrencyAmount' import { ValueType } from 'wallet/src/utils/getCurrencyAmount'
import { FiatOnRampCurrency } from './types'
const ETH_POLYGON_MOONPAY_CODE = 'eth_polygon'
const WETH_POLYGON_MOONPAY_CODE = 'weth_polygon'
const BNB_MAINNET_MOONPAY_CODE = 'bnb'
export function useFormatExactCurrencyAmount( export function useFormatExactCurrencyAmount(
currencyAmount: string, currencyAmount: string,
...@@ -61,6 +68,7 @@ export function useFormatExactCurrencyAmount( ...@@ -61,6 +68,7 @@ export function useFormatExactCurrencyAmount(
/** Returns a new externalTransactionId and a callback to store the transaction. */ /** Returns a new externalTransactionId and a callback to store the transaction. */
export function useFiatOnRampTransactionCreator( export function useFiatOnRampTransactionCreator(
ownerAddress: string, ownerAddress: string,
chainId: ChainId,
initialTypeInfo?: Partial<FiatPurchaseTransactionInfo> initialTypeInfo?: Partial<FiatPurchaseTransactionInfo>
): { ): {
externalTransactionId: string externalTransactionId: string
...@@ -74,7 +82,7 @@ export function useFiatOnRampTransactionCreator( ...@@ -74,7 +82,7 @@ export function useFiatOnRampTransactionCreator(
// adds a dummy transaction detail for now // adds a dummy transaction detail for now
// later, we will attempt to look up information for that id // later, we will attempt to look up information for that id
const transactionDetail: TransactionDetails = { const transactionDetail: TransactionDetails = {
chainId: ChainId.Mainnet, chainId,
id: externalTransactionId.current, id: externalTransactionId.current,
from: ownerAddress, from: ownerAddress,
typeInfo: { typeInfo: {
...@@ -89,7 +97,7 @@ export function useFiatOnRampTransactionCreator( ...@@ -89,7 +97,7 @@ export function useFiatOnRampTransactionCreator(
} }
// use addTransaction action so transactionWatcher picks it up // use addTransaction action so transactionWatcher picks it up
dispatch(addTransaction(transactionDetail)) dispatch(addTransaction(transactionDetail))
}, [dispatch, ownerAddress, initialTypeInfo]) }, [initialTypeInfo, chainId, ownerAddress, dispatch])
return { externalTransactionId: externalTransactionId.current, dispatchAddTransaction } return { externalTransactionId: externalTransactionId.current, dispatchAddTransaction }
} }
...@@ -102,9 +110,11 @@ const MOONPAY_FEES_INCLUDED = true ...@@ -102,9 +110,11 @@ const MOONPAY_FEES_INCLUDED = true
export function useMoonpayFiatOnRamp({ export function useMoonpayFiatOnRamp({
baseCurrencyAmount, baseCurrencyAmount,
quoteCurrencyCode, quoteCurrencyCode,
quoteChainId,
}: { }: {
baseCurrencyAmount: string baseCurrencyAmount: string
quoteCurrencyCode: string | undefined quoteCurrencyCode: string | undefined
quoteChainId: ChainId
}): { }): {
eligible: boolean eligible: boolean
quoteAmount: number quoteAmount: number
...@@ -126,8 +136,10 @@ export function useMoonpayFiatOnRamp({ ...@@ -126,8 +136,10 @@ export function useMoonpayFiatOnRamp({
// for now, always assume the user wants to fund the current account // for now, always assume the user wants to fund the current account
const activeAccountAddress = useActiveAccountAddressWithThrow() const activeAccountAddress = useActiveAccountAddressWithThrow()
const { externalTransactionId, dispatchAddTransaction } = const { externalTransactionId, dispatchAddTransaction } = useFiatOnRampTransactionCreator(
useFiatOnRampTransactionCreator(activeAccountAddress) activeAccountAddress,
quoteChainId
)
const { moonpaySupportedFiatCurrency: baseCurrency } = useMoonpayFiatCurrencySupportInfo() const { moonpaySupportedFiatCurrency: baseCurrency } = useMoonpayFiatCurrencySupportInfo()
const baseCurrencyCode = baseCurrency.code.toLowerCase() const baseCurrencyCode = baseCurrency.code.toLowerCase()
...@@ -307,16 +319,38 @@ function findTokenOptionForMoonpayCurrency( ...@@ -307,16 +319,38 @@ function findTokenOptionForMoonpayCurrency(
commonBaseCurrencies: CurrencyInfo[] | undefined = [], commonBaseCurrencies: CurrencyInfo[] | undefined = [],
moonpayCurrency: MoonpayCurrency moonpayCurrency: MoonpayCurrency
): Maybe<CurrencyInfo> { ): Maybe<CurrencyInfo> {
return commonBaseCurrencies.find((item) => { const currencyInfo = commonBaseCurrencies.find((item) => {
const [code, network] = moonpayCurrency.code.split('_') // Moonpay uses WETH on Polygon to represent ETH on Polygon
const moonpayCurrencyCode =
moonpayCurrency.code === ETH_POLYGON_MOONPAY_CODE
? WETH_POLYGON_MOONPAY_CODE
: moonpayCurrency.code
const [tokenSymbol, network] = moonpayCurrencyCode.split('_')
const chainId = fromMoonpayNetwork(network) const chainId = fromMoonpayNetwork(network)
return ( return (
item && item &&
code && tokenSymbol &&
code === item.currency.symbol?.toLowerCase() && tokenSymbol.toLowerCase() === item.currency.symbol?.toLowerCase() &&
chainId === item.currency.chainId chainId === item.currency.chainId
) )
}) })
if (
!currencyInfo &&
!BRIDGED_BASE_ADDRESSES.find((bridgedAddress) =>
areAddressesEqual(bridgedAddress, moonpayCurrency.metadata?.contractAddress)
) &&
// We do not support BNB onboarding and Moonpay does not return an address for it so map it manually
moonpayCurrency.code !== BNB_MAINNET_MOONPAY_CODE
) {
logger.error(`Moonpay currency ${moonpayCurrency.code} cannot be mapped`, {
tags: { file: 'fiatOnRamp/hooks', function: 'useMoonpaySupportedTokens' },
extra: {
chainId: moonpayCurrency.metadata?.chainId,
address: moonpayCurrency.metadata?.contractAddress,
},
})
}
return currencyInfo
} }
export function useFiatOnRampSupportedTokens({ export function useFiatOnRampSupportedTokens({
......
...@@ -9,6 +9,7 @@ import { ColorTokens, Flex, useSporeColors } from 'ui/src' ...@@ -9,6 +9,7 @@ import { ColorTokens, Flex, useSporeColors } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { isAndroid } from 'uniswap/src/utils/platform' import { isAndroid } from 'uniswap/src/utils/platform'
import { TextInput } from 'wallet/src/components/input/TextInput' import { TextInput } from 'wallet/src/components/input/TextInput'
import { ElementName } from 'wallet/src/telemetry/constants'
interface Props { interface Props {
alwaysShowInputSuffix?: boolean alwaysShowInputSuffix?: boolean
...@@ -122,7 +123,7 @@ function Inputs({ ...@@ -122,7 +123,7 @@ function Inputs({
scrollEnabled={false} scrollEnabled={false}
selectionColor={colors.neutral1.val} selectionColor={colors.neutral1.val}
spellCheck={false} spellCheck={false}
testID="import_account_form/input" testID={ElementName.ImportAccountInput}
textAlign={isInputEmpty ? 'left' : backgroundTextAlignment} textAlign={isInputEmpty ? 'left' : backgroundTextAlignment}
textAlignVertical="bottom" textAlignVertical="bottom"
value={value} value={value}
......
...@@ -99,7 +99,7 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` ...@@ -99,7 +99,7 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = `
"width": 8, "width": 8,
} }
} }
testID="import_account_form/input" testID="import-account-input"
textAlignVertical="bottom" textAlignVertical="bottom"
/> />
</View> </View>
......
...@@ -2,14 +2,14 @@ import { createStore, Store } from '@reduxjs/toolkit' ...@@ -2,14 +2,14 @@ import { createStore, Store } from '@reduxjs/toolkit'
import { ScannerModalState } from 'src/components/QRCodeScanner/constants' import { ScannerModalState } from 'src/components/QRCodeScanner/constants'
import { import {
closeModal, closeModal,
initialModalState, initialModalsState,
modalsReducer, modalsReducer,
openModal, openModal,
} from 'src/features/modals/modalSlice' } from 'src/features/modals/modalSlice'
import { ModalName } from 'wallet/src/telemetry/constants' import { ModalName } from 'wallet/src/telemetry/constants'
import { ModalsState } from './ModalsState' import { ModalsState } from './ModalsState'
const initialState = { ...initialModalState } const initialState = { ...initialModalsState }
const modalName = ModalName.WalletConnectScan const modalName = ModalName.WalletConnectScan
describe('modals reducer', () => { describe('modals reducer', () => {
......
...@@ -101,7 +101,7 @@ export type OpenModalParams = ...@@ -101,7 +101,7 @@ export type OpenModalParams =
export type CloseModalParams = { name: keyof ModalsState } export type CloseModalParams = { name: keyof ModalsState }
export const initialModalState: ModalsState = { export const initialModalsState: ModalsState = {
[ModalName.ExchangeTransferModal]: { [ModalName.ExchangeTransferModal]: {
isOpen: false, isOpen: false,
initialState: undefined, initialState: undefined,
...@@ -174,7 +174,7 @@ export const initialModalState: ModalsState = { ...@@ -174,7 +174,7 @@ export const initialModalState: ModalsState = {
const slice = createSlice({ const slice = createSlice({
name: 'modals', name: 'modals',
initialState: initialModalState, initialState: initialModalsState,
reducers: { reducers: {
openModal: (state, action: PayloadAction<OpenModalParams>) => { openModal: (state, action: PayloadAction<OpenModalParams>) => {
const { name, initialState } = action.payload const { name, initialState } = action.payload
......
...@@ -3,9 +3,9 @@ import React from 'react' ...@@ -3,9 +3,9 @@ import React from 'react'
import { StyleSheet } from 'react-native' 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 { isIOS } from 'uniswap/src/utils/platform' import { isIOS } from 'uniswap/src/utils/platform'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { IAmount } from 'wallet/src/data/__generated__/types-and-hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext' import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
type ListPriceProps = FlexProps & { type ListPriceProps = FlexProps & {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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