ci(release): publish latest release

parent 48a0bb2c
- Onboarding improvements Here are the latest updates:
- Adds fallback support method for opening the side panel on chrome failure
Native Asset Top Up: Easily access our fiat on ramp feature if you don’t have enough native asset on a network to complete a transaction.
Other changes:
- Various bug fixes and performance improvements - Various bug fixes and performance improvements
- Improved gas estimation for Send transactions
extension/1.2.1 mobile/1.32
\ No newline at end of file \ No newline at end of file
import '@tamagui/core/reset.css'
import 'src/app/Global.css'
import { useEffect } from 'react'
import { I18nextProvider, useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { RouterProvider } from 'react-router-dom'
import { PersistGate } from 'redux-persist/integration/react'
import { ExtensionStatsigProvider } from 'src/app/StatsigProvider'
import { GraphqlProvider } from 'src/app/apollo'
import { ErrorElement } from 'src/app/components/ErrorElement'
import { TraceUserProperties } from 'src/app/components/Trace/TraceUserProperties'
import { DappContextProvider } from 'src/app/features/dapp/DappContext'
import { SentryAppNameTag, initializeSentry, sentryCreateHashRouter } from 'src/app/sentry'
import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { getLocalUserId } from 'src/app/utils/storage'
import { getReduxPersistor, getReduxStore } from 'src/store/store'
import { Button, Flex, Image, Text } from 'ui/src'
import { CHROME_LOGO, UNISWAP_LOGO } from 'ui/src/assets'
import { iconSizes, spacing } from 'ui/src/theme'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n'
import { ExtensionScreens } from 'uniswap/src/types/screens/extension'
import { logger } from 'utilities/src/logger/logger'
import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary'
import { LocalizationContextProvider } from 'wallet/src/features/language/LocalizationContext'
import { syncAppWithDeviceLanguage } from 'wallet/src/features/language/slice'
import { SharedProvider } from 'wallet/src/provider'
getLocalUserId()
.then((userId) => {
initializeSentry(SentryAppNameTag.Popup, userId)
})
.catch((error) => {
logger.error(error, {
tags: { file: 'PopupApp.tsx', function: 'getLocalUserId' },
})
})
const router = sentryCreateHashRouter([
{
path: '',
element: <PopupContent />,
errorElement: <ErrorElement />,
},
])
function PopupContent(): JSX.Element {
const { t } = useTranslation()
const dispatch = useDispatch()
useEffect(() => {
dispatch(syncAppWithDeviceLanguage())
}, [dispatch])
const searchParams = new URLSearchParams(window.location.search)
const tabId = searchParams.get('tabId')
const windowId = searchParams.get('windowId')
const tabIdNumber = tabId ? Number(tabId) : undefined
const windowIdNumber = windowId ? Number(windowId) : undefined
return (
<Trace logImpression screen={ExtensionScreens.PopupOpenExtension}>
<Flex fill gap="$spacing16" height="100%" px="$spacing24" py="$spacing24">
<Flex row>
<Flex position="relative">
<Image height={iconSizes.icon40} source={UNISWAP_LOGO} width={iconSizes.icon40} />
<Flex
backgroundColor="$surface1"
borderColor="$surface3"
borderRadius={6}
borderWidth={1}
bottom={-spacing.spacing4}
p="$spacing2"
position="absolute"
right={-spacing.spacing4}
>
<Image height={iconSizes.icon12} source={CHROME_LOGO} width={iconSizes.icon12} />
</Flex>
</Flex>
</Flex>
<Flex gap="$spacing4">
<Text color="$neutral1" variant="subheading1">
{t('extension.popup.chrome.title')}
</Text>
<Text color="$neutral2" variant="body2">
{t('extension.popup.chrome.description')}
</Text>
</Flex>
<Flex fill />
<Trace logPress element={ElementName.ExtensionPopupOpenButton}>
<Button
theme="primary"
width="100%"
onPress={async () => {
if (windowIdNumber) {
// eslint-disable-next-line security/detect-non-literal-fs-filename
await chrome.sidePanel.open({ tabId: tabIdNumber, windowId: windowIdNumber })
window.close()
}
}}
>
{t('extension.popup.chrome.button')}
</Button>
</Trace>
</Flex>
</Trace>
)
}
// TODO WALL-4313 - Backup for some broken chrome.sidePanel.open functionality
// Consider removing this once the issue is resolved or leaving as fallback
export default function PopupApp(): JSX.Element {
// initialize analytics on load
useEffect(() => {
initExtensionAnalytics().catch(() => undefined)
}, [])
return (
<Trace>
<PersistGate persistor={getReduxPersistor()}>
<ExtensionStatsigProvider>
<I18nextProvider i18n={i18n}>
<SharedProvider reduxStore={getReduxStore()}>
<ErrorBoundary>
<GraphqlProvider>
<LocalizationContextProvider>
<UnitagUpdaterContextProvider>
<TraceUserProperties />
<DappContextProvider>
<RouterProvider router={router} />
</DappContextProvider>
</UnitagUpdaterContextProvider>
</LocalizationContextProvider>
</GraphqlProvider>
</ErrorBoundary>
</SharedProvider>
</I18nextProvider>
</ExtensionStatsigProvider>
</PersistGate>
</Trace>
)
}
...@@ -36,7 +36,7 @@ export const useExtensionNavigation = (): { ...@@ -36,7 +36,7 @@ export const useExtensionNavigation = (): {
export async function focusOrCreateOnboardingTab(page?: string): Promise<void> { export async function focusOrCreateOnboardingTab(page?: string): Promise<void> {
const extension = await chrome.management.getSelf() const extension = await chrome.management.getSelf()
const tabs = await chrome.tabs.query({ url: `chrome-extension://${extension.id}/onboarding.html*` }) const tabs = await chrome.tabs.query({ url: `chrome-extension://${extension.id}/*` })
const tab = tabs[0] const tab = tabs[0]
const url = 'onboarding.html#/' + (page ? page : TopLevelRoutes.Onboarding) const url = 'onboarding.html#/' + (page ? page : TopLevelRoutes.Onboarding)
...@@ -68,44 +68,6 @@ export async function focusOrCreateOnboardingTab(page?: string): Promise<void> { ...@@ -68,44 +68,6 @@ export async function focusOrCreateOnboardingTab(page?: string): Promise<void> {
}) })
} }
export async function focusOrCreateDappRequestWindow(tabId: number | undefined, windowId: number): Promise<void> {
const extension = await chrome.management.getSelf()
const window = await chrome.windows.getCurrent()
const tabs = await chrome.tabs.query({ url: `chrome-extension://${extension.id}/popup.html*` })
const tab = tabs[0]
// Centering within current window
const height = 410
const width = 330
const top = Math.round((window.top ?? 0) + ((window.height ?? height) - height) / 2)
const left = Math.round((window.left ?? 0) + ((window.width ?? width) - width) / 2)
let url = `popup.html?windowId=${windowId}`
if (tabId) {
url += `&tabId=${tabId}`
}
if (!tab?.id) {
await chrome.windows.create({
url,
type: 'popup',
top,
left,
width,
height,
})
return
}
await chrome.tabs.update(tab.id, {
url,
active: true,
highlighted: true,
})
await chrome.windows.update(tab.windowId, { focused: true, top, left, width, height })
}
/** /**
* To avoid opening too many tabs while also ensuring that we don't take over the user's active tab, * To avoid opening too many tabs while also ensuring that we don't take over the user's active tab,
* we only update the URL of the active tab if it's already in a specific route of the Uniswap interface. * we only update the URL of the active tab if it's already in a specific route of the Uniswap interface.
......
...@@ -19,7 +19,6 @@ export const enum SentryAppNameTag { ...@@ -19,7 +19,6 @@ export const enum SentryAppNameTag {
Onboarding = 'onboarding', Onboarding = 'onboarding',
ContentScript = 'content-script', ContentScript = 'content-script',
Background = 'background', Background = 'background',
Popup = 'popup',
} }
export function initializeSentry(appNameTag: SentryAppNameTag, sentryUserId: string): void { export function initializeSentry(appNameTag: SentryAppNameTag, sentryUserId: string): void {
......
import { focusOrCreateDappRequestWindow } from 'src/app/navigation/utils'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
export async function openSidePanel(tabId: number | undefined, windowId: number): Promise<void> { export async function openSidePanel(tabId: number | undefined, windowId: number): Promise<void> {
...@@ -9,10 +8,6 @@ export async function openSidePanel(tabId: number | undefined, windowId: number) ...@@ -9,10 +8,6 @@ export async function openSidePanel(tabId: number | undefined, windowId: number)
windowId, windowId,
}) })
} catch (error) { } catch (error) {
// TODO WALL-4313 - Backup for some broken chrome.sidePanel.open functionality
// Consider removing this once the issue is resolved or leaving as fallback
await focusOrCreateDappRequestWindow(tabId, windowId)
logger.error(error, { logger.error(error, {
tags: { tags: {
file: 'background/background.ts', file: 'background/background.ts',
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Uniswap Extension", "name": "Uniswap Extension",
"description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.", "description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.",
"version": "1.2.1", "version": "1.2.0",
"minimum_chrome_version": "116", "minimum_chrome_version": "116",
"icons": { "icons": {
"16": "assets/icon16.png", "16": "assets/icon16.png",
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" href="assets/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="assets/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<style>
html,
body,
#popup-root {
min-height: 100%;
}
* {
font-family: 'Basel', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Basel var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Basel';
font-weight: 500;
font-style: normal;
font-display: block;
font-named-instance: 'Medium';
src:
url(assets/fonts/Basel-Medium.woff) format('woff');
}
@font-face {
font-family: 'Basel';
font-weight: 400;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(assets/fonts/Basel-Book.woff) format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
html {
font-variant: none;
font-smooth: always;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
</style>
<title>Uniswap Extension</title>
</head>
<body>
<div id="popup-root"></div>
<script type="module" src="popup.js"></script>
</body>
</html>
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../../../../index.d.ts" />
import { createRoot } from 'react-dom/client'
import { OptionalStrictMode } from 'src/app/components/OptionalStrictMode'
import PopupApp from 'src/app/PopupApp'
import { initializeSentry, SentryAppNameTag } from 'src/app/sentry'
import { getLocalUserId } from 'src/app/utils/storage'
import { initializeReduxStore } from 'src/store/store'
import { logger } from 'utilities/src/logger/logger'
;(globalThis as any).regeneratorRuntime = undefined // eslint-disable-line @typescript-eslint/no-explicit-any
// The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem
// see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326
getLocalUserId()
.then((userId) => {
initializeSentry(SentryAppNameTag.Popup, userId)
})
.catch((error) => {
logger.error(error, {
tags: { file: 'popup.tsx', function: 'getLocalUserId' },
})
})
async function initPopup(): Promise<void> {
await initializeReduxStore({ readOnly: true })
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const container = document.getElementById('popup-root')!
const root = createRoot(container)
root.render(
<OptionalStrictMode>
<PopupApp />
</OptionalStrictMode>,
)
}
initPopup().catch((error) => {
logger.error(error, {
tags: {
file: 'popup.tsx',
function: 'initPopup',
},
})
})
...@@ -58,7 +58,7 @@ const setupStore = (preloadedState?: PreloadedState<ExtensionState>): ReturnType ...@@ -58,7 +58,7 @@ const setupStore = (preloadedState?: PreloadedState<ExtensionState>): ReturnType
let store: ReturnType<typeof setupStore> | undefined let store: ReturnType<typeof setupStore> | undefined
let persistor: ReturnType<typeof persistStore> | undefined let persistor: ReturnType<typeof persistStore> | undefined
export async function initializeReduxStore(args?: { readOnly?: boolean }): Promise<{ export async function initializeReduxStore(): Promise<{
store: ReturnType<typeof setupStore> store: ReturnType<typeof setupStore>
persistor: ReturnType<typeof persistStore> persistor: ReturnType<typeof persistStore>
}> { }> {
...@@ -69,12 +69,6 @@ export async function initializeReduxStore(args?: { readOnly?: boolean }): Promi ...@@ -69,12 +69,6 @@ export async function initializeReduxStore(args?: { readOnly?: boolean }): Promi
store = setupStore(oldStore) store = setupStore(oldStore)
persistor = persistStore(store) persistor = persistStore(store)
if (args?.readOnly) {
// This means the store will be initialized with the persisted state from disk, but it won't persist any changes.
// Only useful for use cases where we don't want to modify the state (for example, a popup window instead of the sidebar).
persistor.pause()
}
// We wait a few seconds to make sure the store is fully initialized and persisted before deleting the old storage. // We wait a few seconds to make sure the store is fully initialized and persisted before deleting the old storage.
// This is needed because otherwise the background script might think the user is not onboarded if it reads the storage while it's being migrated. // This is needed because otherwise the background script might think the user is not onboarded if it reads the storage while it's being migrated.
if (oldStore) { if (oldStore) {
......
...@@ -172,7 +172,6 @@ module.exports = (env) => { ...@@ -172,7 +172,6 @@ module.exports = (env) => {
sidebar: './src/sidebar/sidebar.tsx', sidebar: './src/sidebar/sidebar.tsx',
injected: './src/contentScript/injected.ts', injected: './src/contentScript/injected.ts',
ethereum: './src/contentScript/ethereum.ts', ethereum: './src/contentScript/ethereum.ts',
popup: './src/popup/popup.tsx',
}, },
output: { output: {
filename: '[name].js', filename: '[name].js',
......
...@@ -93,8 +93,7 @@ if (!__DEV__ && !isDetoxBuild) { ...@@ -93,8 +93,7 @@ if (!__DEV__ && !isDetoxBuild) {
environment: getSentryEnvironment(), environment: getSentryEnvironment(),
dsn: config.sentryDsn, dsn: config.sentryDsn,
attachViewHierarchy: true, attachViewHierarchy: true,
// DataDog would do this for us now enableCaptureFailedRequests: true,
enableCaptureFailedRequests: false,
tracesSampleRate: getSentryTracesSamplingRate(), tracesSampleRate: getSentryTracesSamplingRate(),
integrations: [ integrations: [
new Sentry.ReactNativeTracing({ new Sentry.ReactNativeTracing({
...@@ -175,26 +174,24 @@ function App(): JSX.Element | null { ...@@ -175,26 +174,24 @@ function App(): JSX.Element | null {
return ( return (
<StatsigProvider {...statSigOptions}> <StatsigProvider {...statSigOptions}>
<DatadogProviderWrapper> <Trace>
<Trace> <StrictMode>
<StrictMode> <I18nextProvider i18n={i18n}>
<I18nextProvider i18n={i18n}> <SentryTags>
<SentryTags> <SafeAreaProvider>
<SafeAreaProvider> <SharedProvider reduxStore={store}>
<SharedProvider reduxStore={store}> <AnalyticsNavigationContextProvider
<AnalyticsNavigationContextProvider shouldLogScreen={shouldLogScreen}
shouldLogScreen={shouldLogScreen} useIsPartOfNavigationTree={useIsPartOfNavigationTree}
useIsPartOfNavigationTree={useIsPartOfNavigationTree} >
> <AppOuter />
<AppOuter /> </AnalyticsNavigationContextProvider>
</AnalyticsNavigationContextProvider> </SharedProvider>
</SharedProvider> </SafeAreaProvider>
</SafeAreaProvider> </SentryTags>
</SentryTags> </I18nextProvider>
</I18nextProvider> </StrictMode>
</StrictMode> </Trace>
</Trace>
</DatadogProviderWrapper>
</StatsigProvider> </StatsigProvider>
) )
} }
...@@ -248,38 +245,40 @@ function AppOuter(): JSX.Element | null { ...@@ -248,38 +245,40 @@ function AppOuter(): JSX.Element | null {
<ApolloProvider client={client}> <ApolloProvider client={client}>
<PersistGate loading={null} persistor={persistor}> <PersistGate loading={null} persistor={persistor}>
<ErrorBoundary> <ErrorBoundary>
<LocalizationContextProvider> <DatadogProviderWrapper>
<GestureHandlerRootView style={flexStyles.fill}> <LocalizationContextProvider>
<WalletContextProvider> <GestureHandlerRootView style={flexStyles.fill}>
<UnitagUpdaterContextProvider> <WalletContextProvider>
<BiometricContextProvider> <UnitagUpdaterContextProvider>
<LockScreenContextProvider> <BiometricContextProvider>
<Sentry.TouchEventBoundary> <LockScreenContextProvider>
<DataUpdaters /> <Sentry.TouchEventBoundary>
<NavigationContainer <DataUpdaters />
onReady={(navigationRef): void => { <NavigationContainer
routingInstrumentation.registerNavigationContainer(navigationRef) onReady={(navigationRef): void => {
}} routingInstrumentation.registerNavigationContainer(navigationRef)
> }}
<MobileWalletNavigationProvider> >
<OpenAIContextProvider> <MobileWalletNavigationProvider>
<BottomSheetModalProvider> <OpenAIContextProvider>
<AppModals /> <BottomSheetModalProvider>
<PerformanceProfiler onReportPrepared={onReportPrepared}> <AppModals />
<AppInner /> <PerformanceProfiler onReportPrepared={onReportPrepared}>
</PerformanceProfiler> <AppInner />
</BottomSheetModalProvider> </PerformanceProfiler>
<NotificationToastWrapper /> </BottomSheetModalProvider>
</OpenAIContextProvider> <NotificationToastWrapper />
</MobileWalletNavigationProvider> </OpenAIContextProvider>
</NavigationContainer> </MobileWalletNavigationProvider>
</Sentry.TouchEventBoundary> </NavigationContainer>
</LockScreenContextProvider> </Sentry.TouchEventBoundary>
</BiometricContextProvider> </LockScreenContextProvider>
</UnitagUpdaterContextProvider> </BiometricContextProvider>
</WalletContextProvider> </UnitagUpdaterContextProvider>
</GestureHandlerRootView> </WalletContextProvider>
</LocalizationContextProvider> </GestureHandlerRootView>
</LocalizationContextProvider>
</DatadogProviderWrapper>
</ErrorBoundary> </ErrorBoundary>
</PersistGate> </PersistGate>
</ApolloProvider> </ApolloProvider>
......
...@@ -269,7 +269,9 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -269,7 +269,9 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>
<AccountList accounts={accountsWithoutActive} isVisible={modalState.isOpen} onPress={onPressAccount} /> <Flex maxHeight={fullScreenContentHeight / 2}>
<AccountList accounts={accountsWithoutActive} isVisible={modalState.isOpen} onPress={onPressAccount} />
</Flex>
<TouchableArea hapticFeedback mt="$spacing16" onPress={onPressAddWallet}> <TouchableArea hapticFeedback mt="$spacing16" onPress={onPressAddWallet}>
<Flex row alignItems="center" gap="$spacing8" ml="$spacing24"> <Flex row alignItems="center" gap="$spacing8" ml="$spacing24">
<PlusCircle /> <PlusCircle />
......
...@@ -363,51 +363,57 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -363,51 +363,57 @@ exports[`AccountSwitcher renders correctly 1`] = `
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
"flexShrink": 1, "maxHeight": NaN,
} }
} }
> >
ExpoLinearGradient <View
<RCTScrollView style={
CellRendererComponent={[Function]}
ListHeaderComponent={<React.Fragment />}
bounces={false}
collapsable={false}
data={[]}
getItem={[Function]}
getItemCount={[Function]}
jestAnimatedStyle={
{ {
"value": {}, "flexDirection": "column",
"flexShrink": 1,
} }
} }
keyExtractor={[Function]}
keyboardShouldPersistTaps="always"
onContentSizeChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
scrollEnabled={false}
scrollEventThrottle={16}
sentry-label="VirtualizedList"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
stickyHeaderIndices={[]}
viewabilityConfigCallbackPairs={[]}
> >
<View> ExpoLinearGradient
<View <RCTScrollView
collapsable={false} collapsable={false}
onLayout={[Function]} data={[]}
/> getItem={[Function]}
</View> getItemCount={[Function]}
</RCTScrollView> handlerTag={1}
ExpoLinearGradient handlerType="NativeViewGestureHandler"
keyExtractor={[Function]}
onContentSizeChange={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
renderScrollComponent={[Function]}
scrollEventThrottle={0.0001}
stickyHeaderIndices={[]}
viewabilityConfigCallbackPairs={[]}
waitFor={
[
{
"current": null,
},
{
"current": null,
},
]
}
>
<View />
</RCTScrollView>
ExpoLinearGradient
</View>
</View> </View>
<View <View
cancelable={true} cancelable={true}
......
...@@ -2,8 +2,8 @@ import { LinearGradient } from 'expo-linear-gradient' ...@@ -2,8 +2,8 @@ import { LinearGradient } from 'expo-linear-gradient'
import { ComponentProps, default as React, useCallback, useMemo } from 'react' import { ComponentProps, default as React, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native' import { StyleSheet } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import { AccountCardItem } from 'src/components/accounts/AccountCardItem' import { AccountCardItem } from 'src/components/accounts/AccountCardItem'
import { VirtualizedList } from 'src/components/layout/VirtualizedList'
import { Flex, Text, useSporeColors } from 'ui/src' import { Flex, Text, useSporeColors } from 'ui/src'
import { opacify, spacing } from 'ui/src/theme' import { opacify, spacing } from 'ui/src/theme'
import { PollingInterval } from 'uniswap/src/constants/misc' import { PollingInterval } from 'uniswap/src/constants/misc'
...@@ -12,9 +12,6 @@ import { isNonPollingRequestInFlight } from 'wallet/src/data/utils' ...@@ -12,9 +12,6 @@ import { isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { useAccountList } from 'wallet/src/features/accounts/hooks' import { useAccountList } from 'wallet/src/features/accounts/hooks'
import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types' import { Account, AccountType } from 'wallet/src/features/wallet/accounts/types'
// Most screens can fit more but this is set conservatively
const MIN_ACCOUNTS_TO_ENABLE_SCROLL = 5
type AccountListProps = Pick<ComponentProps<typeof AccountCardItem>, 'onPress'> & { type AccountListProps = Pick<ComponentProps<typeof AccountCardItem>, 'onPress'> & {
accounts: Account[] accounts: Account[]
isVisible?: boolean isVisible?: boolean
...@@ -26,7 +23,7 @@ type AccountWithPortfolioValue = { ...@@ -26,7 +23,7 @@ type AccountWithPortfolioValue = {
portfolioValue: number | undefined portfolioValue: number | undefined
} }
const ViewOnlyHeader = (): JSX.Element => { const ViewOnlyHeaderContent = (): JSX.Element => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<Flex fill px="$spacing24" py="$spacing8"> <Flex fill px="$spacing24" py="$spacing8">
...@@ -37,6 +34,19 @@ const ViewOnlyHeader = (): JSX.Element => { ...@@ -37,6 +34,19 @@ const ViewOnlyHeader = (): JSX.Element => {
) )
} }
enum AccountListItemType {
SignerHeader,
SignerAccount,
ViewOnlyHeader,
ViewOnlyAccount,
}
type AccountListItem =
| { type: AccountListItemType.SignerHeader }
| { type: AccountListItemType.SignerAccount; account: AccountWithPortfolioValue }
| { type: AccountListItemType.ViewOnlyHeader }
| { type: AccountListItemType.ViewOnlyAccount; account: AccountWithPortfolioValue }
const SignerHeader = (): JSX.Element => { const SignerHeader = (): JSX.Element => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
...@@ -107,6 +117,45 @@ export function AccountList({ accounts, onPress, isVisible }: AccountListProps): ...@@ -107,6 +117,45 @@ export function AccountList({ accounts, onPress, isVisible }: AccountListProps):
[onPress], [onPress],
) )
const accountsToRender = useMemo(() => {
const items: AccountListItem[] = []
if (hasSignerAccounts) {
items.push({ type: AccountListItemType.SignerHeader })
items.push(...signerAccounts.map((account) => ({ type: AccountListItemType.SignerAccount, account })))
}
if (hasViewOnlyAccounts) {
items.push({ type: AccountListItemType.ViewOnlyHeader })
items.push(...viewOnlyAccounts.map((account) => ({ type: AccountListItemType.ViewOnlyAccount, account })))
}
return items
}, [hasSignerAccounts, hasViewOnlyAccounts, signerAccounts, viewOnlyAccounts])
const renderItem = useCallback(
({ item }: { item: AccountListItem }) => {
switch (item.type) {
case AccountListItemType.SignerHeader:
return <SignerHeader />
case AccountListItemType.ViewOnlyHeader:
return <ViewOnlyHeaderContent />
case AccountListItemType.SignerAccount:
case AccountListItemType.ViewOnlyAccount:
return renderAccountCardItem(item.account)
}
},
[renderAccountCardItem],
)
const keyExtractor = useCallback(
(item: AccountListItem, index: number) =>
item.type === AccountListItemType.SignerAccount || item.type === AccountListItemType.ViewOnlyAccount
? item.account.account.address
: item.type.toString() + index,
[],
)
return ( return (
<Flex shrink> <Flex shrink>
{/* TODO(MOB-646): attempt to switch gradients to react-native-svg#LinearGradient and avoid new clear color */} {/* TODO(MOB-646): attempt to switch gradients to react-native-svg#LinearGradient and avoid new clear color */}
...@@ -116,24 +165,7 @@ export function AccountList({ accounts, onPress, isVisible }: AccountListProps): ...@@ -116,24 +165,7 @@ export function AccountList({ accounts, onPress, isVisible }: AccountListProps):
start={{ x: 0, y: 1 }} start={{ x: 0, y: 1 }}
style={ListSheet.topGradient} style={ListSheet.topGradient}
/> />
<VirtualizedList <FlatList data={accountsToRender} keyExtractor={keyExtractor} renderItem={renderItem} />
bounces={false}
scrollEnabled={accountsWithPortfolioValue.length >= MIN_ACCOUNTS_TO_ENABLE_SCROLL}
showsVerticalScrollIndicator={false}
>
{hasSignerAccounts && (
<>
<SignerHeader />
{signerAccounts.map(renderAccountCardItem)}
</>
)}
{hasViewOnlyAccounts && (
<>
<ViewOnlyHeader />
{viewOnlyAccounts.map(renderAccountCardItem)}
</>
)}
</VirtualizedList>
<LinearGradient <LinearGradient
colors={[opacify(0, colors.surface1.val), colors.surface1.val]} colors={[opacify(0, colors.surface1.val), colors.surface1.val]}
end={{ x: 0, y: 1 }} end={{ x: 0, y: 1 }}
......
...@@ -11,34 +11,40 @@ exports[`AccountList renders without error 1`] = ` ...@@ -11,34 +11,40 @@ exports[`AccountList renders without error 1`] = `
> >
ExpoLinearGradient ExpoLinearGradient
<RCTScrollView <RCTScrollView
CellRendererComponent={[Function]}
ListHeaderComponent={
<React.Fragment>
<React.Fragment>
<SignerHeader />
<AccountCardItem
address="0x82D56A352367453f74FC0dC7B071b311da373Fa6"
isPortfolioValueLoading={false}
isViewOnly={false}
onPress={[MockFunction]}
portfolioValue={55}
/>
</React.Fragment>
</React.Fragment>
}
bounces={false}
collapsable={false} collapsable={false}
data={[]} data={
[
{
"type": 0,
},
{
"account": {
"account": {
"address": "0x82D56A352367453f74FC0dC7B071b311da373Fa6",
"backups": [
"cloud",
],
"derivationIndex": 0,
"mnemonicId": "0x82D56A352367453f74FC0dC7B071b311da373Fa6",
"name": "Test Account",
"timeImportedMs": 10,
"type": "signerMnemonic",
},
"isPortfolioValueLoading": false,
"portfolioValue": 55,
},
"type": 1,
},
]
}
getItem={[Function]} getItem={[Function]}
getItemCount={[Function]} getItemCount={[Function]}
jestAnimatedStyle={ handlerTag={1}
{ handlerType="NativeViewGestureHandler"
"value": {},
}
}
keyExtractor={[Function]} keyExtractor={[Function]}
keyboardShouldPersistTaps="always"
onContentSizeChange={[Function]} onContentSizeChange={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onLayout={[Function]} onLayout={[Function]}
onMomentumScrollBegin={[Function]} onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]} onMomentumScrollEnd={[Function]}
...@@ -47,18 +53,26 @@ exports[`AccountList renders without error 1`] = ` ...@@ -47,18 +53,26 @@ exports[`AccountList renders without error 1`] = `
onScrollEndDrag={[Function]} onScrollEndDrag={[Function]}
removeClippedSubviews={false} removeClippedSubviews={false}
renderItem={[Function]} renderItem={[Function]}
scrollEnabled={false} renderScrollComponent={[Function]}
scrollEventThrottle={16} scrollEventThrottle={0.0001}
sentry-label="VirtualizedList"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
stickyHeaderIndices={[]} stickyHeaderIndices={[]}
viewabilityConfigCallbackPairs={[]} viewabilityConfigCallbackPairs={[]}
waitFor={
[
{
"current": null,
},
{
"current": null,
},
]
}
> >
<View> <View>
<View <View
collapsable={false} onFocusCapture={[Function]}
onLayout={[Function]} onLayout={[Function]}
style={null}
> >
<View <View
style={ style={
...@@ -88,6 +102,12 @@ exports[`AccountList renders without error 1`] = ` ...@@ -88,6 +102,12 @@ exports[`AccountList renders without error 1`] = `
Your other wallets Your other wallets
</Text> </Text>
</View> </View>
</View>
<View
onFocusCapture={[Function]}
onLayout={[Function]}
style={null}
>
<View <View
actions={ actions={
[ [
......
...@@ -377,7 +377,7 @@ function TokenDetails({ ...@@ -377,7 +377,7 @@ function TokenDetails({
chainId={currencyChainId} chainId={currencyChainId}
currencyId={_currencyId} currencyId={_currencyId}
onClose={(): void => { onClose={(): void => {
setShowWarningModal(false) setShowBuyNativeTokenModal(false)
}} }}
/> />
)} )}
......
...@@ -21,7 +21,6 @@ export const AVATARS_LIGHT = require('./misc/avatars-light.png') ...@@ -21,7 +21,6 @@ export const AVATARS_LIGHT = require('./misc/avatars-light.png')
export const AVATARS_DARK = require('./misc/avatars-dark.png') export const AVATARS_DARK = require('./misc/avatars-dark.png')
export const APP_SCREENSHOT_LIGHT = require('./misc/app-screenshot-light.png') export const APP_SCREENSHOT_LIGHT = require('./misc/app-screenshot-light.png')
export const APP_SCREENSHOT_DARK = require('./misc/app-screenshot-dark.png') export const APP_SCREENSHOT_DARK = require('./misc/app-screenshot-dark.png')
export const CHROME_LOGO = require('./logos/png/chrome-logo.png')
export const DOT_GRID = require('./misc/dot-grid.png') export const DOT_GRID = require('./misc/dot-grid.png')
export const UNITAGS_BANNER_VERTICAL_LIGHT = require('./graphics/unitags-banner-v-light.png') export const UNITAGS_BANNER_VERTICAL_LIGHT = require('./graphics/unitags-banner-v-light.png')
export const UNITAGS_BANNER_VERTICAL_DARK = require('./graphics/unitags-banner-v-dark.png') export const UNITAGS_BANNER_VERTICAL_DARK = require('./graphics/unitags-banner-v-dark.png')
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { createFont, isWeb } from '@tamagui/core' import { createFont, isWeb } from '@tamagui/core'
import { needsSmallFont } from 'ui/src/utils/needs-small-font' import { needsSmallFont } from 'ui/src/utils/needs-small-font'
import { isInterface } from 'utilities/src/platform'
// TODO(EXT-148): remove this type and use Tamagui's FontTokens // TODO(EXT-148): remove this type and use Tamagui's FontTokens
export type TextVariantTokens = keyof typeof fonts export type TextVariantTokens = keyof typeof fonts
...@@ -48,9 +47,9 @@ const MEDIUM_WEIGHT = '500' ...@@ -48,9 +47,9 @@ const MEDIUM_WEIGHT = '500'
const MEDIUM_WEIGHT_WEB = '535' const MEDIUM_WEIGHT_WEB = '535'
const defaultWeights = { const defaultWeights = {
book: isInterface ? BOOK_WEIGHT_WEB : BOOK_WEIGHT, book: isWeb ? BOOK_WEIGHT_WEB : BOOK_WEIGHT,
true: isInterface ? BOOK_WEIGHT_WEB : BOOK_WEIGHT, true: isWeb ? BOOK_WEIGHT_WEB : BOOK_WEIGHT,
medium: isInterface ? MEDIUM_WEIGHT_WEB : MEDIUM_WEIGHT, medium: isWeb ? MEDIUM_WEIGHT_WEB : MEDIUM_WEIGHT,
} }
export const fonts = { export const fonts = {
......
...@@ -19,7 +19,7 @@ import { BottomSheetModalProps } from 'uniswap/src/components/modals/BottomSheet ...@@ -19,7 +19,7 @@ import { BottomSheetModalProps } from 'uniswap/src/components/modals/BottomSheet
import { HandleBar } from 'uniswap/src/components/modals/HandleBar' import { HandleBar } from 'uniswap/src/components/modals/HandleBar'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { useKeyboardLayout } from 'uniswap/src/utils/useKeyboardLayout' import { useKeyboardLayout } from 'uniswap/src/utils/useKeyboardLayout'
import { isAndroid, isIOS } from 'utilities/src/platform' import { isIOS } from 'utilities/src/platform'
/** /**
* (android only) * (android only)
...@@ -250,8 +250,6 @@ function BottomSheetModalContents({ ...@@ -250,8 +250,6 @@ function BottomSheetModalContents({
{...background} {...background}
{...backdrop} {...backdrop}
ref={modalRef} ref={modalRef}
// Adds vertical pan gesture activate offset to avoid nested scroll gesture handler conflicts on android
activeOffsetY={isAndroid ? 12 : undefined}
animatedPosition={animatedPosition} animatedPosition={animatedPosition}
backgroundStyle={backgroundStyle} backgroundStyle={backgroundStyle}
containerComponent={containerComponent} containerComponent={containerComponent}
......
...@@ -127,7 +127,6 @@ export const ElementName = { ...@@ -127,7 +127,6 @@ export const ElementName = {
EmptyStateReceive: 'empty-state-receive', EmptyStateReceive: 'empty-state-receive',
Enable: 'enable', Enable: 'enable',
EtherscanView: 'etherscan-view', EtherscanView: 'etherscan-view',
ExtensionPopupOpenButton: 'extension-popup-open-button',
FiatOnRampTokenSelector: 'fiat-on-ramp-token-selector', FiatOnRampTokenSelector: 'fiat-on-ramp-token-selector',
FiatOnRampWidgetButton: 'fiat-on-ramp-widget-button', FiatOnRampWidgetButton: 'fiat-on-ramp-widget-button',
FiatOnRampCountryPicker: 'fiat-on-ramp-country-picker', FiatOnRampCountryPicker: 'fiat-on-ramp-country-picker',
......
...@@ -258,9 +258,6 @@ ...@@ -258,9 +258,6 @@
"extension.lock.subtitle": "Enter your password to unlock", "extension.lock.subtitle": "Enter your password to unlock",
"extension.lock.title": "Welcome back", "extension.lock.title": "Welcome back",
"extension.network.notSupported": "Unsupported network", "extension.network.notSupported": "Unsupported network",
"extension.popup.chrome.button": "Open extension",
"extension.popup.chrome.description": "Complete this action by opening the Uniswap extension.",
"extension.popup.chrome.title": "Continue in Uniswap",
"extension.settings.password.enter.title": "Enter your current password", "extension.settings.password.enter.title": "Enter your current password",
"extension.settings.password.error.wrong": "Wrong password", "extension.settings.password.error.wrong": "Wrong password",
"extension.settings.password.placeholder": "Current password", "extension.settings.password.placeholder": "Current password",
......
...@@ -258,9 +258,6 @@ ...@@ -258,9 +258,6 @@
"extension.lock.subtitle": "Ingresa tu contraseña para desbloquear", "extension.lock.subtitle": "Ingresa tu contraseña para desbloquear",
"extension.lock.title": "Bienvenido de nuevo", "extension.lock.title": "Bienvenido de nuevo",
"extension.network.notSupported": "Red no compatible", "extension.network.notSupported": "Red no compatible",
"extension.popup.chrome.button": "Abrir extensión",
"extension.popup.chrome.description": "Completa esta acción abriendo la extensión de Uniswap.",
"extension.popup.chrome.title": "Continua en Uniswap",
"extension.settings.password.enter.title": "Ingresa tu contraseña actual", "extension.settings.password.enter.title": "Ingresa tu contraseña actual",
"extension.settings.password.error.wrong": "Contraseña incorrecta", "extension.settings.password.error.wrong": "Contraseña incorrecta",
"extension.settings.password.placeholder": "Contraseña actual", "extension.settings.password.placeholder": "Contraseña actual",
......
...@@ -258,9 +258,6 @@ ...@@ -258,9 +258,6 @@
"extension.lock.subtitle": "Saisissez votre mot de passe pour déverrouiller", "extension.lock.subtitle": "Saisissez votre mot de passe pour déverrouiller",
"extension.lock.title": "Content de vous revoir", "extension.lock.title": "Content de vous revoir",
"extension.network.notSupported": "Réseau non pris en charge", "extension.network.notSupported": "Réseau non pris en charge",
"extension.popup.chrome.button": "Ouvre l'extension",
"extension.popup.chrome.description": "Complète cette action en ouvrant l'extension Uniswap.",
"extension.popup.chrome.title": "Continue sur Uniswap",
"extension.settings.password.enter.title": "Saisissez votre mot de passe actuel", "extension.settings.password.enter.title": "Saisissez votre mot de passe actuel",
"extension.settings.password.error.wrong": "Mot de passe incorrect", "extension.settings.password.error.wrong": "Mot de passe incorrect",
"extension.settings.password.placeholder": "Mot de passe actuel", "extension.settings.password.placeholder": "Mot de passe actuel",
......
...@@ -6,7 +6,6 @@ export enum HomeTabs { ...@@ -6,7 +6,6 @@ export enum HomeTabs {
export enum ExtensionScreens { export enum ExtensionScreens {
Home = 'home', Home = 'home',
PopupOpenExtension = 'PopupOpenExtension',
UnsupportedBrowserScreen = 'UnsupportedBrowserScreen' UnsupportedBrowserScreen = 'UnsupportedBrowserScreen'
} }
......
...@@ -19,7 +19,7 @@ import { ONBOARDING_LANDING_DARK, ONBOARDING_LANDING_LIGHT, UNISWAP_LOGO } from ...@@ -19,7 +19,7 @@ import { ONBOARDING_LANDING_DARK, ONBOARDING_LANDING_LIGHT, UNISWAP_LOGO } from
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions'
import { imageSizes } from 'ui/src/theme' import { imageSizes } from 'ui/src/theme'
import { isAndroid, isMobile } from 'utilities/src/platform' import { isAndroid } from 'utilities/src/platform'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { useTimeout } from 'utilities/src/time/timing' import { useTimeout } from 'utilities/src/time/timing'
import { import {
...@@ -355,7 +355,7 @@ export const LandingBackground = ({ ...@@ -355,7 +355,7 @@ export const LandingBackground = ({
if ( if (
// Android Platform.Version is always a number // Android Platform.Version is always a number
(isAndroid && typeof Platform.Version === 'number' && Platform.Version < 30) || (isAndroid && typeof Platform.Version === 'number' && Platform.Version < 30) ||
(language !== Language.English && isMobile) language !== Language.English
) { ) {
return <OnboardingStaticImage /> return <OnboardingStaticImage />
} }
......
...@@ -4,7 +4,6 @@ import { call, put, select, takeLatest } from 'typed-redux-saga' ...@@ -4,7 +4,6 @@ import { call, put, select, takeLatest } from 'typed-redux-saga'
import i18n from 'uniswap/src/i18n/i18n' import i18n from 'uniswap/src/i18n/i18n'
import { getDeviceLocales } from 'utilities/src/device/locales' import { getDeviceLocales } from 'utilities/src/device/locales'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { isMobile } from 'utilities/src/platform'
import { import {
Language, Language,
Locale, Locale,
...@@ -39,9 +38,7 @@ function* appLanguageSaga(action: ReturnType<typeof updateLanguage>) { ...@@ -39,9 +38,7 @@ function* appLanguageSaga(action: ReturnType<typeof updateLanguage>) {
logger.warn('language/saga', 'appLanguageSaga', 'Sync of language setting state and i18n instance failed') logger.warn('language/saga', 'appLanguageSaga', 'Sync of language setting state and i18n instance failed')
} }
if (isMobile) { yield* call(restartAppIfRTL, localeToSet)
yield* call(restartAppIfRTL, localeToSet)
}
} }
function getDeviceLanguage(): Language { function getDeviceLanguage(): Language {
......
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