From 38eeb7a8c2a2a164d8833c962a802b212ffec627 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Mon, 22 Aug 2022 11:39:59 -0700 Subject: [PATCH 01/91] Feature/4761 update network picker on wallet screen (#4844) * Fix image styling * Update constant name * Replace wallet navbar with PickerNetwork --- .../PickerNetwork/PickerNetwork.styles.ts | 1 + .../__snapshots__/PickerNetwork.test.tsx.snap | 1 + app/components/UI/Navbar/index.js | 19 +++++- app/components/Views/Wallet/index.tsx | 66 ++++++++++++++++++- app/util/networks/customNetworks.tsx | 9 +++ app/util/networks/index.js | 7 ++ 6 files changed, 98 insertions(+), 5 deletions(-) diff --git a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.styles.ts b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.styles.ts index 6aabe750d24..28bf9a23433 100644 --- a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.styles.ts +++ b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.styles.ts @@ -37,6 +37,7 @@ const styleSheet = (params: { ) as ViewStyle, label: { marginHorizontal: 8, + flexShrink: 1, }, }); }; diff --git a/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap b/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap index f8368888757..c6cc9711628 100644 --- a/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap +++ b/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap @@ -26,6 +26,7 @@ exports[`PickerNetwork should render correctly 1`] = ` { @@ -973,7 +980,15 @@ export function getWalletNavbarOptions( } return { - headerTitle: () => , + headerTitle: () => ( + + + + ), headerLeft: () => ( StyleSheet.create({ @@ -125,6 +129,17 @@ const Wallet = ({ navigation }: any) => { * Current onboarding wizard step */ const wizardStep = useSelector((state: any) => state.wizard.step); + /** + * Current network + */ + const networkProvider = useSelector( + (state: any) => state.engine.backgroundState.NetworkController.provider, + ); + const dispatch = useDispatch(); + /** + * Callback to trigger when pressing the navigation title. + */ + const onTitlePress = () => dispatch(toggleNetworkModal()); const { colors: themeColors } = useTheme(); @@ -168,17 +183,62 @@ const Wallet = ({ navigation }: any) => { [navigation], ); + /** + * Get the current network name. + * + * @returns Current network name. + */ + const getNetworkName = useCallback(() => { + let name = ''; + if (networkProvider.nickname) { + name = networkProvider.nickname; + } else { + const networkType: keyof typeof Networks = networkProvider.type; + name = Networks?.[networkType]?.name || Networks.rpc.name; + } + return name; + }, [networkProvider.nickname, networkProvider.type]); + + /** + * Get image source for either default MetaMask networks or popular networks, which include networks such as Polygon, Binance, Avalanche, etc. + * @returns A network image from a local resource or undefined + */ + const getNetworkImageSource = useCallback((): + | ImageSourcePropType + | undefined => { + const defaultNetwork: any = getDefaultNetworkByChainId( + networkProvider.chainId, + ); + if (defaultNetwork) { + return defaultNetwork.imageSource; + } + const popularNetwork = PopularList.find( + (network) => network.chainId === networkProvider.chainId, + ); + if (popularNetwork) { + return popularNetwork.rpcPrefs.imageSource; + } + }, [networkProvider.chainId]); + useEffect(() => { navigation.setOptions( getWalletNavbarOptions( - 'wallet.title', + getNetworkName(), + getNetworkImageSource(), + onTitlePress, navigation, drawerRef, themeColors, ), ); /* eslint-disable-next-line */ - }, [navigation, themeColors]); + }, [ + navigation, + themeColors, + getNetworkName, + getNetworkImageSource, + onTitlePress, + ]); const onRefresh = useCallback(async () => { requestAnimationFrame(async () => { diff --git a/app/util/networks/customNetworks.tsx b/app/util/networks/customNetworks.tsx index ac853d8b1d7..3fef7cf3879 100644 --- a/app/util/networks/customNetworks.tsx +++ b/app/util/networks/customNetworks.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-require-imports, import/no-commonjs */ const InfuraKey = process.env.MM_INFURA_PROJECT_ID; const infuraProjectId = InfuraKey === 'null' ? '' : InfuraKey; @@ -11,6 +12,7 @@ const PopularList = [ rpcPrefs: { blockExplorerUrl: 'https://snowtrace.io', imageUrl: 'AVAX', + imageSource: require('../../images/avalanche.png'), }, }, { @@ -21,6 +23,7 @@ const PopularList = [ rpcPrefs: { blockExplorerUrl: 'https://explorer.arbitrum.io', imageUrl: 'AETH', + imageSource: require('../../images/arbitrum.png'), }, }, { @@ -32,6 +35,7 @@ const PopularList = [ rpcPrefs: { blockExplorerUrl: 'https://bscscan.com', imageUrl: 'BNB', + imageSource: require('../../images/binance.png'), }, }, { @@ -43,6 +47,7 @@ const PopularList = [ rpcPrefs: { blockExplorerUrl: 'https://ftmscan.com', imageUrl: 'FTM', + imageSource: require('../../images/fantom.png'), }, }, { @@ -54,6 +59,7 @@ const PopularList = [ rpcPrefs: { blockExplorerUrl: 'https://explorer.harmony.one', imageUrl: 'ONE', + imageSource: require('../../images/harmony.png'), }, }, { @@ -64,6 +70,7 @@ const PopularList = [ rpcPrefs: { blockExplorerUrl: 'https://optimistic.etherscan.io', imageUrl: 'OPTIMISM', + imageSource: require('../../images/optimism.png'), }, }, { @@ -74,6 +81,7 @@ const PopularList = [ rpcPrefs: { blockExplorerUrl: 'https://polygonscan.com', imageUrl: 'MATIC', + imageSource: require('../../images/matic.png'), }, }, { @@ -84,6 +92,7 @@ const PopularList = [ rpcPrefs: { blockExplorerUrl: 'https://explorer.palm.io', imageUrl: 'PALM', + imageSource: require('../../images/palm.png'), }, }, ]; diff --git a/app/util/networks/index.js b/app/util/networks/index.js index 2b871c47e7b..b52815f255f 100644 --- a/app/util/networks/index.js +++ b/app/util/networks/index.js @@ -18,6 +18,8 @@ import handleNetworkSwitch from './handleNetworkSwitch'; export { handleNetworkSwitch }; +/* eslint-disable-next-line */ +const ethLogo = require('../../images/eth-logo.png'); /** * List of the supported networks * including name, id, and color @@ -34,6 +36,7 @@ const NetworkList = { hexChainId: '0x1', color: '#3cc29e', networkType: 'mainnet', + imageSource: ethLogo, }, [ROPSTEN]: { name: 'Ropsten Test Network', @@ -43,6 +46,7 @@ const NetworkList = { hexChainId: '0x3', color: '#ff4a8d', networkType: 'ropsten', + imageSource: ethLogo, }, [KOVAN]: { name: 'Kovan Test Network', @@ -52,6 +56,7 @@ const NetworkList = { hexChainId: '0x2a', color: '#7057ff', networkType: 'kovan', + imageSource: ethLogo, }, [RINKEBY]: { name: 'Rinkeby Test Network', @@ -61,6 +66,7 @@ const NetworkList = { hexChainId: '0x4', color: '#f6c343', networkType: 'rinkeby', + imageSource: ethLogo, }, [GOERLI]: { name: 'Goerli Test Network', @@ -70,6 +76,7 @@ const NetworkList = { hexChainId: '0x5', color: '#3099f2', networkType: 'goerli', + imageSource: ethLogo, }, [RPC]: { name: 'Private Network', From 413d91d3114c5ab5ec2c4d08419157759e19b539 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Thu, 25 Aug 2022 12:46:01 -0700 Subject: [PATCH 02/91] Feature/4768 action selector sheet (#4891) * Create AccountSelectionList * Create temp loader component * Create temp sheet actions component * Fix AvatarGroup positioning * Provide reserved overlay spacing for bottom sheet * Fix sheet header typo * Make analytics params optional * Rename AccountSelectorList * Create new account selector * Add AccountSelector to navigation stack * Replace all account selection logic * Improve caching logic for ENS name fetch. Introduce 1 hour cache to optimize for network calls. Fetching from middle out beginning with selected account. * Add lodash debounce to prevent taps in quick succession on bottom sheet * Throw away any debounce innvocation when bottom sheet is unmounted * Introduce loading to account selector. Readd test IDS * Remove old account list * Clean up useAccounts * Update snapshots * Fix logic after pulling from main * Update snapshots --- app/actions/modals/index.js | 6 - .../components-temp/Loader/Loader.styles.ts | 29 ++ .../components-temp/Loader/Loader.tsx | 23 + .../components-temp/Loader/Loader.types.ts | 12 + .../components-temp/Loader/index.ts | 1 + .../SheetActions/SheetActions.styles.ts | 28 ++ .../SheetActions/SheetActions.tsx | 46 ++ .../SheetActions/SheetActions.types.ts | 20 + .../components-temp/SheetActions/index.ts | 1 + .../AvatarGroup/AvatarGroup.constants.ts | 14 - .../Avatars/AvatarGroup/AvatarGroup.styles.ts | 21 +- .../Avatars/AvatarGroup/AvatarGroup.tsx | 15 +- .../Avatars/AvatarGroup/AvatarGroup.types.ts | 12 +- .../__snapshots__/AvatarGroup.test.tsx.snap | 5 +- .../components/Cells/Cell/index.ts | 1 + .../Cell/variants/CellSelect/CellSelect.tsx | 2 + .../__snapshots__/CellSelect.test.tsx.snap | 4 + .../components/Sheet/SheetBottom/README.md | 8 + .../SheetBottom/SheetBottom.constants.ts | 6 +- .../Sheet/SheetBottom/SheetBottom.styles.ts | 5 +- .../Sheet/SheetBottom/SheetBottom.tsx | 54 +- .../Sheet/SheetBottom/SheetBottom.types.ts | 8 +- .../Sheet/SheetHeader/SheetHeader.styles.ts | 2 +- .../Sheet/SheetHeader/SheetHeader.tsx | 2 +- .../__snapshots__/SheetHeader.test.tsx.snap | 2 +- app/components/Nav/App/index.js | 5 + .../UI/AccountList/AccountElement/index.js | 217 -------- .../__snapshots__/index.test.tsx.snap | 7 - app/components/UI/AccountList/index.js | 476 ------------------ app/components/UI/AccountList/index.test.tsx | 30 -- app/components/UI/AccountOverview/index.js | 29 +- .../__snapshots__/index.test.tsx.snap | 2 +- app/components/UI/AccountRightButton/index.js | 38 +- .../AccountSelectorList.styles.ts | 20 + .../AccountSelectorList.tsx | 153 ++++++ .../AccountSelectorList.types.ts | 75 +++ .../UI/AccountSelectorList/hooks/index.ts | 3 + .../hooks/useAccounts/index.ts | 4 + .../hooks/useAccounts/useAccounts.ts | 205 ++++++++ .../hooks/useAccounts/useAccounts.types.ts | 41 ++ .../hooks/useAccounts/useAccountsTest.tsx | 65 +++ .../UI/AccountSelectorList/index.ts | 6 + app/components/UI/DrawerView/index.js | 82 +-- .../components/AccountSelector.tsx | 19 +- .../FiatOrders/components/AccountSelector.js | 40 +- .../AccountSelector.constants.ts | 5 + .../Views/AccountSelector/AccountSelector.tsx | 130 +++++ .../AccountSelector/AccountSelector.types.ts | 40 ++ app/components/Views/AccountSelector/index.ts | 1 + .../Confirm/__snapshots__/index.test.tsx.snap | 10 - .../Views/SendFlow/Confirm/index.js | 63 +-- .../SendTo/__snapshots__/index.test.tsx.snap | 10 - app/components/Views/SendFlow/SendTo/index.js | 66 +-- app/constants/navigation/Routes.ts | 3 + app/reducers/modals/index.js | 6 - app/util/ENSUtils.js | 27 +- app/util/analyticsV2.js | 2 +- package.json | 2 + yarn.lock | 9 +- 59 files changed, 1154 insertions(+), 1064 deletions(-) create mode 100644 app/component-library/components-temp/Loader/Loader.styles.ts create mode 100644 app/component-library/components-temp/Loader/Loader.tsx create mode 100644 app/component-library/components-temp/Loader/Loader.types.ts create mode 100644 app/component-library/components-temp/Loader/index.ts create mode 100644 app/component-library/components-temp/SheetActions/SheetActions.styles.ts create mode 100644 app/component-library/components-temp/SheetActions/SheetActions.tsx create mode 100644 app/component-library/components-temp/SheetActions/SheetActions.types.ts create mode 100644 app/component-library/components-temp/SheetActions/index.ts delete mode 100644 app/components/UI/AccountList/AccountElement/index.js delete mode 100644 app/components/UI/AccountList/__snapshots__/index.test.tsx.snap delete mode 100644 app/components/UI/AccountList/index.js delete mode 100644 app/components/UI/AccountList/index.test.tsx create mode 100644 app/components/UI/AccountSelectorList/AccountSelectorList.styles.ts create mode 100644 app/components/UI/AccountSelectorList/AccountSelectorList.tsx create mode 100644 app/components/UI/AccountSelectorList/AccountSelectorList.types.ts create mode 100644 app/components/UI/AccountSelectorList/hooks/index.ts create mode 100644 app/components/UI/AccountSelectorList/hooks/useAccounts/index.ts create mode 100644 app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.ts create mode 100644 app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.types.ts create mode 100644 app/components/UI/AccountSelectorList/hooks/useAccounts/useAccountsTest.tsx create mode 100644 app/components/UI/AccountSelectorList/index.ts create mode 100644 app/components/Views/AccountSelector/AccountSelector.constants.ts create mode 100644 app/components/Views/AccountSelector/AccountSelector.tsx create mode 100644 app/components/Views/AccountSelector/AccountSelector.types.ts create mode 100644 app/components/Views/AccountSelector/index.ts diff --git a/app/actions/modals/index.js b/app/actions/modals/index.js index 1e2656eb724..b8088bb9f94 100644 --- a/app/actions/modals/index.js +++ b/app/actions/modals/index.js @@ -5,12 +5,6 @@ export function toggleNetworkModal() { }; } -export function toggleAccountsModal() { - return { - type: 'TOGGLE_ACCOUNT_MODAL', - }; -} - export function toggleCollectibleContractModal() { return { type: 'TOGGLE_COLLECTIBLE_CONTRACT_MODAL', diff --git a/app/component-library/components-temp/Loader/Loader.styles.ts b/app/component-library/components-temp/Loader/Loader.styles.ts new file mode 100644 index 00000000000..dc27d6226ef --- /dev/null +++ b/app/component-library/components-temp/Loader/Loader.styles.ts @@ -0,0 +1,29 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +// External dependencies. +import { Theme } from '../../../util/theme/models'; + +/** + * Style sheet function for SheetActions component. + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @param params.vars Inputs that the style sheet depends on. + * @returns StyleSheet object. + */ +const styleSheet = (params: { theme: Theme }) => { + const { theme } = params; + const { colors } = theme; + + return StyleSheet.create({ + base: { + ...StyleSheet.absoluteFillObject, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: colors.background.default, + }, + }); +}; + +export default styleSheet; diff --git a/app/component-library/components-temp/Loader/Loader.tsx b/app/component-library/components-temp/Loader/Loader.tsx new file mode 100644 index 00000000000..4ce1c711cd4 --- /dev/null +++ b/app/component-library/components-temp/Loader/Loader.tsx @@ -0,0 +1,23 @@ +// Third party dependencies. +import React from 'react'; +import { ActivityIndicator, View } from 'react-native'; + +// External dependencies. +import { useStyles } from '../../hooks'; + +// Internal dependencies. +import styleSheet from './Loader.styles'; +import { LoaderProps } from './Loader.types'; + +const Loader = ({ size = 'large' }: LoaderProps) => { + const { styles, theme } = useStyles(styleSheet, {}); + const { colors } = theme; + + return ( + + + + ); +}; + +export default Loader; diff --git a/app/component-library/components-temp/Loader/Loader.types.ts b/app/component-library/components-temp/Loader/Loader.types.ts new file mode 100644 index 00000000000..8cf3fcd2635 --- /dev/null +++ b/app/component-library/components-temp/Loader/Loader.types.ts @@ -0,0 +1,12 @@ +// Third party dependencies. +import { ActivityIndicatorProps } from 'react-native'; + +/** + * Loader props. + */ +export interface LoaderProps { + /** + * Activity indicator size. + */ + size?: ActivityIndicatorProps['size']; +} diff --git a/app/component-library/components-temp/Loader/index.ts b/app/component-library/components-temp/Loader/index.ts new file mode 100644 index 00000000000..348c02a9870 --- /dev/null +++ b/app/component-library/components-temp/Loader/index.ts @@ -0,0 +1 @@ +export { default } from './Loader'; diff --git a/app/component-library/components-temp/SheetActions/SheetActions.styles.ts b/app/component-library/components-temp/SheetActions/SheetActions.styles.ts new file mode 100644 index 00000000000..496792849aa --- /dev/null +++ b/app/component-library/components-temp/SheetActions/SheetActions.styles.ts @@ -0,0 +1,28 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +// External dependencies. +import { Theme } from '../../../util/theme/models'; + +/** + * Style sheet function for SheetActions component. + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @param params.vars Inputs that the style sheet depends on. + * @returns StyleSheet object. + */ +const styleSheet = (params: { theme: Theme }) => { + const { theme } = params; + const { colors } = theme; + + return StyleSheet.create({ + separator: { + height: 1, + backgroundColor: colors.border.muted, + marginHorizontal: 16, + }, + }); +}; + +export default styleSheet; diff --git a/app/component-library/components-temp/SheetActions/SheetActions.tsx b/app/component-library/components-temp/SheetActions/SheetActions.tsx new file mode 100644 index 00000000000..10e5cc7e698 --- /dev/null +++ b/app/component-library/components-temp/SheetActions/SheetActions.tsx @@ -0,0 +1,46 @@ +// Third party dependencies. +import React, { useCallback } from 'react'; +import { View } from 'react-native'; + +// External dependencies. +import { useStyles } from '../../hooks'; +import ButtonTertiary from '../../components/Buttons/ButtonTertiary'; +import { ButtonBaseSize } from '../../components/Buttons/ButtonBase'; +import Loader from '../Loader'; + +// Internal dependencies. +import { SheetActionsProps } from './SheetActions.types'; +import styleSheet from './SheetActions.styles'; + +const SheetActions = ({ actions }: SheetActionsProps) => { + const { styles } = useStyles(styleSheet, {}); + + const renderActions = useCallback( + () => + actions.map(({ label, onPress, testID, isLoading, disabled }, index) => { + const key = `${label}-${index}`; + return ( + + {actions.length > 1 && } + + + {isLoading && } + + + ); + }), + [actions, styles.separator], + ); + + return <>{renderActions()}; +}; + +export default SheetActions; diff --git a/app/component-library/components-temp/SheetActions/SheetActions.types.ts b/app/component-library/components-temp/SheetActions/SheetActions.types.ts new file mode 100644 index 00000000000..967d2eabd85 --- /dev/null +++ b/app/component-library/components-temp/SheetActions/SheetActions.types.ts @@ -0,0 +1,20 @@ +/** + * Sheet action options. + */ +export interface Action { + label: string; + onPress: () => void; + testID?: string; + disabled?: boolean; + isLoading?: boolean; +} + +/** + * SheetActionsProps props. + */ +export interface SheetActionsProps { + /** + * List of actions. + */ + actions: Action[]; +} diff --git a/app/component-library/components-temp/SheetActions/index.ts b/app/component-library/components-temp/SheetActions/index.ts new file mode 100644 index 00000000000..5513e2ffb64 --- /dev/null +++ b/app/component-library/components-temp/SheetActions/index.ts @@ -0,0 +1 @@ +export { default } from './SheetActions'; diff --git a/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.constants.ts b/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.constants.ts index 1edd74fdc77..003df07110d 100644 --- a/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.constants.ts +++ b/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.constants.ts @@ -9,98 +9,84 @@ export const AVAILABLE_TOKEN_LIST: AvatarGroupTokenList = [ imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '0', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '1', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '2', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '3', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '4', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '5', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '6', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '7', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '8', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '9', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '10', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '11', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '12', }, { name: 'Ethereum', imageSource: { uri: 'https://cryptologos.cc/logos/avalanche-avax-logo.png', }, - id: '13', }, ]; diff --git a/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.styles.ts b/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.styles.ts index 073ef31963d..0130b75b218 100644 --- a/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.styles.ts +++ b/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.styles.ts @@ -4,6 +4,9 @@ import { StyleSheet } from 'react-native'; // External dependencies. import { Theme } from '../../../../util/theme/models'; +// Internal dependencies. +import { AvatarGroupStyleSheetVars } from './AvatarGroup.types'; + /** * Style sheet function for AvatarGroup component. * @@ -12,18 +15,25 @@ import { Theme } from '../../../../util/theme/models'; * @param params.vars Inputs that the style sheet depends on. * @returns StyleSheet object. */ -const styleSheet = (params: { theme: Theme; vars: any }) => { +const styleSheet = (params: { + theme: Theme; + vars: AvatarGroupStyleSheetVars; +}) => { const { theme, vars } = params; - const { stackWidth } = vars; - + const { stackWidth, stackHeight } = vars; const borderWidth = 1; + const stackHeightWithBorder = stackHeight + borderWidth * 2; return StyleSheet.create({ - base: { flexDirection: 'row' }, - stack: { + base: { flexDirection: 'row', alignItems: 'center', + height: stackHeightWithBorder, + }, + stack: { + flexDirection: 'row', width: stackWidth + borderWidth * 2, + height: stackHeightWithBorder, }, stackedAvatarWrapper: { position: 'absolute', @@ -37,6 +47,7 @@ const styleSheet = (params: { theme: Theme; vars: any }) => { textStyle: { color: theme.colors.text.alternative, marginLeft: 2, + bottom: 2, }, }); }; diff --git a/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.tsx b/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.tsx index c755fe7422a..2215bc2b07c 100644 --- a/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.tsx +++ b/app/component-library/components/Avatars/AvatarGroup/AvatarGroup.tsx @@ -1,5 +1,5 @@ // Third party dependencies. -import React, { useMemo } from 'react'; +import React, { useCallback } from 'react'; import { View } from 'react-native'; // External dependencies. @@ -26,18 +26,21 @@ const AvatarGroup = ({ tokenList }: AvatarGroupProps) => { const stackWidth = avatarSpacing * (amountOfVisibleAvatars + 1); const shouldRenderOverflowCounter = overflowCounter > 0; - const { styles } = useStyles(styleSheet, { stackWidth }); + const { styles } = useStyles(styleSheet, { + stackWidth, + stackHeight: Number(extraSmallSize), + }); - const renderTokenList = useMemo( + const renderTokenList = useCallback( () => tokenList .slice(0, MAX_STACKED_AVATARS) - .map(({ name, imageSource, id }, index) => { + .map(({ name, imageSource }, index) => { const leftOffset = avatarSpacing * index; return ( { return ( - {renderTokenList} + {renderTokenList()} {shouldRenderOverflowCounter && ( { const { styles } = useStyles(styleSheet, { style }); @@ -30,6 +31,7 @@ const CellSelect = ({ isSelected={isSelected} style={styles.base} testID={CELL_SELECT_TEST_ID} + {...props} > TYPE | REQUIRED | DEFAULT | +| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- | +| number | No | 250 | + ### `children` Content to wrap in sheet. diff --git a/app/component-library/components/Sheet/SheetBottom/SheetBottom.constants.ts b/app/component-library/components/Sheet/SheetBottom/SheetBottom.constants.ts index 40436e3511a..324db6823da 100644 --- a/app/component-library/components/Sheet/SheetBottom/SheetBottom.constants.ts +++ b/app/component-library/components/Sheet/SheetBottom/SheetBottom.constants.ts @@ -17,4 +17,8 @@ export const SWIPE_TRIGGERED_ANIMATION_DURATION = 200; /** * The animation duration used for initial render. */ -export const INITIAL_RENDER_ANIMATION_DURATION = 500; +export const INITIAL_RENDER_ANIMATION_DURATION = 350; +/** + * Minimum spacing reserved for the overlay tappable area. + */ +export const DEFAULT_MIN_OVERLAY_HEIGHT = 250; diff --git a/app/component-library/components/Sheet/SheetBottom/SheetBottom.styles.ts b/app/component-library/components/Sheet/SheetBottom/SheetBottom.styles.ts index 8213f2d00af..8e7480d8e3b 100644 --- a/app/component-library/components/Sheet/SheetBottom/SheetBottom.styles.ts +++ b/app/component-library/components/Sheet/SheetBottom/SheetBottom.styles.ts @@ -21,7 +21,7 @@ const styleSheet = (params: { }) => { const { vars, theme } = params; const { colors } = theme; - const { maxSheetHeight } = vars; + const { maxSheetHeight, screenBottomPadding } = vars; return StyleSheet.create({ base: { ...StyleSheet.absoluteFillObject, @@ -37,6 +37,7 @@ const styleSheet = (params: { borderTopRightRadius: 16, maxHeight: maxSheetHeight, overflow: 'hidden', + paddingBottom: screenBottomPadding, }, fill: { flex: 1, @@ -47,7 +48,7 @@ const styleSheet = (params: { borderRadius: 2, backgroundColor: colors.border.muted, alignSelf: 'center', - marginVertical: 4, + marginTop: 4, }, }); }; diff --git a/app/component-library/components/Sheet/SheetBottom/SheetBottom.tsx b/app/component-library/components/Sheet/SheetBottom/SheetBottom.tsx index 96560b57644..29e8d1612e4 100644 --- a/app/component-library/components/Sheet/SheetBottom/SheetBottom.tsx +++ b/app/component-library/components/Sheet/SheetBottom/SheetBottom.tsx @@ -4,6 +4,7 @@ import { useNavigation } from '@react-navigation/native'; import React, { forwardRef, + useCallback, useEffect, useImperativeHandle, useMemo, @@ -30,6 +31,7 @@ import Animated, { withTiming, } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { debounce } from 'lodash'; // External dependencies. import { useStyles } from '../../../hooks'; @@ -41,6 +43,7 @@ import { TAP_TRIGGERED_ANIMATION_DURATION, SWIPE_TRIGGERED_ANIMATION_DURATION, INITIAL_RENDER_ANIMATION_DURATION, + DEFAULT_MIN_OVERLAY_HEIGHT, } from './SheetBottom.constants'; import styleSheet from './SheetBottom.styles'; import { @@ -50,16 +53,28 @@ import { } from './SheetBottom.types'; const SheetBottom = forwardRef( - ({ children, onDismissed, isInteractable = true, ...props }, ref) => { + ( + { + children, + onDismissed, + isInteractable = true, + reservedMinOverlayHeight = DEFAULT_MIN_OVERLAY_HEIGHT, + ...props + }, + ref, + ) => { const postCallback = useRef(); - const { top: screenTopPadding } = useSafeAreaInsets(); + const { top: screenTopPadding, bottom: screenBottomPadding } = + useSafeAreaInsets(); const { height: screenHeight } = useWindowDimensions(); const { styles } = useStyles(styleSheet, { - maxSheetHeight: screenHeight - screenTopPadding, + maxSheetHeight: + screenHeight - screenTopPadding - reservedMinOverlayHeight, + screenBottomPadding, }); const currentYOffset = useSharedValue(screenHeight); const visibleYOffset = useSharedValue(0); - const sheetHeight = useSharedValue(0); + const sheetHeight = useSharedValue(screenHeight); const overlayOpacity = useDerivedValue(() => interpolate( currentYOffset.value, @@ -70,17 +85,12 @@ const SheetBottom = forwardRef( const navigation = useNavigation(); const isMounted = useRef(false); - useEffect(() => { - // Automatically handles animation when content changes - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - }, [children]); - - const onHidden = () => { + const onHidden = useCallback(() => { // Sheet is automatically unmounted from the navigation stack. navigation.goBack(); onDismissed?.(); postCallback.current?.(); - }; + }, [navigation, onDismissed]); const gestureHandler = useAnimatedGestureHandler< PanGestureHandlerGestureEvent, @@ -140,13 +150,27 @@ const SheetBottom = forwardRef( }); }; - const hide = () => { + const hide = useCallback(() => { currentYOffset.value = withTiming( sheetHeight.value, { duration: TAP_TRIGGERED_ANIMATION_DURATION }, () => runOnJS(onHidden)(), ); - }; + // Ref values do not affect deps. + /* eslint-disable-next-line */ + }, [onHidden]); + + const debouncedHide = useMemo( + // Prevent hide from being called multiple times. Potentially caused by taps in quick succession. + () => debounce(hide, 2000, { leading: true }), + [hide], + ); + + useEffect(() => { + // Automatically handles animation when content changes + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + return () => debouncedHide.cancel(); + }, [children, debouncedHide]); const updateSheetHeight = (e: LayoutChangeEvent) => { const { height } = e.nativeEvent.layout; @@ -160,7 +184,7 @@ const SheetBottom = forwardRef( useImperativeHandle(ref, () => ({ hide: (callback) => { postCallback.current = callback; - hide(); + debouncedHide(); }, })); @@ -199,7 +223,7 @@ const SheetBottom = forwardRef( void; /** - * Boolean that indicates if sheet is swippable. This affects whether or not tapping on the overlay will dismiss the sheet as well. + * Optional boolean that indicates if sheet is swippable. This affects whether or not tapping on the overlay will dismiss the sheet as well. * @default true */ isInteractable?: boolean; + /** + * Optional number for the minimum spacing reserved for the overlay tappable area. + * @default 250 + */ + reservedMinOverlayHeight?: number; } export type SheetBottomPostCallback = () => void; @@ -28,4 +33,5 @@ export interface SheetBottomRef { */ export interface SheetBottomStyleSheetVars { maxSheetHeight: number; + screenBottomPadding: number; } diff --git a/app/component-library/components/Sheet/SheetHeader/SheetHeader.styles.ts b/app/component-library/components/Sheet/SheetHeader/SheetHeader.styles.ts index 0adfc7add07..0b66f0eb888 100644 --- a/app/component-library/components/Sheet/SheetHeader/SheetHeader.styles.ts +++ b/app/component-library/components/Sheet/SheetHeader/SheetHeader.styles.ts @@ -20,7 +20,7 @@ const styleSheet = (params: { theme: Theme }) => { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', - paddingHorizontal: 16, + margin: 16, backgroundColor: colors.background.default, height: 32, }, diff --git a/app/component-library/components/Sheet/SheetHeader/SheetHeader.tsx b/app/component-library/components/Sheet/SheetHeader/SheetHeader.tsx index dd10ec57551..9456ad1f41f 100644 --- a/app/component-library/components/Sheet/SheetHeader/SheetHeader.tsx +++ b/app/component-library/components/Sheet/SheetHeader/SheetHeader.tsx @@ -35,7 +35,7 @@ const SheetHeader = ({ testID={SHEET_HEADER_BACK_BUTTON_ID} variant={ButtonIconVariant.Secondary} onPress={onBack} - icon={IconName.ArrowLeftOutline} + iconName={IconName.ArrowLeftOutline} /> )} diff --git a/app/component-library/components/Sheet/SheetHeader/__snapshots__/SheetHeader.test.tsx.snap b/app/component-library/components/Sheet/SheetHeader/__snapshots__/SheetHeader.test.tsx.snap index b8f4fd7c8a8..72cb9446be3 100644 --- a/app/component-library/components/Sheet/SheetHeader/__snapshots__/SheetHeader.test.tsx.snap +++ b/app/component-library/components/Sheet/SheetHeader/__snapshots__/SheetHeader.test.tsx.snap @@ -9,7 +9,7 @@ exports[`SheetHeader should render correctly 1`] = ` "flexDirection": "row", "height": 32, "justifyContent": "space-between", - "paddingHorizontal": 16, + "margin": 16, } } > diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index 6dd47dfde1d..4b638861481 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -54,6 +54,7 @@ import ModalConfirmation from '../../../component-library/components/Modals/Moda import Toast, { ToastContext, } from '../../../component-library/components/Toast'; +import AccountSelector from '../../../components/Views/AccountSelector'; import { TurnOffRememberMeModal } from '../../../components/UI/TurnOffRememberMeModal'; const Stack = createStackNavigator(); @@ -356,6 +357,10 @@ const App = ({ userLoggedIn }) => { component={ModalConfirmation} /> + - StyleSheet.create({ - account: { - borderBottomWidth: StyleSheet.hairlineWidth, - borderColor: colors.border.muted, - flexDirection: 'row', - paddingHorizontal: 20, - paddingVertical: 20, - height: 80, - }, - disabledAccount: { - opacity: 0.5, - }, - accountInfo: { - marginLeft: 15, - marginRight: 0, - flex: 1, - flexDirection: 'row', - }, - accountLabel: { - fontSize: 18, - color: colors.text.default, - ...fontStyles.normal, - }, - accountBalanceWrapper: { - display: 'flex', - flexDirection: 'row', - }, - accountBalance: { - paddingTop: 5, - fontSize: 12, - color: colors.text.alternative, - ...fontStyles.normal, - }, - accountBalanceError: { - color: colors.error.default, - marginLeft: 4, - }, - importedView: { - flex: 0.5, - alignItems: 'flex-start', - marginTop: 2, - }, - accountMain: { - flex: 1, - flexDirection: 'column', - }, - selectedWrapper: { - flex: 0.2, - alignItems: 'flex-end', - }, - importedText: { - color: colors.text.alternative, - fontSize: 10, - ...fontStyles.bold, - }, - importedWrapper: { - paddingHorizontal: 10, - paddingVertical: 3, - borderRadius: 10, - borderWidth: 1, - borderColor: colors.border.default, - }, - }); - -/** - * View that renders specific account element in AccountList - */ -class AccountElement extends PureComponent { - static propTypes = { - /** - * Callback to be called onPress - */ - onPress: PropTypes.func.isRequired, - /** - * Callback to be called onLongPress - */ - onLongPress: PropTypes.func.isRequired, - /** - * Current ticker - */ - ticker: PropTypes.string, - /** - * Whether the account element should be disabled (opaque and not clickable) - */ - disabled: PropTypes.bool, - item: PropTypes.object, - /** - * Updated balance using stored in state - */ - updatedBalanceFromStore: PropTypes.string, - }; - - onPress = () => { - const { onPress } = this.props; - const { address } = this.props.item; - onPress && onPress(address); - }; - - onLongPress = () => { - const { onLongPress } = this.props; - const { address, isImported, index } = this.props.item; - onLongPress && onLongPress(address, isImported, index); - }; - - render() { - const { disabled, updatedBalanceFromStore, ticker } = this.props; - const { - address, - name, - ens, - isSelected, - isImported, - balanceError, - isQRHardware, - } = this.props.item; - const colors = this.context.colors || mockTheme.colors; - const styles = createStyles(colors); - - const selected = isSelected ? ( - - ) : null; - const tag = - isImported || isQRHardware ? ( - - - - {strings( - isImported ? 'accounts.imported' : 'transaction.hardware', - )} - - - - ) : null; - - return ( - true}> - - - - - - {isDefaultAccountName(name) && ens ? ens : name} - - - - {renderFromWei(updatedBalanceFromStore)} {getTicker(ticker)} - - {!!balanceError && ( - - {balanceError} - - )} - - - {!!tag && tag} - {selected} - - - - ); - } -} - -const mapStateToProps = ( - { - engine: { - backgroundState: { PreferencesController, AccountTrackerController }, - }, - }, - { item: { balance, address } }, -) => { - const { selectedAddress } = PreferencesController; - const { accounts } = AccountTrackerController; - const selectedAccount = accounts[selectedAddress]; - const selectedAccountHasBalance = - selectedAccount && - Object.prototype.hasOwnProperty.call(selectedAccount, BALANCE_KEY); - const updatedBalanceFromStore = - balance === EMPTY && - selectedAddress === address && - selectedAccount && - selectedAccountHasBalance - ? selectedAccount[BALANCE_KEY] - : balance; - return { - updatedBalanceFromStore, - }; -}; - -AccountElement.contextType = ThemeContext; - -export default connect(mapStateToProps)(AccountElement); diff --git a/app/components/UI/AccountList/__snapshots__/index.test.tsx.snap b/app/components/UI/AccountList/__snapshots__/index.test.tsx.snap deleted file mode 100644 index ccf4a0e25fe..00000000000 --- a/app/components/UI/AccountList/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Accounts should render correctly 1`] = ` - -`; diff --git a/app/components/UI/AccountList/index.js b/app/components/UI/AccountList/index.js deleted file mode 100644 index aebeb325b52..00000000000 --- a/app/components/UI/AccountList/index.js +++ /dev/null @@ -1,476 +0,0 @@ -import React, { PureComponent } from 'react'; -import { KeyringTypes } from '@metamask/controllers'; -import Engine from '../../../core/Engine'; -import PropTypes from 'prop-types'; -import { - Alert, - ActivityIndicator, - InteractionManager, - FlatList, - TouchableOpacity, - StyleSheet, - Text, - View, - SafeAreaView, -} from 'react-native'; -import { fontStyles } from '../../../styles/common'; -import Device from '../../../util/device'; -import { strings } from '../../../../locales/i18n'; -import { toChecksumAddress } from 'ethereumjs-util'; -import Logger from '../../../util/Logger'; -import Analytics from '../../../core/Analytics/Analytics'; -import AnalyticsV2 from '../../../util/analyticsV2'; -import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; -import { doENSReverseLookup } from '../../../util/ENSUtils'; -import AccountElement from './AccountElement'; -import { connect } from 'react-redux'; -import { ThemeContext, mockTheme } from '../../../util/theme'; - -const createStyles = (colors) => - StyleSheet.create({ - wrapper: { - backgroundColor: colors.background.default, - borderTopLeftRadius: 10, - borderTopRightRadius: 10, - minHeight: 450, - }, - titleWrapper: { - width: '100%', - height: 33, - alignItems: 'center', - justifyContent: 'center', - borderBottomWidth: StyleSheet.hairlineWidth, - borderColor: colors.border.muted, - }, - dragger: { - width: 48, - height: 5, - borderRadius: 4, - backgroundColor: colors.border.default, - opacity: Device.isAndroid() ? 0.6 : 0.5, - }, - accountsWrapper: { - flex: 1, - }, - footer: { - height: Device.isIphoneX() ? 200 : 170, - paddingBottom: Device.isIphoneX() ? 30 : 0, - justifyContent: 'center', - flexDirection: 'column', - alignItems: 'center', - }, - btnText: { - fontSize: 14, - color: colors.primary.default, - ...fontStyles.normal, - }, - footerButton: { - width: '100%', - height: 55, - alignItems: 'center', - justifyContent: 'center', - borderTopWidth: StyleSheet.hairlineWidth, - borderColor: colors.border.muted, - }, - }); - -/** - * View that contains the list of all the available accounts - */ -class AccountList extends PureComponent { - static propTypes = { - /** - * Map of accounts to information objects including balances - */ - accounts: PropTypes.object, - /** - * An object containing each identity in the format address => account - */ - identities: PropTypes.object, - /** - * A string representing the selected address => account - */ - selectedAddress: PropTypes.string, - /** - * An object containing all the keyrings - */ - keyrings: PropTypes.array, - /** - * function to be called when switching accounts - */ - onAccountChange: PropTypes.func, - /** - * function to be called when importing an account - */ - onImportAccount: PropTypes.func, - /** - * function to be called when connect to a QR hardware - */ - onConnectHardware: PropTypes.func, - /** - * Current provider ticker - */ - ticker: PropTypes.string, - /** - * Whether it will show options to create or import accounts - */ - enableAccountsAddition: PropTypes.bool, - /** - * function to generate an error string based on a passed balance - */ - getBalanceError: PropTypes.func, - /** - * Indicates whether third party API mode is enabled - */ - thirdPartyApiMode: PropTypes.bool, - /** - * ID of the current network - */ - network: PropTypes.string, - }; - - state = { - loading: false, - orderedAccounts: {}, - accountsENS: {}, - }; - - flatList = React.createRef(); - lastPosition = 0; - updating = false; - - componentDidMount() { - this.mounted = true; - const orderedAccounts = this.getAccounts(); - InteractionManager.runAfterInteractions(() => { - this.assignENSToAccounts(orderedAccounts); - if (orderedAccounts.length > 4) { - const selectedAccountIndex = - orderedAccounts.findIndex((account) => account.isSelected) || 0; - this.scrollToCurrentAccount(selectedAccountIndex); - } - }); - this.mounted && this.setState({ orderedAccounts }); - } - - componentWillUnmount = () => { - this.mounted = false; - }; - - scrollToCurrentAccount(selectedAccountIndex) { - // eslint-disable-next-line no-unused-expressions - this.flatList?.current?.scrollToIndex({ - index: selectedAccountIndex, - animated: true, - }); - } - - onAccountChange = async (newAddress) => { - const { PreferencesController } = Engine.context; - const { accounts } = this.props; - - requestAnimationFrame(async () => { - try { - // If not enabled is used from address book so we don't change accounts - if (!this.props.enableAccountsAddition) { - this.props.onAccountChange(newAddress); - const orderedAccounts = this.getAccounts(); - this.mounted && this.setState({ orderedAccounts }); - return; - } - - PreferencesController.setSelectedAddress(newAddress); - - this.props.onAccountChange(); - - this.props.thirdPartyApiMode && - InteractionManager.runAfterInteractions(async () => { - setTimeout(() => { - Engine.refreshTransactionHistory(); - }, 1000); - }); - } catch (e) { - Logger.error(e, 'error while trying change the selected account'); // eslint-disable-line - } - InteractionManager.runAfterInteractions(() => { - setTimeout(() => { - // Track Event: "Switched Account" - AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.SWITCHED_ACCOUNT, - { - number_of_accounts: Object.keys(accounts ?? {}).length, - }, - ); - }, 1000); - }); - const orderedAccounts = this.getAccounts(); - this.mounted && this.setState({ orderedAccounts }); - }); - }; - - importAccount = () => { - this.props.onImportAccount(); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT); - }); - }; - - connectHardware = () => { - this.props.onConnectHardware(); - AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, - ); - }; - - addAccount = async () => { - if (this.state.loading) return; - this.mounted && this.setState({ loading: true }); - const { KeyringController } = Engine.context; - requestAnimationFrame(async () => { - try { - await KeyringController.addNewAccount(); - const { PreferencesController } = Engine.context; - const newIndex = Object.keys(this.props.identities).length - 1; - PreferencesController.setSelectedAddress( - Object.keys(this.props.identities)[newIndex], - ); - setTimeout(() => { - this.flatList && - this.flatList.current && - this.flatList.current.scrollToEnd(); - this.mounted && this.setState({ loading: false }); - }, 500); - const orderedAccounts = this.getAccounts(); - this.mounted && this.setState({ orderedAccounts }); - } catch (e) { - // Restore to the previous index in case anything goes wrong - Logger.error(e, 'error while trying to add a new account'); // eslint-disable-line - this.mounted && this.setState({ loading: false }); - } - }); - InteractionManager.runAfterInteractions(() => { - Analytics.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); - }); - }; - - isImported(allKeyrings, address) { - let ret = false; - for (const keyring of allKeyrings) { - if (keyring.accounts.includes(address)) { - ret = keyring.type === KeyringTypes.simple; - break; - } - } - - return ret; - } - - isQRHardware(allKeyrings, address) { - let ret = false; - for (const keyring of allKeyrings) { - if (keyring.accounts.includes(address)) { - ret = keyring.type === KeyringTypes.qr; - break; - } - } - - return ret; - } - - onLongPress = (address, imported, index) => { - if (!imported) return; - Alert.alert( - strings('accounts.remove_account_title'), - strings('accounts.remove_account_message'), - [ - { - text: strings('accounts.no'), - onPress: () => false, - style: 'cancel', - }, - { - text: strings('accounts.yes_remove_it'), - onPress: async () => { - const { PreferencesController } = Engine.context; - const { selectedAddress } = this.props; - const isRemovingCurrentAddress = selectedAddress === address; - const fallbackAccountIndex = index - 1; - const fallbackAccountAddress = - this.state.orderedAccounts[fallbackAccountIndex].address; - - // TODO - Refactor logic. onAccountChange is only used for refreshing latest orderedAccounts after account removal. Duplicate call for PreferencesController.setSelectedAddress exists. - // Set fallback address before removing account if removing current account - isRemovingCurrentAddress && - PreferencesController.setSelectedAddress(fallbackAccountAddress); - await Engine.context.KeyringController.removeAccount(address); - // Default to the previous account in the list if removing current account - this.onAccountChange( - isRemovingCurrentAddress - ? fallbackAccountAddress - : selectedAddress, - ); - }, - }, - ], - { cancelable: false }, - ); - }; - - renderItem = ({ item }) => { - const { ticker } = this.props; - const { accountsENS } = this.state; - return ( - - ); - }; - - getAccounts() { - const { accounts, identities, selectedAddress, keyrings, getBalanceError } = - this.props; - // This is a temporary fix until we can read the state from @metamask/controllers - const allKeyrings = - keyrings && keyrings.length - ? keyrings - : Engine.context.KeyringController.state.keyrings; - - const accountsOrdered = allKeyrings.reduce( - (list, keyring) => list.concat(keyring.accounts), - [], - ); - return accountsOrdered - .filter((address) => !!identities[toChecksumAddress(address)]) - .map((addr, index) => { - const checksummedAddress = toChecksumAddress(addr); - const identity = identities[checksummedAddress]; - const { name, address } = identity; - const identityAddressChecksummed = toChecksumAddress(address); - const isSelected = identityAddressChecksummed === selectedAddress; - const isImported = this.isImported( - allKeyrings, - identityAddressChecksummed, - ); - const isQRHardware = this.isQRHardware( - allKeyrings, - identityAddressChecksummed, - ); - let balance = 0x0; - if (accounts[identityAddressChecksummed]) { - balance = accounts[identityAddressChecksummed].balance; - } - - const balanceError = getBalanceError ? getBalanceError(balance) : null; - return { - index, - name, - address: identityAddressChecksummed, - balance, - isSelected, - isImported, - isQRHardware, - balanceError, - }; - }); - } - - assignENSToAccounts = (orderedAccounts) => { - const { network } = this.props; - orderedAccounts.forEach(async (account) => { - try { - const ens = await doENSReverseLookup(account.address, network); - this.setState((state) => ({ - accountsENS: { - ...state.accountsENS, - [account.address]: ens, - }, - })); - } catch { - // Error - } - }); - }; - - keyExtractor = (item) => item.address; - - render() { - const { orderedAccounts } = this.state; - const { enableAccountsAddition } = this.props; - const colors = this.context.colors || mockTheme.colors; - const styles = createStyles(colors); - - return ( - - - - - ({ - length: 80, - offset: 80 * index, - index, - })} // eslint-disable-line - /> - {enableAccountsAddition && ( - - - {this.state.loading ? ( - - ) : ( - - {strings('accounts.create_new_account')} - - )} - - - - {strings('accounts.import_account')} - - - - - {strings('accounts.connect_hardware')} - - - - )} - - ); - } -} - -AccountList.contextType = ThemeContext; - -const mapStateToProps = (state) => ({ - accounts: state.engine.backgroundState.AccountTrackerController.accounts, - thirdPartyApiMode: state.privacy.thirdPartyApiMode, - keyrings: state.engine.backgroundState.KeyringController.keyrings, - network: state.engine.backgroundState.NetworkController.network, -}); - -export default connect(mapStateToProps)(AccountList); diff --git a/app/components/UI/AccountList/index.test.tsx b/app/components/UI/AccountList/index.test.tsx deleted file mode 100644 index 112a3a07b49..00000000000 --- a/app/components/UI/AccountList/index.test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import AccountList from './'; -import { Provider } from 'react-redux'; -import configureMockStore from 'redux-mock-store'; - -const mockStore = configureMockStore(); -const address = '0xe7E125654064EEa56229f273dA586F10DF96B0a1'; -const store = mockStore({ - engine: { - backgroundState: { - AccountTrackerController: { - accounts: { - [address]: { name: 'account 1', address, balance: 0 }, - }, - }, - }, - }, -}); - -describe('Accounts', () => { - it('should render correctly', () => { - const wrapper = shallow( - - - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js index 7775cb1a85a..5ba46dbb9ee 100644 --- a/app/components/UI/AccountOverview/index.js +++ b/app/components/UI/AccountOverview/index.js @@ -20,10 +20,7 @@ import { strings } from '../../../../locales/i18n'; import { swapsLivenessSelector } from '../../../reducers/swaps'; import { showAlert } from '../../../actions/alert'; import { protectWalletModalVisible } from '../../../actions/user'; -import { - toggleAccountsModal, - toggleReceiveModal, -} from '../../../actions/modals'; +import { toggleReceiveModal } from '../../../actions/modals'; import { newAssetTransaction } from '../../../actions/transaction'; import Device from '../../../util/device'; @@ -164,10 +161,6 @@ class AccountOverview extends PureComponent { /* Triggers global alert */ showAlert: PropTypes.func, - /** - * Action that toggles the accounts modal - */ - toggleAccountsModal: PropTypes.func, /** * whether component is being rendered from onboarding wizard */ @@ -222,17 +215,12 @@ class AccountOverview extends PureComponent { scrollViewContainer = React.createRef(); mainView = React.createRef(); - animatingAccountsModal = false; - - toggleAccountsModal = () => { - const { onboardingWizard } = this.props; - if (!onboardingWizard && !this.animatingAccountsModal) { - this.animatingAccountsModal = true; - this.props.toggleAccountsModal(); - setTimeout(() => { - this.animatingAccountsModal = false; - }, 500); - } + openAccountSelector = () => { + const { onboardingWizard, navigation } = this.props; + !onboardingWizard && + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_SELECTOR, + }); }; isAccountLabelDefined = (accountLabel) => @@ -395,7 +383,7 @@ class AccountOverview extends PureComponent { ({ const mapDispatchToProps = (dispatch) => ({ showAlert: (config) => dispatch(showAlert(config)), - toggleAccountsModal: () => dispatch(toggleAccountsModal()), protectWalletModalVisible: () => dispatch(protectWalletModalVisible()), newAssetTransaction: (selectedAsset) => dispatch(newAssetTransaction(selectedAsset)), diff --git a/app/components/UI/AccountRightButton/__snapshots__/index.test.tsx.snap b/app/components/UI/AccountRightButton/__snapshots__/index.test.tsx.snap index c654a207d6d..4d9b41a0236 100644 --- a/app/components/UI/AccountRightButton/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/AccountRightButton/__snapshots__/index.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AccountRightButton should render correctly 1`] = ``; +exports[`AccountRightButton should render correctly 1`] = ``; diff --git a/app/components/UI/AccountRightButton/index.js b/app/components/UI/AccountRightButton/index.js index 17764bff130..8b0927478a0 100644 --- a/app/components/UI/AccountRightButton/index.js +++ b/app/components/UI/AccountRightButton/index.js @@ -3,9 +3,10 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { TouchableOpacity, StyleSheet } from 'react-native'; import Identicon from '../Identicon'; -import { toggleAccountsModal } from '../../../actions/modals'; import Device from '../../../util/device'; import AnalyticsV2 from '../../../util/analyticsV2'; +import { withNavigation } from '@react-navigation/compat'; +import Routes from '../../../constants/navigation/Routes'; const styles = StyleSheet.create({ leftButton: { @@ -28,27 +29,21 @@ class AccountRightButton extends PureComponent { * Selected address as string */ address: PropTypes.string, - /** - * Action that toggles the account modal - */ - toggleAccountsModal: PropTypes.func, /** * List of accounts from the AccountTrackerController */ accounts: PropTypes.object, + /** + * Navigation object. + */ + navigation: PropTypes.object, }; - animating = false; - - toggleAccountsModal = () => { - const { accounts } = this.props; - if (!this.animating) { - this.animating = true; - this.props.toggleAccountsModal(); - setTimeout(() => { - this.animating = false; - }, 500); - } + openAccountSelector = () => { + const { accounts, navigation } = this.props; + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_SELECTOR, + }); // Track Event: "Opened Acount Switcher" AnalyticsV2.trackEvent( AnalyticsV2.ANALYTICS_EVENTS.BROWSER_OPEN_ACCOUNT_SWITCH, @@ -63,7 +58,7 @@ class AccountRightButton extends PureComponent { return ( @@ -77,8 +72,7 @@ const mapStateToProps = (state) => ({ accounts: state.engine.backgroundState.AccountTrackerController.accounts, }); -const mapDispatchToProps = (dispatch) => ({ - toggleAccountsModal: () => dispatch(toggleAccountsModal()), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(AccountRightButton); +export default connect( + mapStateToProps, + null, +)(withNavigation(AccountRightButton)); diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.styles.ts b/app/components/UI/AccountSelectorList/AccountSelectorList.styles.ts new file mode 100644 index 00000000000..90c0ffedf6d --- /dev/null +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.styles.ts @@ -0,0 +1,20 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +/** + * Style sheet function for AvatarIcon component. + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @param params.vars Inputs that the style sheet depends on. + * @returns StyleSheet object. + */ +const styleSheet = () => + StyleSheet.create({ + balancesContainer: { + alignItems: 'flex-end', + }, + balanceLabel: { textAlign: 'right' }, + }); + +export default styleSheet; diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx new file mode 100644 index 00000000000..892092f3fa4 --- /dev/null +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -0,0 +1,153 @@ +// Third party dependencies. +import React, { useCallback, useEffect, useRef } from 'react'; +import { ListRenderItem, View } from 'react-native'; +import { FlatList } from 'react-native-gesture-handler'; +import { useSelector } from 'react-redux'; +import { KeyringTypes } from '@metamask/controllers'; + +// External dependencies. +import Cell, { + CellVariants, +} from '../../../component-library/components/Cells/Cell'; +import { useStyles } from '../../../component-library/hooks'; +import Text from '../../../component-library/components/Text'; +import AvatarGroup from '../../../component-library/components/Avatars/AvatarGroup'; +import UntypedEngine from '../../../core/Engine'; +import { formatAddress } from '../../../util/address'; +import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; +import { isDefaultAccountName } from '../../../util/ENSUtils'; +import { strings } from '../../../../locales/i18n'; +import { AvatarVariants } from '../../../component-library/components/Avatars/Avatar.types'; + +// Internal dependencies. +import { + Account, + AccountSelectorListProps, + Assets, +} from './AccountSelectorList.types'; +import styleSheet from './AccountSelectorList.styles'; +import { useAccounts } from './hooks'; + +const AccountSelectorList = ({ + onSelectAccount, + checkBalanceError, + isLoading = false, + ...props +}: AccountSelectorListProps) => { + const Engine = UntypedEngine as any; + const accountListRef = useRef(null); + const { styles } = useStyles(styleSheet, {}); + const accountAvatarType = useSelector((state: any) => + state.settings.useBlockieIcon + ? AvatarAccountType.Blockies + : AvatarAccountType.JazzIcon, + ); + const { accounts, ensByAccountAddress } = useAccounts({ + checkBalanceError, + isLoading, + }); + + useEffect(() => { + if (!accounts.length) return; + const account = accounts.find(({ isSelected }) => isSelected); + if (account) { + // Wrap in timeout to provide more time for the list to render. + setTimeout(() => { + accountListRef?.current?.scrollToOffset({ + offset: account.yOffset, + animated: false, + }); + }, 0); + } + // eslint-disable-next-line + }, [accounts.length]); + + const onPress = useCallback( + (address: string) => { + const { PreferencesController } = Engine.context; + PreferencesController.setSelectedAddress(address); + onSelectAccount?.(address); + }, + /* eslint-disable-next-line */ + [onSelectAccount], + ); + + const getKeyExtractor = ({ address }: Account) => address; + + const getTagLabel = (type: KeyringTypes) => { + let label = ''; + switch (type) { + case KeyringTypes.qr: + label = strings('transaction.hardware'); + break; + case KeyringTypes.simple: + label = strings('accounts.imported'); + break; + } + return label; + }; + + const renderAccountBalances = useCallback( + ({ fiatBalance, tokens }: Assets) => ( + + {fiatBalance} + {tokens && } + + ), + [styles.balancesContainer, styles.balanceLabel], + ); + + const renderAccountItem: ListRenderItem = useCallback( + ({ item: { name, address, assets, type, isSelected, balanceError } }) => { + const shortAddress = formatAddress(address, 'short'); + const tagLabel = getTagLabel(type); + const ensName = ensByAccountAddress[address]; + const accountName = + isDefaultAccountName(name) && ensName ? ensName : name; + const isDisabled = !!balanceError || isLoading; + + return ( + onPress(address)} + avatarProps={{ + variant: AvatarVariants.Account, + type: accountAvatarType, + accountAddress: address, + }} + tagLabel={tagLabel} + disabled={isDisabled} + /* eslint-disable-next-line */ + style={{ opacity: isDisabled ? 0.5 : 1 }} + > + {assets && renderAccountBalances(assets)} + + ); + }, + [ + accountAvatarType, + onPress, + renderAccountBalances, + ensByAccountAddress, + isLoading, + ], + ); + + return ( + + ); +}; + +export default AccountSelectorList; diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts new file mode 100644 index 00000000000..7d2e985d5cc --- /dev/null +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts @@ -0,0 +1,75 @@ +// Third party dependencies. +import { KeyringTypes } from '@metamask/controllers'; +import { FlatListProps } from 'react-native'; + +// External dependencies. +import { AvatarGroupToken } from '../../../component-library/components/Avatars/AvatarGroup/AvatarGroup.types'; + +/** + * Asset information associated with the account, which includes both the fiat balance and owned tokens. + */ +export interface Assets { + /** + * Fiat balance in string format. + */ + fiatBalance: string; + /** + * Tokens owned by this account. + */ + tokens?: AvatarGroupToken[]; +} + +/** + * Account information. + */ +export interface Account { + /** + * Account name. + */ + name: string; + /** + * Account address. + */ + address: string; + /** + * Asset information associated with the account, which includes both the fiat balance and owned tokens. + */ + assets?: Assets; + /** + * Account type. + */ + type: KeyringTypes; + /** + * Y offset of the item. Used for scrolling purposes. + */ + yOffset: number; + /** + * Boolean that indicates if the account is selected. + */ + isSelected: boolean; + /** + * Optional error that indicates if the account has enough funds. Non-empty string will render the account item non-selectable. + */ + balanceError?: string; +} + +/** + * AccountSelectorList props. + */ +export interface AccountSelectorListProps + extends Partial> { + /** + * Optional callback to trigger when account is selected. + */ + onSelectAccount?: (address: string) => void; + /** + * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. + * @param balance - The ticker balance of an account in wei and hex string format. + */ + checkBalanceError?: (balance: string) => string; + /** + * Optional boolean that indicates if accounts are being processed in the background. The accounts will be unselectable as long as this is true. + * @default false + */ + isLoading?: boolean; +} diff --git a/app/components/UI/AccountSelectorList/hooks/index.ts b/app/components/UI/AccountSelectorList/hooks/index.ts new file mode 100644 index 00000000000..632be0949b4 --- /dev/null +++ b/app/components/UI/AccountSelectorList/hooks/index.ts @@ -0,0 +1,3 @@ +/* eslint-disable import/prefer-default-export */ + +export { useAccounts } from './useAccounts'; diff --git a/app/components/UI/AccountSelectorList/hooks/useAccounts/index.ts b/app/components/UI/AccountSelectorList/hooks/useAccounts/index.ts new file mode 100644 index 00000000000..7228467747c --- /dev/null +++ b/app/components/UI/AccountSelectorList/hooks/useAccounts/index.ts @@ -0,0 +1,4 @@ +/* eslint-disable import/prefer-default-export */ + +export { useAccounts } from './useAccounts'; +export type { EnsByAccountAddress } from './useAccounts.types'; diff --git a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.ts b/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.ts new file mode 100644 index 00000000000..4dfba38565b --- /dev/null +++ b/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.ts @@ -0,0 +1,205 @@ +/* eslint-disable import/prefer-default-export */ + +// Third party dependencies. +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { toChecksumAddress } from 'ethereumjs-util'; +import { KeyringTypes } from '@metamask/controllers'; +import { isEqual } from 'lodash'; + +// External Dependencies. +import UntypedEngine from '../../../../../core/Engine'; +import { Account } from '../..'; +import { doENSReverseLookup } from '../../../../../util/ENSUtils'; +import { hexToBN, renderFromWei, weiToFiat } from '../../../../../util/number'; +import { getTicker } from '../../../../..//util/transactions'; + +// Internal dependencies +import { + EnsByAccountAddress, + UseAccounts, + UseAccountsParams, +} from './useAccounts.types'; + +/** + * Hook that returns both wallet accounts and ens name information. + * + * @returns Object that contins both wallet accounts and ens name information. + */ +export const useAccounts = ({ + checkBalanceError, + isLoading = false, +}: UseAccountsParams = {}): UseAccounts => { + const Engine = UntypedEngine as any; + const isMountedRef = useRef(false); + const [accounts, setAccounts] = useState([]); + const [ensByAccountAddress, setENSByAccountAddress] = + useState({}); + const identities = useSelector( + (state: any) => + state.engine.backgroundState.PreferencesController.identities, + ); + const network = useSelector( + (state: any) => state.engine.backgroundState.NetworkController.network, + ); + const selectedAddress = useSelector( + (state: any) => + state.engine.backgroundState.PreferencesController.selectedAddress, + ); + const accountInfoByAddress = useSelector( + (state: any) => + state.engine.backgroundState.AccountTrackerController.accounts, + (left, right) => isEqual(left, right), + ); + const conversionRate = useSelector( + (state: any) => + state.engine.backgroundState.CurrencyRateController.conversionRate, + ); + const currentCurrency = useSelector( + (state: any) => + state.engine.backgroundState.CurrencyRateController.currentCurrency, + ); + const ticker = useSelector( + (state: any) => + state.engine.backgroundState.NetworkController.provider.ticker, + ); + + const fetchENSNames = useCallback( + async ({ + flattenedAccounts, + startingIndex, + }: { + flattenedAccounts: Account[]; + startingIndex: number; + }) => { + // Ensure index exists in account list. + let safeStartingIndex = startingIndex; + let mirrorIndex = safeStartingIndex - 1; + let latestENSbyAccountAddress: EnsByAccountAddress = {}; + + if (startingIndex < 0) { + safeStartingIndex = 0; + } else if (startingIndex > flattenedAccounts.length) { + safeStartingIndex = flattenedAccounts.length - 1; + } + + const fetchENSName = async (accountIndex: number) => { + const { address } = flattenedAccounts[accountIndex]; + try { + const ens: string | undefined = await doENSReverseLookup( + address, + network, + ); + if (ens) { + latestENSbyAccountAddress = { + ...latestENSbyAccountAddress, + [address]: ens, + }; + } + } catch (e) { + // ENS either doesn't exists or failed to fetch. + } + }; + + // Iterate outwards in both directions starting at the starting index. + while (mirrorIndex >= 0 || safeStartingIndex < flattenedAccounts.length) { + if (!isMountedRef.current) return; + if (safeStartingIndex < flattenedAccounts.length) { + await fetchENSName(safeStartingIndex); + } + if (mirrorIndex >= 0) { + await fetchENSName(mirrorIndex); + } + mirrorIndex--; + safeStartingIndex++; + setENSByAccountAddress(latestENSbyAccountAddress); + } + }, + [network], + ); + + const getAccounts = useCallback(() => { + // Keep track of the Y position of account item. Used for scrolling purposes. + let yOffset = 0; + let selectedIndex = 0; + // Reading keyrings directly from Redux doesn't work at the momemt. + const keyrings: any[] = Engine.context.KeyringController.state.keyrings; + const flattenedAccounts: Account[] = keyrings.reduce((result, keyring) => { + const { + accounts: accountAddresses, + type, + }: { accounts: string[]; type: KeyringTypes } = keyring; + for (const index in accountAddresses) { + const address = accountAddresses[index]; + const isSelected = selectedAddress === address; + if (isSelected) { + selectedIndex = result.length; + } + const checksummedAddress = toChecksumAddress(address); + const identity = identities[checksummedAddress]; + if (!identity) continue; + const { name } = identity; + // TODO - Improve UI to either include loading and/or balance load failures. + const balanceWeiHex = + accountInfoByAddress?.[checksummedAddress]?.balance || 0x0; + const balanceETH = renderFromWei(balanceWeiHex); // Gives ETH + const balanceFiat = weiToFiat( + hexToBN(balanceWeiHex) as any, + conversionRate, + currentCurrency, + ); + const balanceTicker = getTicker(ticker); + const balanceLabel = `${balanceFiat}\n${balanceETH} ${balanceTicker}`; + const balanceError = checkBalanceError?.(balanceWeiHex); + const mappedAccount: Account = { + name, + address: checksummedAddress, + type, + yOffset, + isSelected, + // TODO - Also fetch assets. Reference AccountList component. + // assets + assets: { fiatBalance: balanceLabel }, + balanceError, + }; + result.push(mappedAccount); + // Calculate height of the account item. + yOffset += 78; + if (balanceError) { + yOffset += 22; + } + if (type !== KeyringTypes.hd) { + yOffset += 24; + } + } + return result; + }, []); + setAccounts(flattenedAccounts); + fetchENSNames({ flattenedAccounts, startingIndex: selectedIndex }); + /* eslint-disable-next-line */ + }, [ + selectedAddress, + identities, + fetchENSNames, + accountInfoByAddress, + conversionRate, + currentCurrency, + ticker, + checkBalanceError, + ]); + + useEffect(() => { + if (!isMountedRef.current) { + isMountedRef.current = true; + } + if (isLoading) return; + // setTimeout is needed for now to ensure next frame contains updated keyrings. + setTimeout(getAccounts, 0); + // Once we can pull keyrings from Redux, we will replace the deps with keyrings. + return () => { + isMountedRef.current = false; + }; + }, [getAccounts, isLoading]); + + return { accounts, ensByAccountAddress }; +}; diff --git a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.types.ts b/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.types.ts new file mode 100644 index 00000000000..df6ae658af0 --- /dev/null +++ b/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.types.ts @@ -0,0 +1,41 @@ +/* eslint-disable import/prefer-default-export */ + +import { + Account, + AccountSelectorListProps, +} from '../../AccountSelectorList.types'; + +/** + * Mapping of ENS names by account address. + */ +export type EnsByAccountAddress = Record; + +/** + * Optional params that useAccount hook takes. + */ +export interface UseAccountsParams { + /** + * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. + * @param balance - The ticker balance of an account in wei and hex string format. + */ + checkBalanceError?: AccountSelectorListProps['checkBalanceError']; + /** + * Optional boolean that indicates if accounts are being processed in the background. Setting this to true will prevent any unnecessary updates while loading. + * @default false + */ + isLoading?: boolean; +} + +/** + * Return value for useAccounts hook. + */ +export interface UseAccounts { + /** + * List of account information. + */ + accounts: Account[]; + /** + * Mapping of ENS names by account address. + */ + ensByAccountAddress: EnsByAccountAddress; +} diff --git a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccountsTest.tsx b/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccountsTest.tsx new file mode 100644 index 00000000000..173b7e17168 --- /dev/null +++ b/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccountsTest.tsx @@ -0,0 +1,65 @@ +// TODO - Finish tests + +// import { renderHook } from '@testing-library/react-hooks'; +// import { useAccounts } from './useAccounts'; + +// const initialState = { +// engine: { +// backgroundState: { +// PreferencesController: { +// selectedAddress: '0xc4800C54cB70E7Dac746C2fA829da0004443613e', +// identities: { +// '0xc4800C54cB70E7Dac746C2fA829da0004443613e': { name: 'Account 1' }, +// }, +// }, +// NetworkController: { +// network: '1', +// provider: { +// ticker: 'ETH', +// }, +// }, +// CurrencyRateController: { +// conversionRate: 1641.87, +// currentCurrency: 'usd', +// }, +// AccountTrackerController: { +// accounts: { +// '0xc4800C54cB70E7Dac746C2fA829da0004443613e': { +// balance: '0x0', +// }, +// }, +// }, +// }, +// }, +// }; + +// jest.mock('react-redux', () => ({ +// ...jest.requireActual('react-redux'), +// useSelector: jest.fn().mockImplementation((cb) => cb(initialState)), +// })); + +// jest.mock('../../../../../core/Engine', () => ({ +// context: { +// KeyringController: { +// state: { +// keyrings: [ +// { +// accounts: ['0xc4800C54cB70E7Dac746C2fA829da0004443613e'], +// type: 'HD Key Tree', +// }, +// ], +// }, +// }, +// }, +// })); + +// describe('useAccounts', () => { +// test('it should start with a state of "Loading"', async () => { +// const { result, waitForNextUpdate } = renderHook(() => useAccounts()); +// jest.runAllTimers(); +// await waitForNextUpdate(); + +// // TODO - Actually test hook +// expect(true).toBeTruthy(); +// }); +// }); diff --git a/app/components/UI/AccountSelectorList/index.ts b/app/components/UI/AccountSelectorList/index.ts new file mode 100644 index 00000000000..34f107af8bc --- /dev/null +++ b/app/components/UI/AccountSelectorList/index.ts @@ -0,0 +1,6 @@ +export { default } from './AccountSelectorList'; +export type { + Account, + AccountSelectorListProps, +} from './AccountSelectorList.types'; +export * from './hooks'; diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js index 9f190e150dc..293115f553d 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -22,7 +22,6 @@ import { } from '../../../util/networks'; import Identicon from '../Identicon'; import StyledButton from '../StyledButton'; -import AccountList from '../AccountList'; import NetworkList from '../NetworkList'; import { renderFromWei, renderFiat } from '../../../util/number'; import { strings } from '../../../../locales/i18n'; @@ -30,7 +29,6 @@ import Modal from 'react-native-modal'; import SecureKeychain from '../../../core/SecureKeychain'; import { toggleNetworkModal, - toggleAccountsModal, toggleReceiveModal, } from '../../../actions/modals'; import { showAlert } from '../../../actions/alert'; @@ -351,10 +349,6 @@ class DrawerView extends PureComponent { * Action that toggles the network modal */ toggleNetworkModal: PropTypes.func, - /** - * Action that toggles the accounts modal - */ - toggleAccountsModal: PropTypes.func, /** * Action that toggles the receive modal */ @@ -375,10 +369,6 @@ class DrawerView extends PureComponent { * Start transaction with asset */ newAssetTransaction: PropTypes.func.isRequired, - /** - * Boolean that determines the status of the networks modal - */ - accountsModalVisible: PropTypes.bool.isRequired, /** * Boolean that determines if the user has set a password before */ @@ -468,7 +458,6 @@ class DrawerView extends PureComponent { previousBalance = null; processedNewBalance = false; animatingNetworksModal = false; - animatingAccountsModal = false; isCurrentAccountImported() { let ret = false; @@ -612,16 +601,17 @@ class DrawerView extends PureComponent { } }; - toggleAccountsModal = async () => { - if (!this.animatingAccountsModal) { - this.animatingAccountsModal = true; - this.props.toggleAccountsModal(); - setTimeout(() => { - this.animatingAccountsModal = false; - }, 500); - } - !this.props.accountsModalVisible && - this.trackEvent(ANALYTICS_EVENT_OPTS.NAVIGATION_TAPS_ACCOUNT_NAME); + openAccountSelector = () => { + const { navigation } = this.props; + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_SELECTOR, + params: { + onOpenImportAccount: this.hideDrawer, + onOpenConnectHardwareWallet: this.hideDrawer, + onSelectAccount: this.hideDrawer, + }, + }); + this.trackEvent(ANALYTICS_EVENT_OPTS.NAVIGATION_TAPS_ACCOUNT_NAME); }; toggleReceiveModal = () => { @@ -831,27 +821,8 @@ class DrawerView extends PureComponent { this.hideDrawer(); } - hideDrawer() { + hideDrawer = () => { this.props.onCloseDrawer(); - } - - onAccountChange = () => { - setTimeout(() => { - this.toggleAccountsModal(); - this.hideDrawer(); - }, 300); - }; - - onImportAccount = () => { - this.toggleAccountsModal(); - this.props.navigation.navigate('ImportPrivateKeyView'); - this.hideDrawer(); - }; - - onConnectHardware = () => { - this.toggleAccountsModal(); - this.props.navigation.navigate('ConnectQRHardwareFlow'); - this.hideDrawer(); }; hasBlockExplorer = (providerType) => { @@ -1160,7 +1131,6 @@ class DrawerView extends PureComponent { accounts, identities, selectedAddress, - keyrings, currentCurrency, ticker, seedphraseBackedUp, @@ -1234,7 +1204,7 @@ class DrawerView extends PureComponent { @@ -1243,7 +1213,7 @@ class DrawerView extends PureComponent { @@ -1416,28 +1386,6 @@ class DrawerView extends PureComponent { onClose={this.closeInvalidCustomNetworkAlert} /> - - - {this.renderOnboardingWizard()} ({ state.engine.backgroundState.CurrencyRateController.currentCurrency, keyrings: state.engine.backgroundState.KeyringController.keyrings, networkModalVisible: state.modals.networkModalVisible, - accountsModalVisible: state.modals.accountsModalVisible, receiveModalVisible: state.modals.receiveModalVisible, passwordSet: state.user.passwordSet, wizard: state.wizard, @@ -1493,7 +1440,6 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ toggleNetworkModal: () => dispatch(toggleNetworkModal()), - toggleAccountsModal: () => dispatch(toggleAccountsModal()), toggleReceiveModal: () => dispatch(toggleReceiveModal()), showAlert: (config) => dispatch(showAlert(config)), newAssetTransaction: (selectedAsset) => diff --git a/app/components/UI/FiatOnRampAggregator/components/AccountSelector.tsx b/app/components/UI/FiatOnRampAggregator/components/AccountSelector.tsx index 21c03a43a08..b5ae8f04990 100644 --- a/app/components/UI/FiatOnRampAggregator/components/AccountSelector.tsx +++ b/app/components/UI/FiatOnRampAggregator/components/AccountSelector.tsx @@ -1,13 +1,14 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { StyleSheet } from 'react-native'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; -import { toggleAccountsModal } from '../../../../actions/modals'; import EthereumAddress from '../../EthereumAddress'; import JSIdenticon from '../../Identicon'; import BaseText from '../../../Base/Text'; import JSSelectorButton from '../../../Base/SelectorButton'; +import { useNavigation } from '@react-navigation/native'; +import Routes from '../../../../constants/navigation/Routes'; // TODO: Convert into typescript and correctly type const SelectorButton = JSSelectorButton as any; @@ -26,7 +27,7 @@ const styles = StyleSheet.create({ }); const AccountSelector = () => { - const dispatch = useDispatch(); + const navigation = useNavigation(); const selectedAddress = useSelector( (state: any) => state.engine.backgroundState.PreferencesController.selectedAddress, @@ -37,11 +38,13 @@ const AccountSelector = () => { state.engine.backgroundState.PreferencesController.identities, ); - const handleToggleAccountsModal = useCallback(() => { - dispatch(toggleAccountsModal()); - }, [dispatch]); + const openAccountSelector = () => + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_SELECTOR, + }); + return ( - + {identities[selectedAddress]?.name} ( diff --git a/app/components/UI/FiatOrders/components/AccountSelector.js b/app/components/UI/FiatOrders/components/AccountSelector.js index 2257c5ed348..e3500c1938d 100644 --- a/app/components/UI/FiatOrders/components/AccountSelector.js +++ b/app/components/UI/FiatOrders/components/AccountSelector.js @@ -2,12 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import { StyleSheet } from 'react-native'; import { connect } from 'react-redux'; +import { useNavigation } from '@react-navigation/native'; -import { toggleAccountsModal } from '../../../../actions/modals'; import EthereumAddress from '../../EthereumAddress'; import Identicon from '../../Identicon'; import Text from '../../../Base/Text'; import SelectorButton from '../../../Base/SelectorButton'; +import Routes from '../../../../constants/navigation/Routes'; const styles = StyleSheet.create({ selector: { @@ -19,22 +20,26 @@ const styles = StyleSheet.create({ marginHorizontal: 5, }, }); -const AccountSelector = ({ - toggleAccountsModal, - selectedAddress, - identities, -}) => ( - - - - {identities[selectedAddress]?.name} ( - ) - - -); +const AccountSelector = ({ selectedAddress, identities }) => { + const navigation = useNavigation(); + + const openAccountSelector = () => + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_SELECTOR, + }); + + return ( + + + + {identities[selectedAddress]?.name} ( + ) + + + ); +}; AccountSelector.propTypes = { - toggleAccountsModal: PropTypes.func.isRequired, selectedAddress: PropTypes.string.isRequired, identities: PropTypes.object.isRequired, }; @@ -45,7 +50,4 @@ const mapStateToProps = (state) => ({ identities: state.engine.backgroundState.PreferencesController.identities, }); -const mapDispatchToProps = (dispatch) => ({ - toggleAccountsModal: () => dispatch(toggleAccountsModal()), -}); -export default connect(mapStateToProps, mapDispatchToProps)(AccountSelector); +export default connect(mapStateToProps, null)(AccountSelector); diff --git a/app/components/Views/AccountSelector/AccountSelector.constants.ts b/app/components/Views/AccountSelector/AccountSelector.constants.ts new file mode 100644 index 00000000000..b41f8b4becb --- /dev/null +++ b/app/components/Views/AccountSelector/AccountSelector.constants.ts @@ -0,0 +1,5 @@ +/* eslint-disable import/prefer-default-export */ + +export const ACCOUNT_LIST_ID = 'account-list'; +export const CREATE_ACCOUNT_BUTTON_ID = 'create-account-button'; +export const IMPORT_ACCOUNT_BUTTON_ID = 'import-account-button'; diff --git a/app/components/Views/AccountSelector/AccountSelector.tsx b/app/components/Views/AccountSelector/AccountSelector.tsx new file mode 100644 index 00000000000..706bede0b6a --- /dev/null +++ b/app/components/Views/AccountSelector/AccountSelector.tsx @@ -0,0 +1,130 @@ +// Third party dependencies. +import React, { useCallback, useRef, useState } from 'react'; +import { useNavigation } from '@react-navigation/native'; + +// External dependencies. +import AccountSelectorList from '../../UI/AccountSelectorList'; +import SheetActions from '../../../component-library/components-temp/SheetActions'; +import SheetBottom, { + SheetBottomRef, +} from '../../../component-library/components/Sheet/SheetBottom'; +import SheetHeader from '../../../component-library/components/Sheet/SheetHeader'; +import UntypedEngine from '../../../core/Engine'; +import Logger from '../../../util/Logger'; +import AnalyticsV2 from '../../../util/analyticsV2'; +import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; +import { strings } from '../../../../locales/i18n'; +import { + ACCOUNT_LIST_ID, + CREATE_ACCOUNT_BUTTON_ID, + IMPORT_ACCOUNT_BUTTON_ID, +} from './AccountSelector.constants'; + +// Internal dependencies. +import { AccountSelectorProps } from './AccountSelector.types'; + +const AccountSelector = ({ route }: AccountSelectorProps) => { + const { + onCreateNewAccount, + onOpenImportAccount, + onOpenConnectHardwareWallet, + onSelectAccount, + checkBalanceError, + isSelectOnly, + } = route.params || {}; + const Engine = UntypedEngine as any; + const [isLoading, setIsLoading] = useState(false); + const sheetRef = useRef(null); + const navigation = useNavigation(); + + const _onSelectAccount = (address: string) => { + sheetRef.current?.hide(); + onSelectAccount?.(address); + }; + + const createNewAccount = useCallback(async () => { + const { KeyringController } = Engine.context; + try { + setIsLoading(true); + await KeyringController.addNewAccount(); + AnalyticsV2.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); + } catch (e: any) { + Logger.error(e, 'error while trying to add a new account'); + } finally { + setIsLoading(false); + } + onCreateNewAccount?.(); + /* eslint-disable-next-line */ + }, [onCreateNewAccount, setIsLoading]); + + const openImportAccount = useCallback(() => { + sheetRef.current?.hide(() => { + navigation.navigate('ImportPrivateKeyView'); + // Is this where we want to track importing an account or within ImportPrivateKeyView screen? + AnalyticsV2.trackEvent( + ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + ); + }); + onOpenImportAccount?.(); + }, [onOpenImportAccount, navigation]); + + const openConnectHardwareWallet = useCallback(() => { + sheetRef.current?.hide(() => { + navigation.navigate('ConnectQRHardwareFlow'); + // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? + AnalyticsV2.trackEvent( + AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, + ); + }); + onOpenConnectHardwareWallet?.(); + }, [onOpenConnectHardwareWallet, navigation]); + + const renderSheetActions = useCallback( + () => + !isSelectOnly && ( + + ), + [ + isSelectOnly, + isLoading, + createNewAccount, + openImportAccount, + openConnectHardwareWallet, + ], + ); + + return ( + + + + {renderSheetActions()} + + ); +}; + +export default AccountSelector; diff --git a/app/components/Views/AccountSelector/AccountSelector.types.ts b/app/components/Views/AccountSelector/AccountSelector.types.ts new file mode 100644 index 00000000000..3e9758cd7a7 --- /dev/null +++ b/app/components/Views/AccountSelector/AccountSelector.types.ts @@ -0,0 +1,40 @@ +// External dependencies. +import { AccountSelectorListProps } from '../../../components/UI/AccountSelectorList'; + +/** + * AccountSelectorProps props. + */ +export interface AccountSelectorProps { + /** + * Props that are passed in while navigating to screen. + */ + route: { + params?: { + /** + * Optional callback that is called whenever a new account is being created. + */ + onCreateNewAccount?: () => void; + /** + * Optional callback that is called whenever import account is being opened. + */ + onOpenImportAccount?: () => void; + /** + * Optional callback that is called whenever connect hardware wallet is being opened. + */ + onOpenConnectHardwareWallet?: () => void; + /** + * Optional callback that is called whenever an account is selected. + */ + onSelectAccount?: (address: string) => void; + /** + * Optional boolean that indicates if the sheet is for selection only. Other account actions are disabled when this is true. + */ + isSelectOnly?: boolean; + /** + * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. + * @param balance - The ticker balance of an account in wei and hex string format. + */ + checkBalanceError?: AccountSelectorListProps['checkBalanceError']; + }; + }; +} diff --git a/app/components/Views/AccountSelector/index.ts b/app/components/Views/AccountSelector/index.ts new file mode 100644 index 00000000000..ef2b9e5a0de --- /dev/null +++ b/app/components/Views/AccountSelector/index.ts @@ -0,0 +1 @@ +export { default } from './AccountSelector'; diff --git a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.tsx.snap b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.tsx.snap index 91d863c8289..f82e4403280 100644 --- a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.tsx.snap @@ -14,16 +14,6 @@ exports[`Confirm should render correctly 1`] = ` conversionRate={1} currentCurrency="USD" identities={Object {}} - keyrings={ - Array [ - Object { - "accounts": Array [ - "0x", - ], - "type": "HD Key Tree", - }, - ] - } network="1" networkType="mainnet" prepareTransaction={[Function]} diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index bada08f1072..2f6961f787d 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -46,7 +46,6 @@ import { import { getGasLimit } from '../../../../util/custom-gas'; import Engine from '../../../../core/Engine'; import Logger from '../../../../util/Logger'; -import AccountList from '../../../UI/AccountList'; import CustomNonceModal from '../../../UI/CustomNonceModal'; import { doENSReverseLookup } from '../../../../util/ENSUtils'; import NotificationManager from '../../../../core/NotificationManager'; @@ -324,10 +323,6 @@ class Confirm extends PureComponent { * List of accounts from the PreferencesController */ identities: PropTypes.object, - /** - * List of keyrings - */ - keyrings: PropTypes.array, /** * Selected asset from current transaction state */ @@ -387,7 +382,6 @@ class Confirm extends PureComponent { transactionTotalAmount: undefined, transactionTotalAmountFiat: undefined, errorMessage: undefined, - fromAccountModalVisible: false, warningModalVisible: false, mode: REVIEW, gasSelected: AppConstants.GAS_OPTIONS.MEDIUM, @@ -1030,13 +1024,11 @@ class Confirm extends PureComponent { return balanceIsInsufficient ? strings('transaction.insufficient') : null; }; - onAccountChange = async (accountAddress) => { + onSelectAccount = async (accountAddress) => { const { identities, accounts } = this.props; const { name } = identities[accountAddress]; - const { PreferencesController } = Engine.context; const ens = await doENSReverseLookup(accountAddress); const fromAccountName = ens || name; - PreferencesController.setSelectedAddress(accountAddress); // If new account doesn't have the asset this.setState({ fromAccountName, @@ -1044,7 +1036,18 @@ class Confirm extends PureComponent { balanceIsZero: hexToBN(accounts[accountAddress].balance).isZero(), }); this.parseTransactionDataHeader(); - this.toggleFromAccountModal(); + }; + + openAccountSelector = () => { + const { navigation } = this.props; + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_SELECTOR, + params: { + isSelectOnly: true, + onSelectAccount: this.onSelectAccount, + checkBalanceError: this.getBalanceError, + }, + }); }; toggleHexDataModal = () => { @@ -1052,11 +1055,6 @@ class Confirm extends PureComponent { this.setState({ hexDataModalVisible: !hexDataModalVisible }); }; - toggleFromAccountModal = () => { - const { fromAccountModalVisible } = this.state; - this.setState({ fromAccountModalVisible: !fromAccountModalVisible }); - }; - cancelGasEdition = () => { this.setState({ EIP1559TransactionDataTemp: { ...this.state.EIP1559TransactionData }, @@ -1286,37 +1284,6 @@ class Confirm extends PureComponent { ); }; - renderFromAccountModal = () => { - const { identities, keyrings, ticker } = this.props; - const { fromAccountModalVisible, fromSelectedAddress } = this.state; - const colors = this.context.colors || mockTheme.colors; - const styles = createStyles(colors); - - return ( - - - - ); - }; - buyEth = () => { const { navigation } = this.props; try { @@ -1464,7 +1431,7 @@ class Confirm extends PureComponent { > - {this.renderFromAccountModal()} {mode === EDIT && this.renderCustomGasModalLegacy()} {mode === EDIT_NONCE && this.renderCustomNonceModal()} {mode === EDIT_EIP1559 && this.renderCustomGasModalEIP1559()} @@ -1666,7 +1632,6 @@ const mapStateToProps = (state) => ({ showCustomNonce: state.settings.showCustomNonce, chainId: state.engine.backgroundState.NetworkController.provider.chainId, ticker: state.engine.backgroundState.NetworkController.provider.ticker, - keyrings: state.engine.backgroundState.KeyringController.keyrings, transaction: getNormalizedTxState(state), selectedAsset: state.transaction.selectedAsset, transactionState: state.transaction, diff --git a/app/components/Views/SendFlow/SendTo/__snapshots__/index.test.tsx.snap b/app/components/Views/SendFlow/SendTo/__snapshots__/index.test.tsx.snap index 94e253a0f9b..7bafcb32ce5 100644 --- a/app/components/Views/SendFlow/SendTo/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/SendFlow/SendTo/__snapshots__/index.test.tsx.snap @@ -29,16 +29,6 @@ exports[`SendTo should render correctly 1`] = ` }, } } - keyrings={ - Array [ - Object { - "accounts": Array [ - "0x", - ], - "type": "HD Key Tree", - }, - ] - } network="1" newAssetTransaction={[Function]} providerType="mainnet" diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js index 727b5a168f9..69e51c564cc 100644 --- a/app/components/Views/SendFlow/SendTo/index.js +++ b/app/components/Views/SendFlow/SendTo/index.js @@ -11,7 +11,6 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { toChecksumAddress } from 'ethereumjs-util'; import { util } from '@metamask/controllers'; -import Modal from 'react-native-modal'; import { SafeAreaView } from 'react-native-safe-area-context'; import Icon from 'react-native-vector-icons/FontAwesome'; import Engine from '../../../../core/Engine'; @@ -22,7 +21,6 @@ import Text from '../../../Base/Text'; import { AddressFrom, AddressTo } from './../AddressInputs'; import WarningMessage from '../WarningMessage'; import { getSendFlowTitle } from '../../../UI/Navbar'; -import AccountList from '../../../UI/AccountList'; import ActionModal from '../../../UI/ActionModal'; import StyledButton from '../../../UI/StyledButton'; import { allowedToBuy } from '../../../UI/FiatOrders'; @@ -98,10 +96,6 @@ class SendFlow extends PureComponent { * List of accounts from the PreferencesController */ identities: PropTypes.object, - /** - * List of keyrings - */ - keyrings: PropTypes.array, /** * Current provider ticker */ @@ -145,7 +139,6 @@ class SendFlow extends PureComponent { state = { addressError: undefined, balanceIsZero: false, - fromAccountModalVisible: false, addToAddressBookModalVisible: false, fromSelectedAddress: this.props.selectedAddress, fromAccountName: this.props.identities[this.props.selectedAddress].name, @@ -219,11 +212,6 @@ class SendFlow extends PureComponent { this.updateNavBar(); }; - toggleFromAccountModal = () => { - const { fromAccountModalVisible } = this.state; - this.setState({ fromAccountModalVisible: !fromAccountModalVisible }); - }; - toggleAddToAddressBookModal = () => { const { addToAddressBookModalVisible } = this.state; this.setState({ @@ -231,16 +219,14 @@ class SendFlow extends PureComponent { }); }; - onAccountChange = async (accountAddress) => { - const { identities, ticker, accounts } = this.props; + onSelectAccount = async (accountAddress) => { + const { ticker, accounts, identities } = this.props; const { name } = identities[accountAddress]; - const { PreferencesController } = Engine.context; const fromAccountBalance = `${renderFromWei( accounts[accountAddress].balance, )} ${getTicker(ticker)}`; const ens = await doENSReverseLookup(accountAddress); const fromAccountName = ens || name; - PreferencesController.setSelectedAddress(accountAddress); // If new account doesn't have the asset this.props.setSelectedAsset(getEther(ticker)); this.setState({ @@ -249,8 +235,19 @@ class SendFlow extends PureComponent { fromSelectedAddress: accountAddress, balanceIsZero: hexToBN(accounts[accountAddress].balance).isZero(), }); - this.toggleFromAccountModal(); }; + + openAccountSelector = () => { + const { navigation } = this.props; + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_SELECTOR, + params: { + isSelectOnly: true, + onSelectAccount: this.onSelectAccount, + }, + }); + }; + /** * This returns the address name from the address book or user accounts if the selectedAddress exist there * @param {String} toAccount - Address input @@ -599,36 +596,6 @@ class SendFlow extends PureComponent { ); }; - renderFromAccountModal = () => { - const { identities, keyrings, ticker } = this.props; - const { fromAccountModalVisible, fromSelectedAddress } = this.state; - const colors = this.context.colors || mockTheme.colors; - const styles = createStyles(colors); - - return ( - - - - ); - }; - onToInputFocus = () => { const { toInputHighlighted } = this.state; this.setState({ toInputHighlighted: !toInputHighlighted }); @@ -710,7 +677,7 @@ class SendFlow extends PureComponent { > )} - - {this.renderFromAccountModal()} {this.renderAddToAddressBookModal()} ); @@ -863,7 +828,6 @@ const mapStateToProps = (state) => ({ state.engine.backgroundState.PreferencesController.selectedAddress, selectedAsset: state.transaction.selectedAsset, identities: state.engine.backgroundState.PreferencesController.identities, - keyrings: state.engine.backgroundState.KeyringController.keyrings, ticker: state.engine.backgroundState.NetworkController.provider.ticker, network: state.engine.backgroundState.NetworkController.network, providerType: state.engine.backgroundState.NetworkController.provider.type, diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index 7dccbdaa618..c76cca2ebbb 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -35,6 +35,9 @@ const Routes = { SETTINGS: { CONTACT_FORM: 'ContactForm', }, + SHEET: { + ACCOUNT_SELECTOR: 'AccountSelector', + }, }; export default Routes; diff --git a/app/reducers/modals/index.js b/app/reducers/modals/index.js index bae979c7904..1f2d416d1d0 100644 --- a/app/reducers/modals/index.js +++ b/app/reducers/modals/index.js @@ -1,6 +1,5 @@ const initialState = { networkModalVisible: false, - accountsModalVisible: false, collectibleContractModalVisible: false, receiveModalVisible: false, receiveAsset: undefined, @@ -22,11 +21,6 @@ const modalsReducer = (state = initialState, action) => { receiveAsset: action.asset, }; } - case 'TOGGLE_ACCOUNT_MODAL': - return { - ...state, - accountsModalVisible: !state.accountsModalVisible, - }; case 'TOGGLE_COLLECTIBLE_CONTRACT_MODAL': return { ...state, diff --git a/app/util/ENSUtils.js b/app/util/ENSUtils.js index cc61cd79671..c2878ae123f 100644 --- a/app/util/ENSUtils.js +++ b/app/util/ENSUtils.js @@ -2,6 +2,10 @@ import Engine from '../core/Engine'; import networkMap from 'ethjs-ens/lib/network-map.json'; import ENS from 'ethjs-ens'; import { toLowerCaseEquals } from '../util/general'; +const ENS_NAME_NOT_DEFINED_ERROR = 'ENS name not defined'; +const INVALID_ENS_NAME_ERROR = 'invalid ENS name'; +// One hour cache threshold. +const CACHE_REFRESH_THRESHOLD = 60 * 60 * 1000; /** * Utility class with the single responsibility @@ -12,12 +16,13 @@ class ENSCache { } export async function doENSReverseLookup(address, network) { - const cache = ENSCache.cache[network + address]; - if (cache) { - return Promise.resolve(cache); - } - const { provider } = Engine.context.NetworkController; + const { name: cachedName, timestamp } = + ENSCache.cache[network + address] || {}; + const nowTimestamp = Date.now(); + if (timestamp && nowTimestamp - timestamp < CACHE_REFRESH_THRESHOLD) { + return Promise.resolve(cachedName); + } const networkHasEnsSupport = Boolean(networkMap[network]); @@ -27,11 +32,17 @@ export async function doENSReverseLookup(address, network) { const name = await this.ens.reverse(address); const resolvedAddress = await this.ens.lookup(name); if (toLowerCaseEquals(address, resolvedAddress)) { - ENSCache.cache[network + address] = name; + ENSCache.cache[network + address] = { name, timestamp: Date.now() }; return name; } - // eslint-disable-next-line no-empty - } catch (e) {} + } catch (e) { + if ( + e.message.includes(ENS_NAME_NOT_DEFINED_ERROR) || + e.message.includes(INVALID_ENS_NAME_ERROR) + ) { + ENSCache.cache[network + address] = { timestamp: Date.now() }; + } + } } } diff --git a/app/util/analyticsV2.js b/app/util/analyticsV2.js index d95ece310d2..2f014964bf6 100644 --- a/app/util/analyticsV2.js +++ b/app/util/analyticsV2.js @@ -186,7 +186,7 @@ export const ANALYTICS_EVENTS_V2 = { * @param {Object} eventName * @param {Object} params */ -export const trackEventV2 = (eventName, params) => { +export const trackEventV2 = (eventName, params = undefined) => { InteractionManager.runAfterInteractions(() => { let anonymousEvent = false; try { diff --git a/package.json b/package.json index 545ccd8921f..b241901ebe9 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,7 @@ "@testing-library/react": "^13.3.0", "@testing-library/react-native": "^9.1.0", "@tradle/react-native-http": "2.0.1", + "@types/lodash": "^4.14.184", "@walletconnect/client": "^1.7.1", "@walletconnect/utils": "^1.7.1", "asyncstorage-down": "4.2.0", @@ -184,6 +185,7 @@ "is-url": "^1.2.4", "json-rpc-engine": "^6.1.0", "json-rpc-middleware-stream": "3.0.0", + "lodash": "^4.17.21", "lottie-react-native": "git+https://github.com/MetaMask/lottie-react-native.git#7ce6a78ac4ac7b9891bc513cb3f12f8b9c9d9106", "metro-config": "^0.71.1", "multihashes": "0.4.14", diff --git a/yarn.lock b/yarn.lock index ac45ed2af23..692dc97632f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4081,6 +4081,11 @@ dependencies: "@types/node" "*" +"@types/lodash@^4.14.184": + version "4.14.184" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe" + integrity sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q== + "@types/node@*": version "16.0.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.1.tgz#70cedfda26af7a2ca073fdcc9beb2fff4aa693f8" @@ -11948,7 +11953,7 @@ lodash-es@^4.17.15: lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== lodash.escape@^4.0.1: version "4.0.1" @@ -11963,7 +11968,7 @@ lodash.flattendeep@^4.4.0: lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== lodash.isplainobject@^4.0.6: version "4.0.6" From c5b946a10ed65efe3478daaa554324e54fdda696 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 28 Sep 2022 16:06:39 -0700 Subject: [PATCH 03/91] Add new logos (#5046) --- app/components/UI/NetworkList/index.js | 20 ++++++++++++------ .../UI/Swaps/components/TokenIcon.js | 2 +- .../__snapshots__/TokenIcon.test.js.snap | 4 ++-- app/components/Views/Wallet/index.tsx | 2 +- app/core/GasPolling/GasPolling.test.ts | 2 +- app/images/eth-logo-new.png | Bin 0 -> 2854 bytes app/images/goerli-logo-dark.png | Bin 0 -> 1720 bytes app/images/image-icons.js | 10 ++++++++- app/images/kovan-logo-dark.png | Bin 0 -> 1363 bytes app/images/rinkeby-logo-dark.png | Bin 0 -> 1604 bytes app/images/ropsten-logo-dark.png | Bin 0 -> 1461 bytes app/util/networks/index.js | 18 ++++++++++------ app/util/transactions/index.js | 2 +- 13 files changed, 40 insertions(+), 20 deletions(-) create mode 100644 app/images/eth-logo-new.png create mode 100644 app/images/goerli-logo-dark.png create mode 100644 app/images/kovan-logo-dark.png create mode 100644 app/images/rinkeby-logo-dark.png create mode 100644 app/images/ropsten-logo-dark.png diff --git a/app/components/UI/NetworkList/index.js b/app/components/UI/NetworkList/index.js index b2fbd3b8db1..4630b0095e4 100644 --- a/app/components/UI/NetworkList/index.js +++ b/app/components/UI/NetworkList/index.js @@ -274,11 +274,17 @@ export class NetworkList extends PureComponent { ) : ( ))} - {!isCustomRpc && ( - - {name[0]} - - )} + {!isCustomRpc && + (image ? ( + + ) : ( + + {name[0]} + + ))} {name} @@ -293,7 +299,7 @@ export class NetworkList extends PureComponent { const colors = this.context.colors || mockTheme.colors; return this.getOtherNetworks().map((network, i) => { - const { color, name } = Networks[network]; + const { name, imageSource } = Networks[network]; const isCustomRpc = false; const selected = provider.type === network ? ( @@ -303,7 +309,7 @@ export class NetworkList extends PureComponent { selected, this.onNetworkChange, name, - color, + imageSource, i, network, isCustomRpc, diff --git a/app/components/UI/Swaps/components/TokenIcon.js b/app/components/UI/Swaps/components/TokenIcon.js index 21b7a6fc24f..da256dde30c 100644 --- a/app/components/UI/Swaps/components/TokenIcon.js +++ b/app/components/UI/Swaps/components/TokenIcon.js @@ -8,7 +8,7 @@ import { useTheme } from '../../../../util/theme'; import imageIcons from '../../../../images/image-icons'; /* eslint-disable import/no-commonjs */ -const ethLogo = require('../../../../images/eth-logo.png'); +const ethLogo = require('../../../../images/eth-logo-new.png'); /* eslint-enable import/no-commonjs */ const REGULAR_SIZE = 24; diff --git a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap index 1eac3986643..0c473e0e1d9 100644 --- a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap +++ b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap @@ -8,7 +8,7 @@ exports[`TokenIcon component should Render correctly 2`] = ` onError={[Function]} source={ Object { - "testUri": "../../../app/images/eth-logo.png", + "testUri": "../../../app/images/eth-logo-new.png", } } style={ @@ -104,7 +104,7 @@ exports[`TokenIcon component should Render correctly 6`] = ` onError={[Function]} source={ Object { - "testUri": "../../../app/images/eth-logo.png", + "testUri": "../../../app/images/eth-logo-new.png", } } style={ diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index deb7434214e..21c3223a0c6 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -307,7 +307,7 @@ const Wallet = ({ navigation }: any) => { conversionRate, currentCurrency, ), - logo: '../images/eth-logo.png', + logo: '../images/eth-logo-new.png', }, ...(tokens || []), ]; diff --git a/app/core/GasPolling/GasPolling.test.ts b/app/core/GasPolling/GasPolling.test.ts index 8e84c25cf7f..3571b6d587f 100644 --- a/app/core/GasPolling/GasPolling.test.ts +++ b/app/core/GasPolling/GasPolling.test.ts @@ -74,7 +74,7 @@ const transactionState = { selectedAsset: { address: '', isETH: true, - logo: '../images/eth-logo.png', + logo: '../images/eth-logo-new.png', name: 'Ether', symbol: 'ETH', }, diff --git a/app/images/eth-logo-new.png b/app/images/eth-logo-new.png new file mode 100644 index 0000000000000000000000000000000000000000..9089e131d2dbcd59c5db7e5531cf1e706147904b GIT binary patch literal 2854 zcmV+>3)%FEP)TGUb6vV=;YqE;iBinQ$kX!p{$IPlb`2Db9Ff=Ss!pGv2` zj(AV(sg!C`rw%FUw6b+nB4|h*(n`XB5kg8r$p84Bv)}hR*ErX{_H}&EN%N5+JN9*A zfA8=AJ@;DB_kd*Tb_+SNcA-_9h{^R@QyV{=5F`>rI+4GvCK}?KalXSp>avtIgN{^} zi6#fqsi((j1rW3XBvXTln0lmB>r6Wd+3pu=xv=>C5LU-ypQKZd_md^8iRcNZv~xfR zYA5pyv9up;E~7?|U}>fec_w#H@HSC8X%2 z?+-(QWGZSgBNq{FE7jV!`EK`0Szb9{nL6flMdjv^ikM8@ZI?>-^4%6%6^PX7p2ts6 zl|&+cGTy7-}X!`H8z;2PVs12;BI!mxC=1 z*}67vX9y*w=yhkFLC;eeI4&etVu;`+lOT{}wr!~)OC*F73k+o?MRe5{w5$TO8enDl z$s~x@t#!2Fk*fNI&}0+MaM8B5C%Xp=@O zO1SaG-2LnYEimaD2NGAK*!&{*T3UFIV24xRs2Kf&a6%j?X zSb?F!n?GMm%K^3HkDsd}TL?!l$rD9IR6@K>mO(PvQH2oCKT~6^c0`iNFL???bdQbL zIU1M4b>oi8-Pq607wjb&9DMWc%fct!jBJ&?yVq2dOoDiHy|k8Ogp{3LrY;mwialhJ za-B(~Aj#MmP*65gyY1i-5f5d1#1^uMf51>JNHVr+N1i*nCEyYf7105*2$D$@ZtUl& z&2cM9hI2<7TzZL!HeETYWD0Y!omYuj=nCQ~<27q*Zk$_*H*#nymAWSj#z5v5aW zt+?LWQWI*xP%Rix7*8@%?#YR$g}n~t;Opxu+J5|jqN_s-bail^X6JN8uQfdd=Uvni z&;6#U)y6`*N(KxN@A~bGdkcoYbKIZi$Fh^{#Jci-2Lp;NPu9@4Hx|eciJXYSf+dC- zFk~{ChVLo5F`S|Kh2mD{iMgg;&{NIG#PO1UbLWe7bc1)se5Nuh4vYKWlc});sRiT5 zPmoN4fOxm=I&|}nLm4OBhn=3)^lx_aAr`2G%bN9Une}yr?&J+9zP>U`CRp5sQ!V_j z$Rt72>X9*r9!x5>EEQTjKZuhMv%W6F6^EoB{-CBnObjS)3@^}~QHP=?E8;>dK?ZA6 zxShWy^vAm>fSAK`598(&O>wu>@o4=~9eqAJO;Hmk3wQ$txa{bCMR)i^Mk!t~yttWI zCl?b@JU0dZ=Gr_(iE$B;On&idgKKq_#Ji}#&ccruH(%fa{M-L7M4Dubi-^ql)s=bc z;`w10iNrEHAMFmc5))DA;s-(^CZXi8 ziBg*GKhQKWWhM@1MXaGAG8^YzE?=EzF|$^QF2V-)IUY`M38f51lSTikH+D|kj^}FK zfo`FF~WSz07rRxJv#2tr+rg= zyHq$>WY~E5#n&@sw?P^89sdu1+-q#eu5+B4$?n7`$(7hU_Pp~Z-!zi}FoD4_*5ZZOLLhX`1=1cfzKiCO-yRKJBH5Z;o~(tnBD$tohx9BKW+cTS^i%X zDLE0__WXLcAYo((fgX2wG(+t>8*;P68GZ~y27nKR0vuu@={&l=c>5G2BErH5w~aHt zF&V7e@})uC!yNJXEw!$A;PTZu{@c;_%{av^Ct~YQ zj|>Yfjh+#8?Pi8uy51ENV2p*95H1c&^~6Lr{pQ+&E9!b4D0pIeBEog=v3Ljte%F1u zwVrnI(Z2i`i@$wgV2+HS)ww0R+GKL^Ko5QVtqnOLJ+g^sn!X%Z8uYz024KN`|D1NM z&R?=XJXqZ}p49lQ7q0&Kg=xMuQ{!lo2bKgMj*Ht&#YGQT+X83M?awY9y?HKnFlKR~!hg#}KZeNa#{ z=7S;b`-dNMkICc%*To8lU+!&mM_WO_jbVjm5$*{?1-r28*vKh<(8|&}lsiX5EF#2^1Wl`FcV0MjCrN2XE2$(^xZv|-iCsrr=uO8Dr^ zL@t3;1L7cxYU2n>=bx!xR7Bg>h?sY-uliGPF<3C|#vtDcLEz}bwl$vr$4sdB7z8ZP zWIdR}XN4e8L|7w7PisjAeA+)W>&E8lK@VhPzZHU#Lp&uihsjdi2oM)PI&;6G z*PsYTTW15I^@qi-T2fL(yg+GX)vb}K&wMr+dV3VqHsT;#;iaIfm6RPPr}09|ZjJbu zKe!D7ZJoi@UO8C6K`qrALC=Re*q}tb3-N#~g0`?!*<&jXE>iN!ccDZ~r#6i9p6DQp z@eyTDsrZOE7_{^*TJ#BS?@C^vE%ae8H$FPf>tvCjwh-lvl{l!>snb18sdC#@bnFmi zkJ3&TSp;QIep`qswG}rO;%x89=EGq&t@r?J`n}C*n?h#Go@`r)ICx>e%tFn4xLfN} zV)9_v?U%itBl+&_q!cl0=S1VwwZU5%0Uy29ULFmp@U%DQb*O@W^rm1d%PzvN;it61R{R;*Io)O#`${B3s$h z+q0>g4NiRWQ=@$@2|pi+wcY$okhT4=6`hQ$e?|7t_Hd{#t6KPcKMqgy0S(v-55yM_e6 zvleo{PK5klB{GOxKJGifE_z*)93tghrugiNeU-@n0eR-@U;qIQr~m)}07*qoM6N<$ Ef;#MXGynhq literal 0 HcmV?d00001 diff --git a/app/images/goerli-logo-dark.png b/app/images/goerli-logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..65ef67c27cc48c2b2e6de6310a88dff0a6a35480 GIT binary patch literal 1720 zcmV;p21ogcP)?^J0BWS~SEj#`3rEI6>NJKbr-h(HXG&@AvL^ywcT6|`zt zF)7&g3@IZ8&1h4A`FNuXdu_usdL#6<3-OQ!@JK8j69|V2(g5N4h$th5X@e^nX z)Lm=vWS}g83UK2*hq^bH;OHhpdBVk@JRx`rNMjDIBRtdPCT;;7QGoeiYXF>Bv>NOb z0}>_A6eyTJkA4!ZNe1oqIqPk7|w;kEQw!M+95+wq-0CDD7z@LXHZ zI>_b}R{nAjBOWVgJGgH2A!gw(oiQ+Use=TUNOY7=LRk^>f7*Dqyo&{T)lAU}#`w*F z1x#+OA|~@^!SDCIg`Pz9)%X0mjnUME44dRDprvODERc<53)uAh&t$bGnJi=q#Z z;wm&V-gn%c>wZ|!r+?&J)1LRuZ}toP3=4Y0C+spg#?;y#v>^m_tT7@A-hUMu86-!? zBj)Gn%N!oQ$W{|9~du(tAg8b{@foMF~st4`_l-ea;u7IlMKj5` zaE5Aw%f14MJ3TiNa=+!p{lFqT+LdCyw#JBUyF1Gcx5YOQ!@7XZ`TyZCT!U?e)xd%0KTL3`X4|~WGJ}>uZNPD^1^rK;0`g|kQd8l6sh#8{i*;+TdxBEr zSaO59!d}0`9zmvJWkR8GnnL3(G$W3hrjS>5({#Aa(vzEubZc|%7p6SQ8tS}O>9$b) zDl(yqq|KU-rPLiB`4GZ0p&NE&N&`Hf(AX&6-(1P;4^vggGM_@)chQg`l0qHS*VfR< zyVX2Z`=Kc*=X9E-wnH_%M;_dLh-&)Xjbhda%IzEd~ypp3!@FMXYp60!W5?3QABi|5E?#k6H z^(|)``tWoq@(vRw9QXMAh5h66LL@ymDyr3s$LB$-RmxeStEsq;&;JJn@rY;5-Lx$L O0000 za%e+iRgNoi^r6s_-~H61f!rI)Js$7M`4K!E^{=Y{6{1i9Vl)k2mtWt&xS{hB$Y&t` z>7Ix&K*^LQKzCiY@E9)1U+l_h3)K)Pjh^;!P?l#D7Qm9>X@=rqeF>4hqCylBU~5BP z(m)UP3{PV`V&|sX!-6a%fa%yYz6LwDdJu>uXBY7X4|Bp+7vm36AL9ud+u<&^tV!&PYh~Z>lS^K0dPmVmw{GgXLhie?wiI<-wT*5Z5}CCs(mt3`B&p(;k}I z2bh*$KI2I{^4f_^6hK=3GM2~fv*<+qRxY_A5RLJDGy#n*=@>I@$rK4f{F|?UpAM#f@97m_`;+nOKimSo zzlAc0y`aaFhasSG8IGax?|n3Wy+q?flnHf%5x}hBIgb4&p8__wfybXnUN7DFkXB^# zsRwl65E{>x=W&#Zj1T$GEZRl_Xsp2zdB(H9sLXicnH6hz(Yn142hiQKjQ{P|u+yv< zUlw$I5C6NF>Kfxe95?=c09^YFc6bzRhJX_RHb0PF-%noq_ImP~L*q${sjv?V zti3iK^NjUtpE@(1_;YWJEK1sV!j`{(4^FWrKo&2&1m{>2fa80bSI>w2f=ehs01F&^ z7v}b99E%WmrYyx0%?V|c*K-=Ry;5Hl*i;zx}c$QWym@AR7Uq3QTMH zKpuHB!@PCF0vyLxr=$A8NnnehZ@4fSmL zV)lZF0;?>W6bdI|)48l0nF1I$X;LU?T+OQ_&s`FW>gsBd8x(@Qeol>GczX)6dAchK zzJp~%VOLuI;^aqWU`dOm??|V2ZrVdLtqO|^orxz8OMx=w@>7i`GcSvaD|Hx8s5e?Z z{b_DL%3S*bCWX}4LOqAb6ZzlXmzLj}=SxLinyO1hDO;T{R&Flx($qo%#F)C`u)NNP z8&1p^KVg`gMj-)6+{E_ug`P0N|{&a2UWj5{{T+U VwR@gDB_;p>002ovPDHLkV1iqdX^{W` literal 0 HcmV?d00001 diff --git a/app/images/rinkeby-logo-dark.png b/app/images/rinkeby-logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2ea8cb9daa75a209d1f56fae169b8c60eef256 GIT binary patch literal 1604 zcmV-K2D|x*P)=yJE^o1!9W+X&>kzD+Htti?P+9G^1$&gfwsu z2#}M2vxj!aLbHNsN&pAzW0Uk_Qeh3;2UjCHhn5%Mf?P-dyXkimakDr_Z!L%h*&{nP zauIEi&>%eP9cFO@qBD4gCuHMbb#w|Eq#+GBSQ~vpuBzdSgoyx5btYG4>hE7C`L(e* zT5l6@2_UyQ-#xVau%5nk3E<$__yz?pgK)XXbS0NZYIUk<19;{y;ouo7|7>ioR;P0X z@M3*@`m(6K9|B>5wD-xmeX4!I)}BtY&<-ek)HS@~I~HF7u%BmF*TMiNXmS)M-1dtT ze3tw< z`fm#~Q__MIe>hzNQiy425u*DIFkZa}@wdNp4{@a*bRwjfEWLLHu$LaUAR=pD_TGZ{ zTiS)tx3_Lt?)CUZ7&rkDLR`5H9dCXPIuXja&u7;LNCCs38T+qq>G-aqUBs`G`rie! z|DLCvHGvOZHbF|dV+}B6vh@cjCO|W2=K&9QD%+`z2kHFt)LsAOnEiL5pwB2KIABno6}bexVr~Et_ga2*_CY6P`w|j`NI(Q& zw>-CsHO&X1rIj(p7kl9zR1lB+X%D_0x4LBF5} z5C3*OgJu#Ygaps-*%__|+WT&osJ0>jggtm7D^ZFZgg_}2=9w=3`Ax<5=D;WJwm3`>Fee3K7D9$q z{p`j_m{%cwb4L5U0M9j1Y)hroHnafNg0m{mULt<;F@yyoZc0jbZ$gV;d%*s_99{-7 z3W~zOH06dg9F>6<;rjbuK6B6In6G_6$NQigRj{{Y=SU_SmM8Fo4WplDh>=m0y&Csv zWczsR?kzEeY7trp+X6q&(4o;N(k?i|XIB6yjqjnwRP_vegOtvJUk@F(vdk4gPjV^4 zJ`q|>HP67B5GaVxdy>ahr!P}uN`3_qz|S+VEeO&xkouoW^i%)^WzR-I3qtS=IVd`- zr4OE4A^Fwul#tl63{e=+B+qmwS5l?Vp6qZ`v_KoQ_NA@n%bPp`;EbtI5U|CG4!ri= zxTn0$uj9e`7#B%c&`!|ocimYTINR)7BP49+VLL4a+Vt!SM~(V@=PLkKlXhAPLdwTy zRlh{lLP@TxQvi{O4Vr6$f^--=J zx@`5r>8|ARXsu3lJupFm!)=o0g&#`^8=m=k2G7*}#0aU<1cHdVVMMZ9v$fQ|oQ58m z=}s=CxbQfNE|Ogx-QN53YI6BeBY$_Hv?y0rpdr%`jm$FAk)hb-qiU}1JetZ2b6qj1 z&~&Q=;RZP5CcpyYLRi?-4F2C-w?O9P)_j?fns|;`j!8JGf zG=j!WwkM#0m(cU@^!m6Z5hn!Ta1+A7lh-W$%0&mnBHy}$fYDwX!kSSuj6p8O;(8?V zAPGuI`8_`W90lem;L6gcZH2gH#8js9oyHAYBm56l!)M{s-GYw*0000uc(mrAE;AtNWvjKam7KR*CY-(RH8~skw9t_2abT0-maZ`<8{3LF|%ubq_w;2$yV>}`!zdn6bwV- zpQeD}G!%^j>NJ!}fso|$lz*TqL7@yH{?_|^z6ToGQ#*4d7(~GUG=7r>R+j;1szK29 zDe+N&)*EVPp@1IKGX+pOm&A)S!vfE`PuL7>9cl>`b*ERB(X|3B3GT;r6}1YjHo8?r zR|=qsl^Hm+WKHl!T#Hb*vm5A;jub%SxfBPgHU8@~;z0@88SBIv9JGo1)Q5C2M#2jz zIJ!s^%d=>cwk&|&$>l)0?f=9s>=Ikq>!C-79Y~Y8H9%+3AFQjb`ROnNLn(kJZxwmy zDEbLPL2b{EwHnqk26#`t&x98^M$zQ*TB~6vDu5HTgUE1T%$+o#sSCK#UePKB z0m2-+L!c~y3UKqhq`K$|oL*DrI^ld!oe=B-j@}(0;f17IxCPLl01g}(NMSK(NKgzY zlzcAWn!Y^!K~#W88Bd)vsxVisUGQ7L5N0sA!V5fe{m*9wM;0)Efv*`pD@Yt&c67R@ z!r#t@AN$*%d>jDZ{{sB<2MklQf+abrcY#Gj#o1SYx8DFhd)GToy$rKBGe`fD5BD8j zTzvoBYc`aStjb)q0HrdB6C*5qb{s}=U)t7UfKnG>n*2Rr^Iqe15mwA!j(WrriOYk7)7aM!kPt0M1M0Ns7b*y5>WI!`inEeuP{t10b7hT5i|(#{ZGDy z4eTi(EHWv|edkS(ED#?B{CMB17R}EFeQ*{~CD=fOoBkTOkq`d;?tSaRGbL7lF2e@3 zQuyuz;LF{{-z5vkwAdy@WqTG-PrleVF?#6*;G-!E?*i%|Jd{G{A)?@`LZit`mbf+r zYdEwg7O;n?ka!^Zz8-OdD`{AvGE{VU#iW3!c*1ev>Nkxw|I`KG@&y=2seQ*=E{Y%y z)Of^!@kT(=^Fi7xkxfA;iR{&vqB@T#kysFv8Lk-&iV6k!@)V$H6H(JFGss&a`R+AZ zyPajO0^ANv>Ai~@TbY6Id#&sA`MLn<&k+ZLGJ`u^PNQd#I%=YNu7KghYAme4d}d%7 z)t%aje|32B<`U~9x)mZT=<8~0VX1cJUmcE%6_|p3A3CvKf61o+waF`{8ljsE`wsT~ z<83m*Jwg-9yD@8p?H-Bz+w*6SUk>gOB4>KQT;Y&qx)k(7paNVo86;OA{c*ES@}`ZF zUSA!>qc-RYj{4gRP5T{N_6pO)t#zI?y@!5;XkBf8cw*CZ$Y$vkF4SYRh8PB-T^9aQ zt6`xwOsK8wdA?!C+fuCKJ^7Zx3!!(6@C`HKPlz|f!mqY6`+C~iGUJwzgt?feNKSP+ z%fipK^GDrmnv!BEh3Ju|UH}cf-^r$_?zYX1FK74;ixDc~(9Q8Dq`6jfzj4y-FRsk; ztt3_2>M}j#c=QFHCRXeFemkLeTW7sytF%FjkS2z5uLQ4Qa`SYCZ-221Wl;6A#2 Date: Wed, 28 Sep 2022 16:07:47 -0700 Subject: [PATCH 04/91] [PS] Fix component styles (#5047) * Add new logos * Update styles. Add missing exports. --- .../SheetActions/SheetActions.tsx | 43 +++++++++--------- .../SheetActions/SheetActions.types.ts | 4 ++ .../components/Avatars/index.ts | 1 + .../components/Badges/Badge/index.ts | 1 + .../BadgeNetwork/BadgeNetwork.types.ts | 2 +- .../Buttons/ButtonBase/ButtonBase.styles.ts | 1 + .../__snapshots__/ButtonBase.test.tsx.snap | 1 + .../components/Cards/Card/Card.tsx | 6 +-- .../components/Cards/Card/Card.types.ts | 4 +- .../components/Cards/Card/README.md | 2 +- .../Card/__snapshots__/Card.test.tsx.snap | 2 +- .../Cell/variants/CellDisplay/CellDisplay.tsx | 3 +- .../Cells/Cell/variants/CellDisplay/README.md | 16 ++----- .../__snapshots__/CellDisplay.test.tsx.snap | 1 + .../CellMultiselect/CellMultiselect.tsx | 2 + .../CellMultiselect.test.tsx.snap | 4 ++ .../PickerNetwork/PickerNetwork.styles.ts | 1 + .../PickerNetwork/PickerNetwork.types.ts | 4 +- .../Pickers/PickerNetwork/README.md | 4 +- .../__snapshots__/PickerNetwork.test.tsx.snap | 1 + .../Sheet/SheetBottom/SheetBottom.tsx | 15 ++++--- .../components/Tags/TagUrl/TagUrl.styles.ts | 5 +++ .../components/Tags/TagUrl/TagUrl.tsx | 13 +++++- .../components/Tags/TagUrl/TagUrl.types.ts | 7 +++ .../TagUrl/__snapshots__/TagUrl.test.tsx.snap | 2 + .../components/Toast/Toast.styles.ts | 1 + .../components/Toast/Toast.tsx | 45 ++++++++++++------- .../components/Toast/index.ts | 1 + 28 files changed, 123 insertions(+), 69 deletions(-) diff --git a/app/component-library/components-temp/SheetActions/SheetActions.tsx b/app/component-library/components-temp/SheetActions/SheetActions.tsx index 10e5cc7e698..defc760c0b4 100644 --- a/app/component-library/components-temp/SheetActions/SheetActions.tsx +++ b/app/component-library/components-temp/SheetActions/SheetActions.tsx @@ -17,26 +17,29 @@ const SheetActions = ({ actions }: SheetActionsProps) => { const renderActions = useCallback( () => - actions.map(({ label, onPress, testID, isLoading, disabled }, index) => { - const key = `${label}-${index}`; - return ( - - {actions.length > 1 && } - - - {isLoading && } - - - ); - }), + actions.map( + ({ label, onPress, testID, isLoading, disabled, variant }, index) => { + const key = `${label}-${index}`; + return ( + + {actions.length > 1 && } + + + {isLoading && } + + + ); + }, + ), [actions, styles.separator], ); diff --git a/app/component-library/components-temp/SheetActions/SheetActions.types.ts b/app/component-library/components-temp/SheetActions/SheetActions.types.ts index 967d2eabd85..6bfa6025183 100644 --- a/app/component-library/components-temp/SheetActions/SheetActions.types.ts +++ b/app/component-library/components-temp/SheetActions/SheetActions.types.ts @@ -1,3 +1,6 @@ +// External dependencies. +import { ButtonTertiaryVariant } from '../../../component-library/components/Buttons/ButtonTertiary'; + /** * Sheet action options. */ @@ -7,6 +10,7 @@ export interface Action { testID?: string; disabled?: boolean; isLoading?: boolean; + variant?: ButtonTertiaryVariant; } /** diff --git a/app/component-library/components/Avatars/index.ts b/app/component-library/components/Avatars/index.ts index ea51b745217..8ec7c20606f 100644 --- a/app/component-library/components/Avatars/index.ts +++ b/app/component-library/components/Avatars/index.ts @@ -1 +1,2 @@ export { default } from './Avatar'; +export { AvatarVariants } from './Avatar.types'; diff --git a/app/component-library/components/Badges/Badge/index.ts b/app/component-library/components/Badges/Badge/index.ts index 593258f53a1..42e97646a95 100644 --- a/app/component-library/components/Badges/Badge/index.ts +++ b/app/component-library/components/Badges/Badge/index.ts @@ -1 +1,2 @@ export { default } from './Badge'; +export { BadgeVariants } from './Badge.types'; diff --git a/app/component-library/components/Badges/Badge/variants/BadgeNetwork/BadgeNetwork.types.ts b/app/component-library/components/Badges/Badge/variants/BadgeNetwork/BadgeNetwork.types.ts index a4b30694e54..39b0b86d5bc 100644 --- a/app/component-library/components/Badges/Badge/variants/BadgeNetwork/BadgeNetwork.types.ts +++ b/app/component-library/components/Badges/Badge/variants/BadgeNetwork/BadgeNetwork.types.ts @@ -28,7 +28,7 @@ export interface BadgeNetworkProps extends Omit { /** * Image of the network from either a local or remote source. */ - imageSource: ImageSourcePropType; + imageSource?: ImageSourcePropType; /** * Enum that represents the position of the network badge. * @defaults TopRight diff --git a/app/component-library/components/Buttons/ButtonBase/ButtonBase.styles.ts b/app/component-library/components/Buttons/ButtonBase/ButtonBase.styles.ts index 78e60021da0..d483e7cd8d5 100644 --- a/app/component-library/components/Buttons/ButtonBase/ButtonBase.styles.ts +++ b/app/component-library/components/Buttons/ButtonBase/ButtonBase.styles.ts @@ -31,6 +31,7 @@ const styleSheet = (params: { alignItems: 'center', justifyContent: 'center', borderRadius: sizeAsNum / 2, + paddingHorizontal: 16, } as ViewStyle, style, ) as ViewStyle, diff --git a/app/component-library/components/Buttons/ButtonBase/__snapshots__/ButtonBase.test.tsx.snap b/app/component-library/components/Buttons/ButtonBase/__snapshots__/ButtonBase.test.tsx.snap index 7eca120da97..b5fd662e715 100644 --- a/app/component-library/components/Buttons/ButtonBase/__snapshots__/ButtonBase.test.tsx.snap +++ b/app/component-library/components/Buttons/ButtonBase/__snapshots__/ButtonBase.test.tsx.snap @@ -12,6 +12,7 @@ exports[`ButtonBase should render correctly 1`] = ` "flexDirection": "row", "height": 40, "justifyContent": "center", + "paddingHorizontal": 16, } } > diff --git a/app/component-library/components/Cards/Card/Card.tsx b/app/component-library/components/Cards/Card/Card.tsx index f9a955ab1c3..698b2b5254e 100644 --- a/app/component-library/components/Cards/Card/Card.tsx +++ b/app/component-library/components/Cards/Card/Card.tsx @@ -2,7 +2,7 @@ // Third party dependencies. import React from 'react'; -import { View } from 'react-native'; +import { TouchableOpacity } from 'react-native'; // External dependencies. import { useStyles } from '../../../hooks'; @@ -15,9 +15,9 @@ const Card: React.FC = ({ style, children, ...props }) => { const { styles } = useStyles(styleSheet, { style }); return ( - + {children} - + ); }; diff --git a/app/component-library/components/Cards/Card/Card.types.ts b/app/component-library/components/Cards/Card/Card.types.ts index b2590017861..841dbe0899f 100644 --- a/app/component-library/components/Cards/Card/Card.types.ts +++ b/app/component-library/components/Cards/Card/Card.types.ts @@ -1,10 +1,10 @@ // Third party dependencies. -import { ViewProps } from 'react-native'; +import { TouchableOpacityProps } from 'react-native'; /** * Card component props. */ -export interface CardProps extends ViewProps { +export interface CardProps extends TouchableOpacityProps { /** * Content to wrap to display. */ diff --git a/app/component-library/components/Cards/Card/README.md b/app/component-library/components/Cards/Card/README.md index c41c5abd2c2..5472e4c253f 100644 --- a/app/component-library/components/Cards/Card/README.md +++ b/app/component-library/components/Cards/Card/README.md @@ -4,7 +4,7 @@ Card is a wrapper component typically used for displaying Card Content. ## Props -This component extends React Native's [View](https://reactnative.dev/docs/view) component. +This component extends React Native's [TouchableOpacityProps](https://reactnative.dev/docs/touchableopacity) component. ### `children` diff --git a/app/component-library/components/Cards/Card/__snapshots__/Card.test.tsx.snap b/app/component-library/components/Cards/Card/__snapshots__/Card.test.tsx.snap index ff0e960c1b8..dffa904dbf4 100644 --- a/app/component-library/components/Cards/Card/__snapshots__/Card.test.tsx.snap +++ b/app/component-library/components/Cards/Card/__snapshots__/Card.test.tsx.snap @@ -13,5 +13,5 @@ exports[`Card - Snapshot should render correctly 1`] = ` } > - + `; diff --git a/app/component-library/components/Cells/Cell/variants/CellDisplay/CellDisplay.tsx b/app/component-library/components/Cells/Cell/variants/CellDisplay/CellDisplay.tsx index 595c14ad582..f668a4ea369 100644 --- a/app/component-library/components/Cells/Cell/variants/CellDisplay/CellDisplay.tsx +++ b/app/component-library/components/Cells/Cell/variants/CellDisplay/CellDisplay.tsx @@ -21,11 +21,12 @@ const CellDisplay = ({ tertiaryText, tagLabel, children, + ...props }: CellDisplayProps) => { const { styles } = useStyles(styleSheet, { style }); return ( - + TYPE | REQUIRED | | :-------------------------------------------------- | :------------------------------------------------------ | -| [CellVariants](../../Cell.types.ts#L9) | No | +| [CellVariants](../../Cell.types.ts#L9) | No | ### `avatarProps` Props for the [Avatar](../../../../Avatars/Avatar.tsx) component (with the exception of size). Avatar size is restricted to size Md(32x32) for Cells -| TYPE | REQUIRED | -| :-------------------------------------------------- | :------------------------------------------------------ | -| [AvatarProps](../../../../Avatars/Avatar.types.ts#L19) | Yes | +| TYPE | REQUIRED | +| :----------------------------------------------------- | :------------------------------------------------------ | +| [AvatarProps](../../../../Avatars/Avatar.types.ts#L19) | Yes | ### `title` @@ -54,14 +54,6 @@ Optional label (using [Tag](../../../../Tags/Tag/Tag.tsx) component) below the t | :-------------------------------------------------- | :------------------------------------------------------ | | string | No | -### `children` - -Optional accessory that can be inserted on the right of Cell. - -| TYPE | REQUIRED | -| :-------------------------------------------------- | :------------------------------------------------------ | -| ReactNode | Yes | - ## Usage ```javascript diff --git a/app/component-library/components/Cells/Cell/variants/CellDisplay/__snapshots__/CellDisplay.test.tsx.snap b/app/component-library/components/Cells/Cell/variants/CellDisplay/__snapshots__/CellDisplay.test.tsx.snap index 7045a72d473..15abfff902f 100644 --- a/app/component-library/components/Cells/Cell/variants/CellDisplay/__snapshots__/CellDisplay.test.tsx.snap +++ b/app/component-library/components/Cells/Cell/variants/CellDisplay/__snapshots__/CellDisplay.test.tsx.snap @@ -4,6 +4,7 @@ exports[`CellDisplay - Snapshot should render default settings correctly 1`] = ` { const { styles } = useStyles(styleSheet, { style }); @@ -30,6 +31,7 @@ const CellMultiselect = ({ isSelected={isSelected} style={styles.base} testID={CELL_MULTI_SELECT_TEST_ID} + {...props} > TYPE | REQUIRED | | :-------------------------------------------------------------------- | :------------------------------------------------------ | diff --git a/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap b/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap index c6cc9711628..e08a3831d45 100644 --- a/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap +++ b/app/component-library/components/Pickers/PickerNetwork/__snapshots__/PickerNetwork.test.tsx.snap @@ -6,6 +6,7 @@ exports[`PickerNetwork should render correctly 1`] = ` style={ Object { "alignItems": "center", + "alignSelf": "center", "backgroundColor": "#F2F4F6", "borderRadius": 16, "flexDirection": "row", diff --git a/app/component-library/components/Sheet/SheetBottom/SheetBottom.tsx b/app/component-library/components/Sheet/SheetBottom/SheetBottom.tsx index 29e8d1612e4..3af564997d3 100644 --- a/app/component-library/components/Sheet/SheetBottom/SheetBottom.tsx +++ b/app/component-library/components/Sheet/SheetBottom/SheetBottom.tsx @@ -11,7 +11,7 @@ import React, { useRef, } from 'react'; import { - LayoutAnimation, + // LayoutAnimation, LayoutChangeEvent, TouchableOpacity, useWindowDimensions, @@ -166,11 +166,14 @@ const SheetBottom = forwardRef( [hide], ); - useEffect(() => { - // Automatically handles animation when content changes - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - return () => debouncedHide.cancel(); - }, [children, debouncedHide]); + useEffect( + () => + // Automatically handles animation when content changes + // Disable for now since network switches causes the screen to hang with this on. + // LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + debouncedHide.cancel(), + [children, debouncedHide], + ); const updateSheetHeight = (e: LayoutChangeEvent) => { const { height } = e.nativeEvent.layout; diff --git a/app/component-library/components/Tags/TagUrl/TagUrl.styles.ts b/app/component-library/components/Tags/TagUrl/TagUrl.styles.ts index 12b1ebf7f26..661ba237054 100644 --- a/app/component-library/components/Tags/TagUrl/TagUrl.styles.ts +++ b/app/component-library/components/Tags/TagUrl/TagUrl.styles.ts @@ -31,16 +31,21 @@ const styleSheet = (params: { theme: Theme; vars: TagUrlStyleSheetVars }) => { paddingRight: 16, flexDirection: 'row', alignItems: 'center', + alignSelf: 'center', } as ViewStyle, style, ) as ViewStyle, label: { marginLeft: 8, color: colors.text.alternative, + flexShrink: 1, }, cta: { marginLeft: 16, }, + icon: { + marginLeft: 8, + }, }); }; diff --git a/app/component-library/components/Tags/TagUrl/TagUrl.tsx b/app/component-library/components/Tags/TagUrl/TagUrl.tsx index 0e656d89e1b..caa7b5806ba 100644 --- a/app/component-library/components/Tags/TagUrl/TagUrl.tsx +++ b/app/component-library/components/Tags/TagUrl/TagUrl.tsx @@ -9,17 +9,28 @@ import { AvatarBaseSize } from '../../Avatars/AvatarBase'; import AvatarFavicon from '../../Avatars/AvatarFavicon'; import ButtonLink from '../../Buttons/ButtonLink'; import Text, { TextVariant } from '../../Texts/Text'; +import Icon, { IconSize } from '../../Icon'; import { useStyles } from '../../../hooks'; // Internal dependencies. import styleSheet from './TagUrl.styles'; import { TagUrlProps } from './TagUrl.types'; -const TagUrl = ({ imageSource, label, cta, style, ...props }: TagUrlProps) => { +const TagUrl = ({ + imageSource, + label, + cta, + style, + iconName, + ...props +}: TagUrlProps) => { const { styles } = useStyles(styleSheet, { style }); return ( + {iconName ? ( + + ) : null} {label} diff --git a/app/component-library/components/Tags/TagUrl/TagUrl.types.ts b/app/component-library/components/Tags/TagUrl/TagUrl.types.ts index 1b8fd2c50bd..ff95236a46e 100644 --- a/app/component-library/components/Tags/TagUrl/TagUrl.types.ts +++ b/app/component-library/components/Tags/TagUrl/TagUrl.types.ts @@ -1,6 +1,9 @@ // Third party dependencies. import { ImageSourcePropType, ViewProps } from 'react-native'; +// External dependencies. +import { IconName } from '../../Icon'; + interface TagUrlCta { label: string; onPress: () => void; @@ -22,6 +25,10 @@ export interface TagUrlProps extends ViewProps { * Object that contains the label and callback of the call to action. */ cta?: TagUrlCta; + /** + * Optional icon to render to the left of the label. + */ + iconName?: IconName; } /** diff --git a/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap b/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap index 74b9928773e..544a93bedbd 100644 --- a/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap +++ b/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap @@ -5,6 +5,7 @@ exports[`TagUrl should render correctly 1`] = ` style={ Object { "alignItems": "center", + "alignSelf": "center", "backgroundColor": "#FFFFFF", "borderColor": "#BBC0C5", "borderRadius": 99, @@ -28,6 +29,7 @@ exports[`TagUrl should render correctly 1`] = ` style={ Object { "color": "#535A61", + "flexShrink": 1, "marginLeft": 8, } } diff --git a/app/component-library/components/Toast/Toast.styles.ts b/app/component-library/components/Toast/Toast.styles.ts index 3d3c73ece2c..38ebc567dcc 100644 --- a/app/component-library/components/Toast/Toast.styles.ts +++ b/app/component-library/components/Toast/Toast.styles.ts @@ -27,6 +27,7 @@ const styleSheet = StyleSheet.create({ marginRight: 8, }, labelsContainer: { + flex: 1, justifyContent: 'center', }, label: { diff --git a/app/component-library/components/Toast/Toast.tsx b/app/component-library/components/Toast/Toast.tsx index ededd5a441d..3907d5c9d16 100644 --- a/app/component-library/components/Toast/Toast.tsx +++ b/app/component-library/components/Toast/Toast.tsx @@ -15,6 +15,7 @@ import { ViewStyle, } from 'react-native'; import Animated, { + cancelAnimation, runOnJS, useAnimatedStyle, useSharedValue, @@ -39,8 +40,9 @@ import { ToastVariant, } from './Toast.types'; import styles from './Toast.styles'; +import { useSelector } from 'react-redux'; -const visibilityDuration = 2500; +const visibilityDuration = 2750; const animationDuration = 250; const bottomPadding = 16; const screenHeight = Dimensions.get('window').height; @@ -60,12 +62,22 @@ const Toast = forwardRef((_, ref: React.ForwardedRef) => { /* eslint-disable-next-line */ [], ); + const accountAvatarType = useSelector((state: any) => + state.settings.useBlockieIcon + ? AvatarAccountType.Blockies + : AvatarAccountType.JazzIcon, + ); const showToast = (options: ToastOptions) => { + let timeoutDuration = 0; if (toastOptions) { - return; + // Reset animation. + cancelAnimation(translateYProgress); + timeoutDuration = 100; } - setToastOptions(options); + setTimeout(() => { + setToastOptions(options); + }, timeoutDuration); }; useImperativeHandle(ref, () => ({ @@ -83,15 +95,16 @@ const Toast = forwardRef((_, ref: React.ForwardedRef) => { translateYProgress.value = withTiming( translateYToValue, { duration: animationDuration }, - () => - (translateYProgress.value = withDelay( + () => { + translateYProgress.value = withDelay( visibilityDuration, withTiming( height, { duration: animationDuration }, runOnJS(resetState), ), - )), + ); + }, ); } }; @@ -129,23 +142,21 @@ const Toast = forwardRef((_, ref: React.ForwardedRef) => { return ( ); } case ToastVariant.Network: { - { - const { networkImageSource } = toastOptions; - return ( - - ); - } + const { networkImageSource } = toastOptions; + return ( + + ); } } }; diff --git a/app/component-library/components/Toast/index.ts b/app/component-library/components/Toast/index.ts index 242ffd87421..299592b229c 100644 --- a/app/component-library/components/Toast/index.ts +++ b/app/component-library/components/Toast/index.ts @@ -1,3 +1,4 @@ export { default } from './Toast'; export { ToastVariant } from './Toast.types'; +export type { ToastOptions } from './Toast.types'; export { ToastContext, ToastContextWrapper } from './Toast.context'; From 92e9654e1b12555b03b123e0d93d036e6d35e833 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 28 Sep 2022 16:08:33 -0700 Subject: [PATCH 05/91] [PS] Patch controllers (#5048) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values --- index.js | 2 ++ package.json | 3 ++- patches/@metamask+controllers+30.3.0.patch | 25 +++++++++++----------- patches/react-native-crypto+2.1.2.patch | 23 ++++++++++++++++++++ yarn.lock | 12 +++++++++-- 5 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 patches/react-native-crypto+2.1.2.patch diff --git a/index.js b/index.js index 2bf5201501b..e1f3592d79d 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +// Needed to polyfill random number generation. +import 'react-native-get-random-values'; import './shim.js'; import 'react-native-gesture-handler'; diff --git a/package.json b/package.json index b241901ebe9..095885e0d87 100644 --- a/package.json +++ b/package.json @@ -202,6 +202,7 @@ "punycode": "^2.1.1", "qs": "6.7.0", "query-string": "^6.12.1", + "randomfill": "^1.0.4", "react": "17.0.2", "react-native": "0.66.3", "react-native-actionsheet": "beefe/react-native-actionsheet#107/head", @@ -225,7 +226,7 @@ "react-native-flash-message": "0.1.11", "react-native-fs": "^2.16.6", "react-native-gesture-handler": "^1.10.3", - "react-native-get-random-values": "1.8.0", + "react-native-get-random-values": "^1.8.0", "react-native-i18n": "2.0.15", "react-native-in-app-review": "^3.2.3", "react-native-inappbrowser-reborn": "^3.7.0", diff --git a/patches/@metamask+controllers+30.3.0.patch b/patches/@metamask+controllers+30.3.0.patch index 98a38ff0166..78e9b6d7047 100644 --- a/patches/@metamask+controllers+30.3.0.patch +++ b/patches/@metamask+controllers+30.3.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@metamask/controllers/dist/keyring/KeyringController.js b/node_modules/@metamask/controllers/dist/keyring/KeyringController.js -index 8b9d1ba..818792b 100644 +index 8b9d1ba..d78f8b7 100644 --- a/node_modules/@metamask/controllers/dist/keyring/KeyringController.js +++ b/node_modules/@metamask/controllers/dist/keyring/KeyringController.js @@ -119,6 +119,7 @@ class KeyringController extends BaseController_1.BaseController { @@ -10,24 +10,23 @@ index 8b9d1ba..818792b 100644 * * @returns Promise resolving to current state when the account is added. */ -@@ -129,16 +130,10 @@ class KeyringController extends BaseController_1.BaseController { - if (!primaryKeyring) { - throw new Error('No HD keyring found'); - } -- const oldAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); - yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewAccount(primaryKeyring); +@@ -134,12 +135,13 @@ class KeyringController extends BaseController_1.BaseController { const newAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); yield this.verifySeedPhrase(); this.updateIdentities(newAccounts); -- newAccounts.forEach((selectedAddress) => { -- if (!oldAccounts.includes(selectedAddress)) { ++ let addedAccountAddress = ''; + newAccounts.forEach((selectedAddress) => { + if (!oldAccounts.includes(selectedAddress)) { - this.setSelectedAddress(selectedAddress); -- } -- }); - return this.fullUpdate(); ++ addedAccountAddress = selectedAddress + } + }); +- return this.fullUpdate(); ++ return { addedAccountAddress, keyringState: this.fullUpdate() }; }); } -@@ -307,8 +302,8 @@ class KeyringController extends BaseController_1.BaseController { + /** +@@ -307,8 +309,8 @@ class KeyringController extends BaseController_1.BaseController { const accounts = yield newKeyring.getAccounts(); const allAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); this.updateIdentities(allAccounts); diff --git a/patches/react-native-crypto+2.1.2.patch b/patches/react-native-crypto+2.1.2.patch new file mode 100644 index 00000000000..513d0b6249f --- /dev/null +++ b/patches/react-native-crypto+2.1.2.patch @@ -0,0 +1,23 @@ +diff --git a/node_modules/react-native-crypto/index.js b/node_modules/react-native-crypto/index.js +index 5c15698..ae45a48 100644 +--- a/node_modules/react-native-crypto/index.js ++++ b/node_modules/react-native-crypto/index.js +@@ -1,6 +1,7 @@ + 'use strict' + + import { randomBytes } from 'react-native-randombytes' ++var rf = require('randomfill') + exports.randomBytes = exports.rng = exports.pseudoRandomBytes = exports.prng = randomBytes + + // implement window.getRandomValues(), for packages that rely on it +@@ -23,6 +24,10 @@ if (typeof window === 'object') { + } + } + ++// Export missing crypto utils. Needed by nanoid package. ++exports.randomFill = rf.randomFill ++exports.randomFillSync = rf.randomFillSync ++ + exports.createHash = exports.Hash = require('create-hash') + exports.createHmac = exports.Hmac = require('create-hmac') + diff --git a/yarn.lock b/yarn.lock index 692dc97632f..673dd41ab48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14782,13 +14782,21 @@ randombytes@2.0.3: resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec" integrity sha1-Z0yZdgkBw8QRJ3GjHlIdw0nMCew= -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.6, randombytes@^2.1.0: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.0.6, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" +randomfill@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + randomstring@^1.1.5: version "1.2.1" resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.2.1.tgz#71cd3cda24ad1b7e0b65286b3aa5c10853019349" @@ -15110,7 +15118,7 @@ react-native-gesture-handler@^1.10.3: invariant "^2.2.4" prop-types "^15.7.2" -react-native-get-random-values@1.8.0: +react-native-get-random-values@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.8.0.tgz#1cb4bd4bd3966a356e59697b8f372999fe97cb16" integrity sha512-H/zghhun0T+UIJLmig3+ZuBCvF66rdbiWUfRSNS6kv5oDSpa1ZiVyvRWtuPesQpT8dXj+Bv7WJRQOUP+5TB1sA== From 04b824ab29c48367b5763b3549e41b6d68fee2f6 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 28 Sep 2022 16:11:16 -0700 Subject: [PATCH 06/91] [PS] Organize account utils (#5049) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values * Organize account hooks and utils * Export ENS cache. Update account util to return ENS name. * Update account selector * Remove account selector styles --- app/components/Nav/Main/index.js | 50 ++++++- .../AccountSelectorList.tsx | 139 +++++++++++++----- .../AccountSelectorList.types.ts | 82 ++++------- .../UI/AccountSelectorList/hooks/index.ts | 3 - .../hooks/useAccounts/index.ts | 4 - .../hooks/useAccounts/useAccounts.types.ts | 41 ------ .../hooks/useAccounts/useAccountsTest.tsx | 65 -------- .../UI/AccountSelectorList/index.ts | 6 +- .../AccountSelector/AccountSelector.styles.ts | 7 + .../Views/AccountSelector/AccountSelector.tsx | 26 +++- .../AccountSelector/AccountSelector.types.ts | 4 +- app/components/Views/Wallet/index.tsx | 67 +++------ app/components/hooks/useAccounts/index.ts | 2 + .../hooks/useAccounts/useAccounts.ts | 18 +-- .../hooks/useAccounts/useAccounts.types.ts | 89 +++++++++++ app/util/ENSUtils.js | 2 +- app/util/accounts/index.ts | 32 ++++ app/util/address/index.js | 9 +- app/util/networks/index.js | 37 +++++ 19 files changed, 409 insertions(+), 274 deletions(-) delete mode 100644 app/components/UI/AccountSelectorList/hooks/index.ts delete mode 100644 app/components/UI/AccountSelectorList/hooks/useAccounts/index.ts delete mode 100644 app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.types.ts delete mode 100644 app/components/UI/AccountSelectorList/hooks/useAccounts/useAccountsTest.tsx create mode 100644 app/components/Views/AccountSelector/AccountSelector.styles.ts create mode 100644 app/components/hooks/useAccounts/index.ts rename app/components/{UI/AccountSelectorList => }/hooks/useAccounts/useAccounts.ts (93%) create mode 100644 app/components/hooks/useAccounts/useAccounts.types.ts create mode 100644 app/util/accounts/index.ts diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 725d3c44a15..7491423d185 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -1,4 +1,10 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React, { + useState, + useEffect, + useRef, + useCallback, + useContext, +} from 'react'; import { ActivityIndicator, @@ -10,7 +16,7 @@ import { } from 'react-native'; import NetInfo from '@react-native-community/netinfo'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import GlobalAlert from '../../UI/GlobalAlert'; import BackgroundTimer from 'react-native-background-timer'; import NotificationManager from '../../../core/NotificationManager'; @@ -51,6 +57,14 @@ import { colors as importedColors } from '../../../styles/common'; import WarningAlert from '../../../components/UI/WarningAlert'; import { KOVAN, RINKEBY, ROPSTEN } from '../../../constants/network'; import { MM_DEPRECATED_NETWORKS } from '../../../constants/urls'; +import { + getNetworkImageSource, + getNetworkNameFromProvider, +} from '../../../util/networks'; +import { + ToastContext, + ToastVariant, +} from '../../../component-library/components/Toast'; const Stack = createStackNavigator(); @@ -195,6 +209,38 @@ const Main = (props) => { if (skipCheckbox) toggleRemindLater(); }; + /** + * Current network + */ + const networkProvider = useSelector( + (state) => state.engine.backgroundState.NetworkController.provider, + ); + const prevNetworkProvider = useRef(undefined); + const { toastRef } = useContext(ToastContext); + + // Show network switch confirmation. + useEffect(() => { + if ( + prevNetworkProvider.current && + networkProvider.chainId !== prevNetworkProvider.current.chainId + ) { + const networkImage = getNetworkImageSource(networkProvider.chainId); + const networkName = getNetworkNameFromProvider(networkProvider); + toastRef?.current?.showToast({ + variant: ToastVariant.Network, + labelOptions: [ + { + label: networkName, + isBold: true, + }, + { label: strings('toast.now_active') }, + ], + networkImageSource: networkImage, + }); + } + prevNetworkProvider.current = networkProvider; + }, [networkProvider, toastRef]); + useEffect(() => { if (locale.current !== I18n.locale) { locale.current = I18n.locale; diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 892092f3fa4..2d0552726d8 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -1,6 +1,6 @@ // Third party dependencies. import React, { useCallback, useEffect, useRef } from 'react'; -import { ListRenderItem, View } from 'react-native'; +import { Alert, ListRenderItem, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { useSelector } from 'react-redux'; import { KeyringTypes } from '@metamask/controllers'; @@ -12,26 +12,29 @@ import Cell, { import { useStyles } from '../../../component-library/hooks'; import Text from '../../../component-library/components/Text'; import AvatarGroup from '../../../component-library/components/Avatars/AvatarGroup'; -import UntypedEngine from '../../../core/Engine'; import { formatAddress } from '../../../util/address'; import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; import { isDefaultAccountName } from '../../../util/ENSUtils'; import { strings } from '../../../../locales/i18n'; import { AvatarVariants } from '../../../component-library/components/Avatars/Avatar.types'; +import { Account, Assets } from '../../hooks/useAccounts'; +import UntypedEngine from '../../../core/Engine'; +import { removeAccountFromPermissions } from '../../../core/Permissions'; // Internal dependencies. -import { - Account, - AccountSelectorListProps, - Assets, -} from './AccountSelectorList.types'; +import { AccountSelectorListProps } from './AccountSelectorList.types'; import styleSheet from './AccountSelectorList.styles'; -import { useAccounts } from './hooks'; const AccountSelectorList = ({ onSelectAccount, - checkBalanceError, + accounts, + ensByAccountAddress, isLoading = false, + selectedAddresses, + isMultiSelect = false, + renderRightAccessory, + isSelectionDisabled, + isRemoveAccountEnabled = false, ...props }: AccountSelectorListProps) => { const Engine = UntypedEngine as any; @@ -42,14 +45,15 @@ const AccountSelectorList = ({ ? AvatarAccountType.Blockies : AvatarAccountType.JazzIcon, ); - const { accounts, ensByAccountAddress } = useAccounts({ - checkBalanceError, - isLoading, - }); useEffect(() => { - if (!accounts.length) return; - const account = accounts.find(({ isSelected }) => isSelected); + if (!accounts.length || isMultiSelect) return; + const selectedAddressOverride = selectedAddresses?.[0]; + const account = accounts.find(({ isSelected, address }) => + selectedAddressOverride + ? selectedAddressOverride === address + : isSelected, + ); if (account) { // Wrap in timeout to provide more time for the list to render. setTimeout(() => { @@ -60,17 +64,7 @@ const AccountSelectorList = ({ }, 0); } // eslint-disable-next-line - }, [accounts.length]); - - const onPress = useCallback( - (address: string) => { - const { PreferencesController } = Engine.context; - PreferencesController.setSelectedAddress(address); - onSelectAccount?.(address); - }, - /* eslint-disable-next-line */ - [onSelectAccount], - ); + }, [accounts.length, selectedAddresses, isMultiSelect]); const getKeyExtractor = ({ address }: Account) => address; @@ -97,23 +91,94 @@ const AccountSelectorList = ({ [styles.balancesContainer, styles.balanceLabel], ); + const onLongPress = useCallback( + ({ + address, + imported, + isSelected, + index, + }: { + address: string; + imported: boolean; + isSelected: boolean; + index: number; + }) => { + if (!imported || !isRemoveAccountEnabled) return; + Alert.alert( + strings('accounts.remove_account_title'), + strings('accounts.remove_account_message'), + [ + { + text: strings('accounts.no'), + onPress: () => false, + style: 'cancel', + }, + { + text: strings('accounts.yes_remove_it'), + onPress: async () => { + // TODO: Refactor account deletion logic to make more robust. + const { PreferencesController } = Engine.context; + const selectedAddressOverride = selectedAddresses?.[0]; + const account = accounts.find( + ({ isSelected: isAccountSelected, address: accountAddress }) => + selectedAddressOverride + ? selectedAddressOverride === accountAddress + : isAccountSelected, + ) as Account; + let nextActiveAddress = account.address; + if (isSelected) { + const nextActiveIndex = index === 0 ? 1 : index - 1; + nextActiveAddress = accounts[nextActiveIndex].address; + PreferencesController.setSelectedAddress(nextActiveAddress); + } + await Engine.context.KeyringController.removeAccount(address); + removeAccountFromPermissions(address); + onSelectAccount?.(nextActiveAddress, isSelected); + }, + }, + ], + { cancelable: false }, + ); + }, + /* eslint-disable-next-line */ + [accounts, onSelectAccount, isRemoveAccountEnabled, selectedAddresses], + ); + const renderAccountItem: ListRenderItem = useCallback( - ({ item: { name, address, assets, type, isSelected, balanceError } }) => { + ({ + item: { name, address, assets, type, isSelected, balanceError }, + index, + }) => { const shortAddress = formatAddress(address, 'short'); const tagLabel = getTagLabel(type); const ensName = ensByAccountAddress[address]; const accountName = isDefaultAccountName(name) && ensName ? ensName : name; - const isDisabled = !!balanceError || isLoading; + const isDisabled = !!balanceError || isLoading || isSelectionDisabled; + const cellVariant = isMultiSelect + ? CellVariants.Multiselect + : CellVariants.Select; + let isSelectedAccount = isSelected; + if (selectedAddresses) { + isSelectedAccount = selectedAddresses.includes(address); + } return ( { + onLongPress({ + address, + imported: type !== KeyringTypes.hd, + isSelected: isSelectedAccount, + index, + }); + }} + variant={cellVariant} + isSelected={isSelectedAccount} title={accountName} secondaryText={shortAddress} tertiaryText={balanceError} - onPress={() => onPress(address)} + onPress={() => onSelectAccount?.(address, isSelectedAccount)} avatarProps={{ variant: AvatarVariants.Account, type: accountAvatarType, @@ -122,18 +187,24 @@ const AccountSelectorList = ({ tagLabel={tagLabel} disabled={isDisabled} /* eslint-disable-next-line */ - style={{ opacity: isDisabled ? 0.5 : 1 }} + style={{ opacity: isLoading ? 0.5 : 1 }} > - {assets && renderAccountBalances(assets)} + {renderRightAccessory?.(address, accountName) || + (assets && renderAccountBalances(assets))} ); }, [ accountAvatarType, - onPress, + onSelectAccount, renderAccountBalances, ensByAccountAddress, isLoading, + selectedAddresses, + isMultiSelect, + renderRightAccessory, + isSelectionDisabled, + onLongPress, ], ); diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts index 7d2e985d5cc..2dc4decc0bb 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts @@ -1,75 +1,55 @@ // Third party dependencies. -import { KeyringTypes } from '@metamask/controllers'; +import React from 'react'; import { FlatListProps } from 'react-native'; // External dependencies. -import { AvatarGroupToken } from '../../../component-library/components/Avatars/AvatarGroup/AvatarGroup.types'; +import { Account, UseAccounts } from '../../hooks/useAccounts'; -/** - * Asset information associated with the account, which includes both the fiat balance and owned tokens. - */ -export interface Assets { - /** - * Fiat balance in string format. - */ - fiatBalance: string; - /** - * Tokens owned by this account. - */ - tokens?: AvatarGroupToken[]; +export interface SelectedAccount { + address: string; + lastUsed?: number; } +export type SelectedAccountByAddress = Record; + /** - * Account information. + * AccountSelectorList props. */ -export interface Account { - /** - * Account name. - */ - name: string; - /** - * Account address. - */ - address: string; - /** - * Asset information associated with the account, which includes both the fiat balance and owned tokens. - */ - assets?: Assets; +export interface AccountSelectorListProps + extends Partial>, + UseAccounts { /** - * Account type. + * Optional callback to trigger when account is selected. */ - type: KeyringTypes; + onSelectAccount?: (address: string, isSelected: boolean) => void; /** - * Y offset of the item. Used for scrolling purposes. + * Optional boolean that indicates if accounts are being processed in the background. The accounts will be unselectable as long as this is true. + * @default false */ - yOffset: number; + isLoading?: boolean; /** - * Boolean that indicates if the account is selected. + * Optional list of selected addresses that will be used to show selected accounts. + * Scenarios where this can be used includes temporarily showing one or more selected accounts. + * This is required for multi select to work since the list does not track selected accounts by itself. */ - isSelected: boolean; + selectedAddresses?: string[]; /** - * Optional error that indicates if the account has enough funds. Non-empty string will render the account item non-selectable. + * Optional boolean that indicates if list should be used as multi select. */ - balanceError?: string; -} - -/** - * AccountSelectorList props. - */ -export interface AccountSelectorListProps - extends Partial> { + isMultiSelect?: boolean; /** - * Optional callback to trigger when account is selected. + * Optional render function to replace the right accessory of each account element. */ - onSelectAccount?: (address: string) => void; + renderRightAccessory?: ( + accountAddress: string, + accountName: string, + ) => React.ReactNode; /** - * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. - * @param balance - The ticker balance of an account in wei and hex string format. + * Optional boolean to disable selection of the account elements. */ - checkBalanceError?: (balance: string) => string; + isSelectionDisabled?: boolean; /** - * Optional boolean that indicates if accounts are being processed in the background. The accounts will be unselectable as long as this is true. - * @default false + * Optional boolean to enable removing accounts. */ - isLoading?: boolean; + isRemoveAccountEnabled?: boolean; } diff --git a/app/components/UI/AccountSelectorList/hooks/index.ts b/app/components/UI/AccountSelectorList/hooks/index.ts deleted file mode 100644 index 632be0949b4..00000000000 --- a/app/components/UI/AccountSelectorList/hooks/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -/* eslint-disable import/prefer-default-export */ - -export { useAccounts } from './useAccounts'; diff --git a/app/components/UI/AccountSelectorList/hooks/useAccounts/index.ts b/app/components/UI/AccountSelectorList/hooks/useAccounts/index.ts deleted file mode 100644 index 7228467747c..00000000000 --- a/app/components/UI/AccountSelectorList/hooks/useAccounts/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable import/prefer-default-export */ - -export { useAccounts } from './useAccounts'; -export type { EnsByAccountAddress } from './useAccounts.types'; diff --git a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.types.ts b/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.types.ts deleted file mode 100644 index df6ae658af0..00000000000 --- a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.types.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable import/prefer-default-export */ - -import { - Account, - AccountSelectorListProps, -} from '../../AccountSelectorList.types'; - -/** - * Mapping of ENS names by account address. - */ -export type EnsByAccountAddress = Record; - -/** - * Optional params that useAccount hook takes. - */ -export interface UseAccountsParams { - /** - * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. - * @param balance - The ticker balance of an account in wei and hex string format. - */ - checkBalanceError?: AccountSelectorListProps['checkBalanceError']; - /** - * Optional boolean that indicates if accounts are being processed in the background. Setting this to true will prevent any unnecessary updates while loading. - * @default false - */ - isLoading?: boolean; -} - -/** - * Return value for useAccounts hook. - */ -export interface UseAccounts { - /** - * List of account information. - */ - accounts: Account[]; - /** - * Mapping of ENS names by account address. - */ - ensByAccountAddress: EnsByAccountAddress; -} diff --git a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccountsTest.tsx b/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccountsTest.tsx deleted file mode 100644 index 173b7e17168..00000000000 --- a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccountsTest.tsx +++ /dev/null @@ -1,65 +0,0 @@ -// TODO - Finish tests - -// import { renderHook } from '@testing-library/react-hooks'; -// import { useAccounts } from './useAccounts'; - -// const initialState = { -// engine: { -// backgroundState: { -// PreferencesController: { -// selectedAddress: '0xc4800C54cB70E7Dac746C2fA829da0004443613e', -// identities: { -// '0xc4800C54cB70E7Dac746C2fA829da0004443613e': { name: 'Account 1' }, -// }, -// }, -// NetworkController: { -// network: '1', -// provider: { -// ticker: 'ETH', -// }, -// }, -// CurrencyRateController: { -// conversionRate: 1641.87, -// currentCurrency: 'usd', -// }, -// AccountTrackerController: { -// accounts: { -// '0xc4800C54cB70E7Dac746C2fA829da0004443613e': { -// balance: '0x0', -// }, -// }, -// }, -// }, -// }, -// }; - -// jest.mock('react-redux', () => ({ -// ...jest.requireActual('react-redux'), -// useSelector: jest.fn().mockImplementation((cb) => cb(initialState)), -// })); - -// jest.mock('../../../../../core/Engine', () => ({ -// context: { -// KeyringController: { -// state: { -// keyrings: [ -// { -// accounts: ['0xc4800C54cB70E7Dac746C2fA829da0004443613e'], -// type: 'HD Key Tree', -// }, -// ], -// }, -// }, -// }, -// })); - -// describe('useAccounts', () => { -// test('it should start with a state of "Loading"', async () => { -// const { result, waitForNextUpdate } = renderHook(() => useAccounts()); -// jest.runAllTimers(); -// await waitForNextUpdate(); - -// // TODO - Actually test hook -// expect(true).toBeTruthy(); -// }); -// }); diff --git a/app/components/UI/AccountSelectorList/index.ts b/app/components/UI/AccountSelectorList/index.ts index 34f107af8bc..6fd7bb878df 100644 --- a/app/components/UI/AccountSelectorList/index.ts +++ b/app/components/UI/AccountSelectorList/index.ts @@ -1,6 +1,2 @@ export { default } from './AccountSelectorList'; -export type { - Account, - AccountSelectorListProps, -} from './AccountSelectorList.types'; -export * from './hooks'; +export type { AccountSelectorListProps } from './AccountSelectorList.types'; diff --git a/app/components/Views/AccountSelector/AccountSelector.styles.ts b/app/components/Views/AccountSelector/AccountSelector.styles.ts new file mode 100644 index 00000000000..b6113ad83aa --- /dev/null +++ b/app/components/Views/AccountSelector/AccountSelector.styles.ts @@ -0,0 +1,7 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + sheet: { + paddingBottom: 16, + }, +}); diff --git a/app/components/Views/AccountSelector/AccountSelector.tsx b/app/components/Views/AccountSelector/AccountSelector.tsx index 706bede0b6a..e60e60754f9 100644 --- a/app/components/Views/AccountSelector/AccountSelector.tsx +++ b/app/components/Views/AccountSelector/AccountSelector.tsx @@ -1,5 +1,6 @@ // Third party dependencies. import React, { useCallback, useRef, useState } from 'react'; +import { View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; // External dependencies. @@ -14,14 +15,16 @@ import Logger from '../../../util/Logger'; import AnalyticsV2 from '../../../util/analyticsV2'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; import { strings } from '../../../../locales/i18n'; +import { useAccounts } from '../../hooks/useAccounts'; + +// Internal dependencies. import { ACCOUNT_LIST_ID, CREATE_ACCOUNT_BUTTON_ID, IMPORT_ACCOUNT_BUTTON_ID, } from './AccountSelector.constants'; - -// Internal dependencies. import { AccountSelectorProps } from './AccountSelector.types'; +import styles from './AccountSelector.styles'; const AccountSelector = ({ route }: AccountSelectorProps) => { const { @@ -36,17 +39,24 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { const [isLoading, setIsLoading] = useState(false); const sheetRef = useRef(null); const navigation = useNavigation(); + const { accounts, ensByAccountAddress } = useAccounts({ + checkBalanceError, + isLoading, + }); const _onSelectAccount = (address: string) => { + const { PreferencesController } = Engine.context; + PreferencesController.setSelectedAddress(address); sheetRef.current?.hide(); onSelectAccount?.(address); }; const createNewAccount = useCallback(async () => { - const { KeyringController } = Engine.context; + const { KeyringController, PreferencesController } = Engine.context; try { setIsLoading(true); - await KeyringController.addNewAccount(); + const { addedAccountAddress } = await KeyringController.addNewAccount(); + PreferencesController.setSelectedAddress(addedAccountAddress); AnalyticsV2.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); } catch (e: any) { Logger.error(e, 'error while trying to add a new account'); @@ -115,14 +125,16 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { return ( - + - {renderSheetActions()} + {renderSheetActions()} ); }; diff --git a/app/components/Views/AccountSelector/AccountSelector.types.ts b/app/components/Views/AccountSelector/AccountSelector.types.ts index 3e9758cd7a7..a107ba5d844 100644 --- a/app/components/Views/AccountSelector/AccountSelector.types.ts +++ b/app/components/Views/AccountSelector/AccountSelector.types.ts @@ -1,5 +1,5 @@ // External dependencies. -import { AccountSelectorListProps } from '../../../components/UI/AccountSelectorList'; +import { UseAccountsParams } from 'app/components/hooks/useAccounts'; /** * AccountSelectorProps props. @@ -34,7 +34,7 @@ export interface AccountSelectorProps { * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. * @param balance - The ticker balance of an account in wei and hex string format. */ - checkBalanceError?: AccountSelectorListProps['checkBalanceError']; + checkBalanceError?: UseAccountsParams['checkBalanceError']; }; }; } diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index 21c3223a0c6..a4a325f5ce7 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -4,6 +4,7 @@ import React, { useState, useCallback, useContext, + useMemo, } from 'react'; import { RefreshControl, @@ -12,7 +13,6 @@ import { ActivityIndicator, StyleSheet, View, - ImageSourcePropType, } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import ScrollableTabView from 'react-native-scrollable-tab-view'; @@ -35,9 +35,11 @@ import { useTheme } from '../../../util/theme'; import { shouldShowWhatsNewModal } from '../../../util/onboarding'; import Logger from '../../../util/Logger'; import Routes from '../../../constants/navigation/Routes'; -import Networks, { getDefaultNetworkByChainId } from '../../../util/networks'; +import { + getNetworkImageSource, + getNetworkNameFromProvider, +} from '../../../util/networks'; import { toggleNetworkModal } from '../../../actions/modals'; -import PopularList from '../../../util/networks/customNetworks'; const createStyles = (colors: any) => StyleSheet.create({ @@ -136,6 +138,16 @@ const Wallet = ({ navigation }: any) => { (state: any) => state.engine.backgroundState.NetworkController.provider, ); const dispatch = useDispatch(); + const networkName = useMemo( + () => getNetworkNameFromProvider(networkProvider), + [networkProvider], + ); + + const networkImageSource = useMemo( + () => getNetworkImageSource(networkProvider.chainId), + [networkProvider.chainId], + ); + /** * Callback to trigger when pressing the navigation title. */ @@ -183,48 +195,11 @@ const Wallet = ({ navigation }: any) => { [navigation], ); - /** - * Get the current network name. - * - * @returns Current network name. - */ - const getNetworkName = useCallback(() => { - let name = ''; - if (networkProvider.nickname) { - name = networkProvider.nickname; - } else { - const networkType: keyof typeof Networks = networkProvider.type; - name = Networks?.[networkType]?.name || Networks.rpc.name; - } - return name; - }, [networkProvider.nickname, networkProvider.type]); - - /** - * Get image source for either default MetaMask networks or popular networks, which include networks such as Polygon, Binance, Avalanche, etc. - * @returns A network image from a local resource or undefined - */ - const getNetworkImageSource = useCallback((): - | ImageSourcePropType - | undefined => { - const defaultNetwork: any = getDefaultNetworkByChainId( - networkProvider.chainId, - ); - if (defaultNetwork) { - return defaultNetwork.imageSource; - } - const popularNetwork = PopularList.find( - (network) => network.chainId === networkProvider.chainId, - ); - if (popularNetwork) { - return popularNetwork.rpcPrefs.imageSource; - } - }, [networkProvider.chainId]); - useEffect(() => { navigation.setOptions( getWalletNavbarOptions( - getNetworkName(), - getNetworkImageSource(), + networkName, + networkImageSource, onTitlePress, navigation, drawerRef, @@ -232,13 +207,7 @@ const Wallet = ({ navigation }: any) => { ), ); /* eslint-disable-next-line */ - }, [ - navigation, - themeColors, - getNetworkName, - getNetworkImageSource, - onTitlePress, - ]); + }, [navigation, themeColors, networkName, networkImageSource, onTitlePress]); const onRefresh = useCallback(async () => { requestAnimationFrame(async () => { diff --git a/app/components/hooks/useAccounts/index.ts b/app/components/hooks/useAccounts/index.ts new file mode 100644 index 00000000000..bc4aa0a582a --- /dev/null +++ b/app/components/hooks/useAccounts/index.ts @@ -0,0 +1,2 @@ +export { default as useAccounts } from './useAccounts'; +export * from './useAccounts.types'; diff --git a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.ts b/app/components/hooks/useAccounts/useAccounts.ts similarity index 93% rename from app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.ts rename to app/components/hooks/useAccounts/useAccounts.ts index 4dfba38565b..7be14bdbbc3 100644 --- a/app/components/UI/AccountSelectorList/hooks/useAccounts/useAccounts.ts +++ b/app/components/hooks/useAccounts/useAccounts.ts @@ -1,5 +1,3 @@ -/* eslint-disable import/prefer-default-export */ - // Third party dependencies. import { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; @@ -8,14 +6,14 @@ import { KeyringTypes } from '@metamask/controllers'; import { isEqual } from 'lodash'; // External Dependencies. -import UntypedEngine from '../../../../../core/Engine'; -import { Account } from '../..'; -import { doENSReverseLookup } from '../../../../../util/ENSUtils'; -import { hexToBN, renderFromWei, weiToFiat } from '../../../../../util/number'; -import { getTicker } from '../../../../..//util/transactions'; +import UntypedEngine from '../../../core/Engine'; +import { doENSReverseLookup } from '../../../util/ENSUtils'; +import { hexToBN, renderFromWei, weiToFiat } from '../../../util/number'; +import { getTicker } from '../../../util/transactions'; // Internal dependencies import { + Account, EnsByAccountAddress, UseAccounts, UseAccountsParams, @@ -26,7 +24,7 @@ import { * * @returns Object that contins both wallet accounts and ens name information. */ -export const useAccounts = ({ +const useAccounts = ({ checkBalanceError, isLoading = false, }: UseAccountsParams = {}): UseAccounts => { @@ -49,7 +47,7 @@ export const useAccounts = ({ const accountInfoByAddress = useSelector( (state: any) => state.engine.backgroundState.AccountTrackerController.accounts, - (left, right) => isEqual(left, right), + isEqual, ); const conversionRate = useSelector( (state: any) => @@ -203,3 +201,5 @@ export const useAccounts = ({ return { accounts, ensByAccountAddress }; }; + +export default useAccounts; diff --git a/app/components/hooks/useAccounts/useAccounts.types.ts b/app/components/hooks/useAccounts/useAccounts.types.ts new file mode 100644 index 00000000000..c77bfeb425b --- /dev/null +++ b/app/components/hooks/useAccounts/useAccounts.types.ts @@ -0,0 +1,89 @@ +// Third party dependencies. +import { KeyringTypes } from '@metamask/controllers'; + +// External dependencies. +import { AvatarGroupToken } from '../../../component-library/components/Avatars/AvatarGroup/AvatarGroup.types'; + +/** + * Asset information associated with the account, which includes both the fiat balance and owned tokens. + */ +export interface Assets { + /** + * Fiat balance in string format. + */ + fiatBalance: string; + /** + * Tokens owned by this account. + */ + tokens?: AvatarGroupToken[]; +} + +/** + * Account information. + */ +export interface Account { + /** + * Account name. + */ + name: string; + /** + * Account address. + */ + address: string; + /** + * Asset information associated with the account, which includes both the fiat balance and owned tokens. + */ + assets?: Assets; + /** + * Account type. + */ + type: KeyringTypes; + /** + * Y offset of the item. Used for scrolling purposes. + */ + yOffset: number; + /** + * Boolean that indicates if the account matches the active account on the wallet. + */ + isSelected: boolean; + /** + * Optional error that indicates if the account has enough funds. Non-empty string will render the account item non-selectable. + */ + balanceError?: string; +} + +/** + * Mapping of ENS names by account address. + */ +export type EnsByAccountAddress = Record; + +/** + * Optional params that useAccount hook takes. + */ +export interface UseAccountsParams { + /** + * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. + * @param balance - The ticker balance of an account in wei and hex string format. + */ + + checkBalanceError?: (balance: string) => string; + /** + * Optional boolean that indicates if accounts are being processed in the background. Setting this to true will prevent any unnecessary updates while loading. + * @default false + */ + isLoading?: boolean; +} + +/** + * Return value for useAccounts hook. + */ +export interface UseAccounts { + /** + * List of account information. + */ + accounts: Account[]; + /** + * Mapping of ENS names by account address. + */ + ensByAccountAddress: EnsByAccountAddress; +} diff --git a/app/util/ENSUtils.js b/app/util/ENSUtils.js index c2878ae123f..68f2a9e80bd 100644 --- a/app/util/ENSUtils.js +++ b/app/util/ENSUtils.js @@ -11,7 +11,7 @@ const CACHE_REFRESH_THRESHOLD = 60 * 60 * 1000; * Utility class with the single responsibility * of caching ENS names */ -class ENSCache { +export class ENSCache { static cache = {}; } diff --git a/app/util/accounts/index.ts b/app/util/accounts/index.ts new file mode 100644 index 00000000000..b0157739b6a --- /dev/null +++ b/app/util/accounts/index.ts @@ -0,0 +1,32 @@ +// External dependencies. +import { + Account, + EnsByAccountAddress, +} from '../../components/hooks/useAccounts'; +import { isDefaultAccountName } from '../ENSUtils'; + +/** + * Gets the Account nickname, ENS name, or default account name - Whichever one is available. + * + * @param params.accountAddress - Address of the account. + * @param params.accounts - Array of accounts returned from useAccounts hook. + * @param params.ensByAccountAddress - ENS name map returned from useAccounts hook. + * @returns - Account nickname, ENS name, or default account name. + */ +export const getAccountNameWithENS = ({ + accountAddress, + accounts, + ensByAccountAddress, +}: { + accountAddress: string; + accounts: Account[]; + ensByAccountAddress: EnsByAccountAddress; +}) => { + const account = accounts.find(({ address }) => address === accountAddress); + const ensName = ensByAccountAddress[accountAddress]; + return isDefaultAccountName(account?.name) && ensName + ? ensName + : account?.name || ''; +}; + +export default getAccountNameWithENS; diff --git a/app/util/address/index.js b/app/util/address/index.js index 2609125f65d..15dff6c0193 100644 --- a/app/util/address/index.js +++ b/app/util/address/index.js @@ -12,6 +12,7 @@ import Engine from '../../core/Engine'; import { strings } from '../../../locales/i18n'; import { tlc } from '../general'; import { PROTOCOLS } from '../../constants/deeplinks'; +import { ENSCache, isDefaultAccountName } from '../ENSUtils'; /** * Returns full checksummed address @@ -87,9 +88,15 @@ export function renderSlightlyLongAddress( * @returns {String} - String corresponding to account name. If there is no name, returns the original short format address */ export function renderAccountName(address, identities) { + const { NetworkController } = Engine.context; + const network = NetworkController.state.network; address = safeToChecksumAddress(address); if (identities && address && address in identities) { - return identities[address].name; + const identityName = identities[address].name; + const ensName = ENSCache.cache[`${network}${address}`]?.name || ''; + return isDefaultAccountName(identityName) && ensName + ? ensName + : identityName; } return renderShortAddress(address); } diff --git a/app/util/networks/index.js b/app/util/networks/index.js index 93a62c03085..bd2d311df54 100644 --- a/app/util/networks/index.js +++ b/app/util/networks/index.js @@ -25,6 +25,7 @@ const kovanLogo = require('../../images/kovan-logo-dark.png'); const rinkebyLogo = require('../../images/rinkeby-logo-dark.png'); const goerliLogo = require('../../images/goerli-logo-dark.png'); /* eslint-enable */ +import PopularList from './customNetworks'; /** * List of the supported networks @@ -284,3 +285,39 @@ export function blockTagParamIndex(payload) { return undefined; } } + +/** + * Gets the current network name given the network provider. + * + * @param {Object} provider - Network provider state from the NetworkController. + * @returns {string} Name of the network. + */ +export const getNetworkNameFromProvider = (provider) => { + let name = ''; + if (provider.nickname) { + name = provider.nickname; + } else { + const networkType = provider.type; + name = NetworkList?.[networkType]?.name || NetworkList.rpc.name; + } + return name; +}; + +/** + * Gets the image source given the chain ID. + * + * @param {string} chainId - ChainID of the network. + * @returns {Object} - Image source of the network. + */ +export const getNetworkImageSource = (chainId) => { + const defaultNetwork = getDefaultNetworkByChainId(chainId); + if (defaultNetwork) { + return defaultNetwork.imageSource; + } + const popularNetwork = PopularList.find( + (network) => network.chainId === chainId, + ); + if (popularNetwork) { + return popularNetwork.rpcPrefs.imageSource; + } +}; From b1ad83607c2cfedee410ebba1531693f595fb155 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 28 Sep 2022 16:11:46 -0700 Subject: [PATCH 07/91] [PS] Hook up PS controller (#5050) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values * Organize account hooks and utils * Export ENS cache. Update account util to return ENS name. * Update account selector * Remove account selector styles * Hook up permission system controller logic --- app/actions/privacy/index.js | 11 - app/components/Nav/Main/RootRPCMethodsUI.js | 56 +--- .../Settings/GeneralSettings/index.test.tsx | 2 +- .../__snapshots__/index.test.tsx.snap | 4 - .../Views/Settings/SecuritySettings/index.js | 63 +---- .../Settings/SecuritySettings/index.test.tsx | 2 +- app/components/Views/Settings/index.test.tsx | 2 +- app/core/BackgroundBridge.js | 8 + app/core/Engine.js | 91 ++++-- app/core/EngineService.ts | 4 + app/core/Permissions/constants.js | 7 + app/core/Permissions/index.ts | 190 +++++++++++++ app/core/Permissions/specifications.js | 231 ++++++++++++++++ app/core/Permissions/specifications.test.js | 260 ++++++++++++++++++ app/core/RPCMethods/RPCMethodMiddleware.ts | 147 +++++----- app/core/WalletConnect.js | 3 - app/reducers/privacy/index.js | 6 - package.json | 1 + yarn.lock | 2 +- 19 files changed, 857 insertions(+), 233 deletions(-) create mode 100644 app/core/Permissions/constants.js create mode 100644 app/core/Permissions/index.ts create mode 100644 app/core/Permissions/specifications.js create mode 100644 app/core/Permissions/specifications.test.js diff --git a/app/actions/privacy/index.js b/app/actions/privacy/index.js index e3067cbc3aa..841d65b9aa4 100644 --- a/app/actions/privacy/index.js +++ b/app/actions/privacy/index.js @@ -12,17 +12,6 @@ export function rejectHost(hostname) { }; } -export function clearHosts() { - return { type: 'CLEAR_HOSTS' }; -} - -export function setPrivacyMode(enabled) { - return { - type: 'SET_PRIVACY_MODE', - enabled, - }; -} - export function setThirdPartyApiMode(enabled) { return { type: 'SET_THIRD_PARTY_API_MODE', diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 4c06185f5bc..835d0a2e5fd 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -57,6 +57,7 @@ import { useTheme } from '../../../util/theme'; import withQRHardwareAwareness from '../../UI/QRHardware/withQRHardwareAwareness'; import QRSigningModal from '../../UI/QRHardware/QRSigningModal'; import { networkSwitched } from '../../../actions/onboardNetwork'; +import Routes from '../../../constants/navigation/Routes'; const hstInterface = new ethers.utils.Interface(abi); @@ -82,8 +83,6 @@ const RootRPCMethodsUI = (props) => { const [customNetworkToAdd, setCustomNetworkToAdd] = useState(null); const [customNetworkToSwitch, setCustomNetworkToSwitch] = useState(null); - const [hostToApprove, setHostToApprove] = useState(null); - const [watchAsset, setWatchAsset] = useState(false); const [suggestedAssetMeta, setSuggestedAssetMeta] = useState(undefined); @@ -602,47 +601,6 @@ const RootRPCMethodsUI = (props) => { ); - /** - * When user clicks on approve to connect with a dapp - */ - const onAccountsConfirm = () => { - acceptPendingApproval(hostToApprove.id, hostToApprove.requestData); - setShowPendingApproval(false); - }; - - /** - * When user clicks on reject to connect with a dapp - */ - const onAccountsReject = () => { - rejectPendingApproval(hostToApprove.id, hostToApprove.requestData); - setShowPendingApproval(false); - }; - - /** - * Render the modal that asks the user to approve/reject connections to a dapp - */ - const renderAccountsApprovalModal = () => ( - - - - ); - /** * On rejection addinga an asset */ @@ -702,8 +660,17 @@ const RootRPCMethodsUI = (props) => { setCurrentPageMeta(requestData.pageMeta); } switch (request.type) { + case 'wallet_requestPermissions': + if (requestData?.permissions?.eth_accounts) { + props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_CONNECT, + params: { + hostInfo: requestData, + }, + }); + } + break; case ApprovalTypes.CONNECT_ACCOUNTS: - setHostToApprove({ data: requestData, id: request.id }); showPendingApprovalModal({ type: ApprovalTypes.CONNECT_ACCOUNTS, origin: request.origin, @@ -783,7 +750,6 @@ const RootRPCMethodsUI = (props) => { {renderApproveModal()} {renderAddCustomNetworkModal()} {renderSwitchCustomNetworkModal()} - {renderAccountsApprovalModal()} {renderWatchAssetModal()} {renderQRSigningModal()} diff --git a/app/components/Views/Settings/GeneralSettings/index.test.tsx b/app/components/Views/Settings/GeneralSettings/index.test.tsx index 272a8b8ba9b..027aeea4320 100644 --- a/app/components/Views/Settings/GeneralSettings/index.test.tsx +++ b/app/components/Views/Settings/GeneralSettings/index.test.tsx @@ -7,7 +7,7 @@ import { AppThemeKey } from '../../../../util/theme/models'; const mockStore = configureMockStore(); const initialState = { - privacy: { approvedHosts: [], privacyMode: true }, + privacy: { approvedHosts: [] }, browser: { history: [] }, settings: { lockTime: 1000, diff --git a/app/components/Views/Settings/SecuritySettings/__snapshots__/index.test.tsx.snap b/app/components/Views/Settings/SecuritySettings/__snapshots__/index.test.tsx.snap index 71d26178f21..ae0375225b2 100644 --- a/app/components/Views/Settings/SecuritySettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/Settings/SecuritySettings/__snapshots__/index.test.tsx.snap @@ -3,10 +3,8 @@ exports[`SecuritySettings should render correctly 1`] = ` diff --git a/app/components/Views/Settings/SecuritySettings/index.js b/app/components/Views/Settings/SecuritySettings/index.js index f4a00dbc84e..86f3dcd8094 100644 --- a/app/components/Views/Settings/SecuritySettings/index.js +++ b/app/components/Views/Settings/SecuritySettings/index.js @@ -22,11 +22,7 @@ import SelectComponent from '../../../UI/SelectComponent'; import StyledButton from '../../../UI/StyledButton'; import SettingsNotification from '../../../UI/SettingsNotification'; import { clearHistory } from '../../../../actions/browser'; -import { - clearHosts, - setPrivacyMode, - setThirdPartyApiMode, -} from '../../../../actions/privacy'; +import { setThirdPartyApiMode } from '../../../../actions/privacy'; import { fontStyles, colors as importedColors, @@ -217,14 +213,6 @@ Heading.propTypes = { */ class Settings extends PureComponent { static propTypes = { - /** - * Indicates whether privacy mode is enabled - */ - privacyMode: PropTypes.bool, - /** - * Called to toggle privacy mode - */ - setPrivacyMode: PropTypes.func, /** * Called to toggle set party api mode */ @@ -241,14 +229,6 @@ class Settings extends PureComponent { /* navigation object required to push new views */ navigation: PropTypes.object, - /** - * Map of hostnames with approved account access - */ - approvedHosts: PropTypes.object, - /** - * Called to clear all hostnames with account access - */ - clearHosts: PropTypes.func, /** * Array of visited websites */ @@ -518,7 +498,8 @@ class Settings extends PureComponent { }; clearApprovals = () => { - this.props.clearHosts(); + const { PermissionController } = Engine.context; + PermissionController?.clearState?.(); this.toggleClearApprovalsModal(); }; @@ -527,10 +508,6 @@ class Settings extends PureComponent { this.toggleClearBrowserHistoryModal(); }; - togglePrivacy = (value) => { - this.props.setPrivacyMode(value); - }; - toggleThirdPartyAPI = (value) => { this.props.setThirdPartyApiMode(value); }; @@ -868,7 +845,6 @@ class Settings extends PureComponent { }; renderClearPrivacySection = () => { - const { approvedHosts } = this.props; const { styles } = this.getStyles(); return ( @@ -885,7 +861,6 @@ class Settings extends PureComponent { {strings('app_settings.clear_privacy_title')} @@ -918,33 +893,6 @@ class Settings extends PureComponent { ); }; - renderPrivacyModeSection = () => { - const { privacyMode } = this.props; - const { styles, colors } = this.getStyles(); - - return ( - - {strings('app_settings.privacy_mode')} - - {strings('app_settings.privacy_mode_desc')} - - - - - - ); - }; - renderMetaMetricsSection = () => { const { analyticsEnabled } = this.state; const { styles, colors } = this.getStyles(); @@ -1144,7 +1092,6 @@ class Settings extends PureComponent { {this.renderClearPrivacySection()} {this.renderClearBrowserHistorySection()} - {this.renderPrivacyModeSection()} {this.renderMetaMetricsSection()} @@ -1163,10 +1110,8 @@ class Settings extends PureComponent { Settings.contextType = ThemeContext; const mapStateToProps = (state) => ({ - approvedHosts: state.privacy.approvedHosts, browserHistory: state.browser.history, lockTime: state.settings.lockTime, - privacyMode: state.privacy.privacyMode, thirdPartyApiMode: state.privacy.thirdPartyApiMode, selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress, @@ -1184,9 +1129,7 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ clearBrowserHistory: () => dispatch(clearHistory()), - clearHosts: () => dispatch(clearHosts()), setLockTime: (lockTime) => dispatch(setLockTime(lockTime)), - setPrivacyMode: (enabled) => dispatch(setPrivacyMode(enabled)), setThirdPartyApiMode: (enabled) => dispatch(setThirdPartyApiMode(enabled)), passwordSet: () => dispatch(passwordSet()), }); diff --git a/app/components/Views/Settings/SecuritySettings/index.test.tsx b/app/components/Views/Settings/SecuritySettings/index.test.tsx index 2dbae2c63c6..20525b4e7e6 100644 --- a/app/components/Views/Settings/SecuritySettings/index.test.tsx +++ b/app/components/Views/Settings/SecuritySettings/index.test.tsx @@ -6,7 +6,7 @@ import { Provider } from 'react-redux'; const mockStore = configureMockStore(); const initialState = { - privacy: { approvedHosts: {}, privacyMode: true }, + privacy: { approvedHosts: {} }, browser: { history: [] }, settings: { lockTime: 1000 }, user: { passwordSet: true }, diff --git a/app/components/Views/Settings/index.test.tsx b/app/components/Views/Settings/index.test.tsx index c4a869ceb15..244a228357e 100644 --- a/app/components/Views/Settings/index.test.tsx +++ b/app/components/Views/Settings/index.test.tsx @@ -7,7 +7,7 @@ import { Provider } from 'react-redux'; const mockStore = configureMockStore(); const initialState = { user: { seedphraseBackedUp: true }, - privacy: { approvedHosts: [], privacyMode: true }, + privacy: { approvedHosts: [] }, browser: { history: [] }, settings: { lockTime: 1000, diff --git a/app/core/BackgroundBridge.js b/app/core/BackgroundBridge.js index 6e773ff062b..03edae1e638 100644 --- a/app/core/BackgroundBridge.js +++ b/app/core/BackgroundBridge.js @@ -336,6 +336,14 @@ export class BackgroundBridge extends EventEmitter { }), ); + const permissionController = Engine.context.PermissionController; + + engine.push( + permissionController.createPermissionMiddleware({ + origin, + }), + ); + // forward to metamask primary provider engine.push(providerAsMiddleware(provider)); return engine; diff --git a/app/core/Engine.js b/app/core/Engine.js index 2c6543640d6..452d8b59241 100644 --- a/app/core/Engine.js +++ b/app/core/Engine.js @@ -24,6 +24,7 @@ import { TokenDetectionController, CollectibleDetectionController, ApprovalController, + PermissionController, } from '@metamask/controllers'; import SwapsController, { swapsUtils } from '@metamask/swaps-controller'; import AsyncStorage from '@react-native-async-storage/async-storage'; @@ -46,6 +47,11 @@ import Logger from '../util/Logger'; import { LAST_INCOMING_TX_BLOCK_INFO } from '../constants/storage'; import { isZero } from '../util/lodash'; import AnalyticsV2 from '../util/analyticsV2'; +import { + getCaveatSpecifications, + getPermissionSpecifications, + unrestrictedMethods, +} from './Permissions/specifications.js'; const NON_EMPTY = 'NON_EMPTY'; @@ -207,30 +213,48 @@ class Engine { 'https://gas-api.metaswap.codefi.network/networks//suggestedGasFees', }); + const approvalController = new ApprovalController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'ApprovalController', + }), + showApprovalRequest: () => null, + }); + const additionalKeyrings = [QRHardwareKeyring]; + const getIdentities = () => { + const identities = preferencesController.state.identities; + const newIdentities = {}; + Object.keys(identities).forEach((key) => { + newIdentities[key.toLowerCase()] = identities[key]; + }); + return newIdentities; + }; + + const keyringController = new KeyringController( + { + removeIdentity: preferencesController.removeIdentity.bind( + preferencesController, + ), + syncIdentities: preferencesController.syncIdentities.bind( + preferencesController, + ), + updateIdentities: preferencesController.updateIdentities.bind( + preferencesController, + ), + setSelectedAddress: preferencesController.setSelectedAddress.bind( + preferencesController, + ), + setAccountLabel: preferencesController.setAccountLabel.bind( + preferencesController, + ), + }, + { encryptor, keyringTypes: additionalKeyrings }, + initialState.KeyringController, + ); + const controllers = [ - new KeyringController( - { - removeIdentity: preferencesController.removeIdentity.bind( - preferencesController, - ), - syncIdentities: preferencesController.syncIdentities.bind( - preferencesController, - ), - updateIdentities: preferencesController.updateIdentities.bind( - preferencesController, - ), - setSelectedAddress: preferencesController.setSelectedAddress.bind( - preferencesController, - ), - setAccountLabel: preferencesController.setAccountLabel.bind( - preferencesController, - ), - }, - { encryptor, keyringTypes: additionalKeyrings }, - initialState.KeyringController, - ), + keyringController, new AccountTrackerController({ onPreferencesStateChange: (listener) => preferencesController.subscribe(listener), @@ -344,11 +368,28 @@ class Engine { }, ), gasFeeController, - new ApprovalController({ + approvalController, + new PermissionController({ messenger: this.controllerMessenger.getRestricted({ - name: 'ApprovalController', + name: 'PermissionController', + allowedActions: [ + `${approvalController.name}:addRequest`, + `${approvalController.name}:hasRequest`, + `${approvalController.name}:acceptRequest`, + `${approvalController.name}:rejectRequest`, + ], }), - showApprovalRequest: () => null, + state: initialState.PermissionController, + caveatSpecifications: getCaveatSpecifications({ getIdentities }), + permissionSpecifications: { + ...getPermissionSpecifications({ + getAllAccounts: () => keyringController.getAccounts(), + }), + /* + ...this.getSnapPermissionSpecifications(), + */ + }, + unrestrictedMethods, }), ]; // set initial state @@ -783,6 +824,7 @@ export default { TokensController, TokenDetectionController, CollectibleDetectionController, + PermissionController, } = instance.datamodel.state; // normalize `null` currencyRate to `0` @@ -816,6 +858,7 @@ export default { GasFeeController, TokenDetectionController, CollectibleDetectionController, + PermissionController, }; }, get datamodel() { diff --git a/app/core/EngineService.ts b/app/core/EngineService.ts index d802faa14f9..ba1c5ac1f5d 100644 --- a/app/core/EngineService.ts +++ b/app/core/EngineService.ts @@ -52,6 +52,10 @@ class EngineService { name: 'ApprovalController', key: `${Engine.context.ApprovalController.name}:stateChange`, }, + { + name: 'PermissionController', + key: `${Engine.context.PermissionController.name}:stateChange`, + }, ]; Engine?.datamodel?.subscribe?.(() => { diff --git a/app/core/Permissions/constants.js b/app/core/Permissions/constants.js new file mode 100644 index 00000000000..546945a4f6b --- /dev/null +++ b/app/core/Permissions/constants.js @@ -0,0 +1,7 @@ +export const CaveatTypes = Object.freeze({ + restrictReturnedAccounts: 'restrictReturnedAccounts', +}); + +export const RestrictedMethods = Object.freeze({ + eth_accounts: 'eth_accounts', +}); diff --git a/app/core/Permissions/index.ts b/app/core/Permissions/index.ts new file mode 100644 index 00000000000..3cdee32acdb --- /dev/null +++ b/app/core/Permissions/index.ts @@ -0,0 +1,190 @@ +import { errorCodes as rpcErrorCodes } from 'eth-rpc-errors'; +import { orderBy } from 'lodash'; +import { RestrictedMethods, CaveatTypes } from './constants'; +import ImportedEngine from '../Engine'; +import { SelectedAccount } from '../../components/UI/AccountSelectorList/AccountSelectorList.types'; +import Logger from '../../util/Logger'; +const Engine = ImportedEngine as any; + +function getAccountsCaveatFromPermission(accountsPermission: any = {}) { + return ( + Array.isArray(accountsPermission.caveats) && + accountsPermission.caveats.find( + (caveat: any) => caveat.type === CaveatTypes.restrictReturnedAccounts, + ) + ); +} + +function getAccountsPermissionFromSubject(subject: any = {}) { + return subject.permissions?.eth_accounts || {}; +} + +function getAccountsFromPermission(accountsPermission: any) { + const accountsCaveat = getAccountsCaveatFromPermission(accountsPermission); + return accountsCaveat && Array.isArray(accountsCaveat.value) + ? accountsCaveat.value + : []; +} + +function getAccountsFromSubject(subject: any) { + return getAccountsFromPermission(getAccountsPermissionFromSubject(subject)); +} + +export const getPermittedAccountsByHostname = ( + state: any, + hostname: string, +) => { + const subjects = state.subjects; + const accountsByHostname = Object.keys(subjects).reduce( + (acc: any, subjectKey) => { + const accounts = getAccountsFromSubject(subjects[subjectKey]); + if (accounts.length > 0) { + acc[subjectKey] = accounts.map( + ({ address }: { address: string }) => address, + ); + } + return acc; + }, + {}, + ); + + return accountsByHostname?.[hostname] || []; +}; + +export const switchActiveAccounts = (hostname: string, accAddress: string) => { + const { PermissionController } = Engine.context; + const existingAccounts: SelectedAccount[] = PermissionController.getCaveat( + hostname, + RestrictedMethods.eth_accounts, + CaveatTypes.restrictReturnedAccounts, + ).value; + const accountIndex = existingAccounts.findIndex( + ({ address }) => address === accAddress, + ); + if (accountIndex === -1) { + throw new Error( + `eth_accounts permission for hostname "${hostname}" does not permit "${accAddress} account".`, + ); + } + let newAccounts = [...existingAccounts]; + newAccounts.splice(accountIndex, 1); + newAccounts = [{ address: accAddress, lastUsed: Date.now() }, ...newAccounts]; + + PermissionController.updateCaveat( + hostname, + RestrictedMethods.eth_accounts, + CaveatTypes.restrictReturnedAccounts, + newAccounts, + ); +}; + +export const addPermittedAccounts = ( + hostname: string, + addresses: string[], +): string => { + const { PermissionController } = Engine.context; + const existing = PermissionController.getCaveat( + hostname, + RestrictedMethods.eth_accounts, + CaveatTypes.restrictReturnedAccounts, + ); + const existingPermittedAccountAddresses = existing.value.map( + ({ address }: { address: string }) => address, + ); + + for (const address in addresses) { + if (existingPermittedAccountAddresses.includes(address)) { + throw new Error( + `eth_accounts permission for hostname "${hostname}" already permits account "${address}".`, + ); + } + } + + const selectedAccounts: SelectedAccount[] = addresses.map( + (address, index) => ({ address, lastUsed: Date.now() - index }), + ); + + const newSortedAccounts = orderBy( + [...existing.value, ...selectedAccounts], + 'lastUsed', + 'desc', + ); + + PermissionController.updateCaveat( + hostname, + RestrictedMethods.eth_accounts, + CaveatTypes.restrictReturnedAccounts, + newSortedAccounts, + ); + + return newSortedAccounts[0].address; +}; + +export const removePermittedAccount = (hostname: string, account: string) => { + const { PermissionController } = Engine.context; + + const existing = PermissionController.getCaveat( + hostname, + RestrictedMethods.eth_accounts, + CaveatTypes.restrictReturnedAccounts, + ); + const existingPermittedAccountAddresses = existing.value.map( + ({ address }: { address: string }) => address, + ); + + if (!existingPermittedAccountAddresses.includes(account)) { + throw new Error( + `eth_accounts permission for hostname "${hostname}" already does not permit account "${account}".`, + ); + } + + const remainingAccounts = existing.value.filter( + ({ address }: { address: string }) => address !== account, + ); + + if (remainingAccounts.length === 0) { + PermissionController.revokePermission( + hostname, + RestrictedMethods.eth_accounts, + ); + } else { + PermissionController.updateCaveat( + hostname, + RestrictedMethods.eth_accounts, + CaveatTypes.restrictReturnedAccounts, + remainingAccounts, + ); + } +}; + +export const removeAccountFromPermissions = async (address: string) => { + const { PermissionController } = Engine.context; + for (const subject in PermissionController.state.subjects) { + try { + removePermittedAccount(subject, address); + } catch (e) { + Logger.log( + e, + 'Failed to remove account from permissions after deleting account from wallet.', + ); + } + } +}; + +export const getPermittedAccounts = async (hostname: string) => { + try { + const accountsWithLastUsed = + await Engine.context.PermissionController.executeRestrictedMethod( + hostname, + RestrictedMethods.eth_accounts, + ); + return accountsWithLastUsed.map( + ({ address }: { address: string }) => address, + ); + } catch (error: any) { + if (error.code === rpcErrorCodes.provider.unauthorized) { + return []; + } + throw error; + } +}; diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js new file mode 100644 index 00000000000..a049906a425 --- /dev/null +++ b/app/core/Permissions/specifications.js @@ -0,0 +1,231 @@ +import { constructPermission, PermissionType } from '@metamask/controllers'; +import { v1 as random } from 'uuid'; +import { CaveatTypes, RestrictedMethods } from './constants'; + +/** + * This file contains the specifications of the permissions and caveats + * that are recognized by our permission system. See the PermissionController + * README in @metamask/snap-controllers for details. + */ + +/** + * The "keys" of all of permissions recognized by the PermissionController. + * Permission keys and names have distinct meanings in the permission system. + */ +const PermissionKeys = Object.freeze({ + ...RestrictedMethods, +}); + +/** + * Factory functions for all caveat types recognized by the + * PermissionController. + */ +const CaveatFactories = Object.freeze({ + [CaveatTypes.restrictReturnedAccounts]: (accounts) => ({ + type: CaveatTypes.restrictReturnedAccounts, + value: accounts, + }), +}); + +/** + * A PreferencesController identity object. + * + * @typedef {Object} Identity + * @property {string} address - The address of the identity. + * @property {string} name - The name of the identity. + * @property {number} [lastSelected] - Unix timestamp of when the identity was + * last selected in the UI. + */ + +/** + * Gets the specifications for all caveats that will be recognized by the + * PermissionController. + * + * @param {{ + * getIdentities: () => Record, + * }} options - Options bag. + */ +export const getCaveatSpecifications = ({ getIdentities }) => ({ + [CaveatTypes.restrictReturnedAccounts]: { + type: CaveatTypes.restrictReturnedAccounts, + + decorator: (method, caveat) => async (args) => { + const allAccounts = await method(args); + const res = caveat.value.filter(({ address }) => { + const addressToCompare = address.toLowerCase(); + return allAccounts.includes(addressToCompare); + }); + return res.slice(0, 1); + }, + + validator: (caveat, _origin, _target) => + validateCaveatAccounts(caveat.value, getIdentities), + }, +}); + +/** + * Gets the specifications for all permissions that will be recognized by the + * PermissionController. + * + * @param {{ + * getAllAccounts: () => Promise, + * getIdentities: () => Record, + * }} options - Options bag. + * @param options.getAllAccounts - A function that returns all Ethereum accounts + * in the current MetaMask instance. + * @param options.getIdentities - A function that returns the + * `PreferencesController` identity objects for all Ethereum accounts in the + * @param options.captureKeyringTypesWithMissingIdentities - A function that + * captures extra error information about the "Missing identity for address" + * error. + * current MetaMask instance. + */ +export const getPermissionSpecifications = ({ getAllAccounts }) => ({ + [PermissionKeys.eth_accounts]: { + permissionType: PermissionType.RestrictedMethod, + targetKey: PermissionKeys.eth_accounts, + allowedCaveats: [CaveatTypes.restrictReturnedAccounts], + + factory: (permissionOptions, requestData) => { + if (Array.isArray(permissionOptions.caveats)) { + throw new Error( + `${PermissionKeys.eth_accounts} error: Received unexpected caveats. Any permitted caveats will be added automatically.`, + ); + } + + // This value will be further validated as part of the caveat. + if (!requestData.approvedAccounts) { + throw new Error( + `${PermissionKeys.eth_accounts} error: No approved accounts specified.`, + ); + } + + return constructPermission({ + id: random(), + ...permissionOptions, + caveats: [ + CaveatFactories[CaveatTypes.restrictReturnedAccounts]( + requestData.approvedAccounts, + ), + ], + }); + }, + + methodImplementation: async (_args) => { + const accounts = await getAllAccounts(); + return accounts; + }, + + validator: (permission, _origin, _target) => { + const { caveats } = permission; + if ( + !caveats || + caveats.length !== 1 || + caveats[0].type !== CaveatTypes.restrictReturnedAccounts + ) { + throw new Error( + `${PermissionKeys.eth_accounts} error: Invalid caveats. There must be a single caveat of type "${CaveatTypes.restrictReturnedAccounts}".`, + ); + } + }, + }, +}); + +/** + * Validates the accounts associated with a caveat. In essence, ensures that + * the accounts value is an array of non-empty strings, and that each string + * corresponds to a PreferencesController identity. + * + * @param {string[]} accounts - The accounts associated with the caveat. + * @param {() => Record} getIdentities - Gets all + * PreferencesController identities. + */ +function validateCaveatAccounts(accounts, getIdentities) { + if (!Array.isArray(accounts) || accounts.length === 0) { + throw new Error( + `${PermissionKeys.eth_accounts} error: Expected non-empty array of Ethereum addresses.`, + ); + } + + const identities = getIdentities(); + accounts.forEach(({ address }) => { + if (!address || typeof address !== 'string') { + throw new Error( + `${PermissionKeys.eth_accounts} error: Expected an array of objects that contains an Ethereum addresses. Received: "${address}".`, + ); + } + + if (!identities[address.toLowerCase()]) { + throw new Error( + `${PermissionKeys.eth_accounts} error: Received unrecognized address: "${address}".`, + ); + } + }); +} + +/** + * All unrestricted methods recognized by the PermissionController. + * Unrestricted methods are ignored by the permission system, but every + * JSON-RPC request seen by the permission system must correspond to a + * restricted or unrestricted method, or the request will be rejected with a + * "method not found" error. + */ +export const unrestrictedMethods = Object.freeze([ + 'eth_blockNumber', + 'eth_call', + 'eth_chainId', + 'eth_coinbase', + 'eth_decrypt', + 'eth_estimateGas', + 'eth_feeHistory', + 'eth_gasPrice', + 'eth_getBalance', + 'eth_getBlockByHash', + 'eth_getBlockByNumber', + 'eth_getBlockTransactionCountByHash', + 'eth_getBlockTransactionCountByNumber', + 'eth_getCode', + 'eth_getEncryptionPublicKey', + 'eth_getFilterChanges', + 'eth_getFilterLogs', + 'eth_getLogs', + 'eth_getProof', + 'eth_getStorageAt', + 'eth_getTransactionByBlockHashAndIndex', + 'eth_getTransactionByBlockNumberAndIndex', + 'eth_getTransactionByHash', + 'eth_getTransactionCount', + 'eth_getTransactionReceipt', + 'eth_getUncleByBlockHashAndIndex', + 'eth_getUncleByBlockNumberAndIndex', + 'eth_getUncleCountByBlockHash', + 'eth_getUncleCountByBlockNumber', + 'eth_getWork', + 'eth_hashrate', + 'eth_mining', + 'eth_newBlockFilter', + 'eth_newFilter', + 'eth_newPendingTransactionFilter', + 'eth_protocolVersion', + 'eth_sendRawTransaction', + 'eth_sendTransaction', + 'eth_sign', + 'eth_signTypedData', + 'eth_signTypedData_v1', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + 'eth_submitHashrate', + 'eth_submitWork', + 'eth_syncing', + 'eth_uninstallFilter', + 'metamask_getProviderState', + 'metamask_watchAsset', + 'net_listening', + 'net_peerCount', + 'net_version', + 'personal_ecRecover', + 'personal_sign', + 'wallet_watchAsset', + 'web3_clientVersion', + 'web3_sha3', +]); diff --git a/app/core/Permissions/specifications.test.js b/app/core/Permissions/specifications.test.js new file mode 100644 index 00000000000..807db77a519 --- /dev/null +++ b/app/core/Permissions/specifications.test.js @@ -0,0 +1,260 @@ +import { CaveatTypes, RestrictedMethods } from './constants'; +import { + getCaveatSpecifications, + getPermissionSpecifications, + unrestrictedMethods, +} from './specifications'; + +// Note: This causes Date.now() to return the number 1. +jest.useFakeTimers('modern').setSystemTime(1); + +describe('PermissionController specifications', () => { + describe('caveat specifications', () => { + it('getCaveatSpecifications returns the expected specifications object', () => { + const caveatSpecifications = getCaveatSpecifications({}); + expect(Object.keys(caveatSpecifications)).toHaveLength(1); + expect( + caveatSpecifications[CaveatTypes.restrictReturnedAccounts].type, + ).toStrictEqual(CaveatTypes.restrictReturnedAccounts); + }); + + describe('restrictReturnedAccounts', () => { + describe('decorator', () => { + it('returns the first array member included in the caveat value', async () => { + const getIdentities = jest.fn(); + const { decorator } = getCaveatSpecifications({ getIdentities })[ + CaveatTypes.restrictReturnedAccounts + ]; + + const method = async () => ['0x1', '0x2', '0x3']; + const caveat = { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x1', '0x2'], + }; + const decorated = decorator(method, caveat); + expect(await decorated()).toStrictEqual(['0x1']); + }); + + it('returns an empty array if no array members are included in the caveat value', async () => { + const getIdentities = jest.fn(); + const { decorator } = getCaveatSpecifications({ getIdentities })[ + CaveatTypes.restrictReturnedAccounts + ]; + + const method = async () => ['0x1', '0x2', '0x3']; + const caveat = { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x5'], + }; + const decorated = decorator(method, caveat); + expect(await decorated()).toStrictEqual([]); + }); + + it('returns an empty array if the method result is an empty array', async () => { + const getIdentities = jest.fn(); + const { decorator } = getCaveatSpecifications({ getIdentities })[ + CaveatTypes.restrictReturnedAccounts + ]; + + const method = async () => []; + const caveat = { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x1', '0x2'], + }; + const decorated = decorator(method, caveat); + expect(await decorated()).toStrictEqual([]); + }); + }); + + describe('validator', () => { + it('rejects invalid array values', () => { + const getIdentities = jest.fn(); + const { validator } = getCaveatSpecifications({ getIdentities })[ + CaveatTypes.restrictReturnedAccounts + ]; + + [null, 'foo', {}, []].forEach((invalidValue) => { + expect(() => validator({ value: invalidValue })).toThrow( + /Expected non-empty array of Ethereum addresses\.$/u, + ); + }); + }); + + it('rejects falsy or non-string addresses', () => { + const getIdentities = jest.fn(); + const { validator } = getCaveatSpecifications({ getIdentities })[ + CaveatTypes.restrictReturnedAccounts + ]; + + [[{}], [[]], [null], ['']].forEach((invalidValue) => { + expect(() => validator({ value: invalidValue })).toThrow( + /Expected an array of Ethereum addresses. Received:/u, + ); + }); + }); + + it('rejects addresses that have no corresponding identity', () => { + const getIdentities = jest.fn().mockImplementationOnce(() => ({ + '0x1': true, + '0x3': true, + })); + + const { validator } = getCaveatSpecifications({ getIdentities })[ + CaveatTypes.restrictReturnedAccounts + ]; + + expect(() => validator({ value: ['0x1', '0x2', '0x3'] })).toThrow( + /Received unrecognized address:/u, + ); + }); + }); + }); + }); + + describe('permission specifications', () => { + it('getPermissionSpecifications returns the expected specifications object', () => { + const permissionSpecifications = getPermissionSpecifications({}); + expect(Object.keys(permissionSpecifications)).toHaveLength(1); + expect( + permissionSpecifications[RestrictedMethods.eth_accounts].targetKey, + ).toStrictEqual(RestrictedMethods.eth_accounts); + }); + + describe('eth_accounts', () => { + describe('factory', () => { + it('constructs a valid eth_accounts permission', () => { + const getAllAccounts = jest.fn(); + const { factory } = getPermissionSpecifications({ + getAllAccounts, + })[RestrictedMethods.eth_accounts]; + + expect( + factory( + { invoker: 'foo.bar', target: 'eth_accounts' }, + { approvedAccounts: ['0x1'] }, + ), + ).toStrictEqual({ + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x1'], + }, + ], + date: 1, + id: expect.any(String), + invoker: 'foo.bar', + parentCapability: 'eth_accounts', + }); + }); + + it('throws an error if no approvedAccounts are specified', () => { + const getAllAccounts = jest.fn(); + const { factory } = getPermissionSpecifications({ + getAllAccounts, + })[RestrictedMethods.eth_accounts]; + + expect(() => + factory( + { invoker: 'foo.bar', target: 'eth_accounts' }, + {}, // no approvedAccounts + ), + ).toThrow(/No approved accounts specified\.$/u); + }); + + it('throws an error if any caveats are specified directly', () => { + const getAllAccounts = jest.fn(); + const { factory } = getPermissionSpecifications({ + getAllAccounts, + })[RestrictedMethods.eth_accounts]; + + expect(() => + factory( + { + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x1', '0x2'], + }, + ], + invoker: 'foo.bar', + target: 'eth_accounts', + }, + { approvedAccounts: ['0x1'] }, + ), + ).toThrow(/Received unexpected caveats./u); + }); + }); + + describe('methodImplementation', () => { + it('returns the exact keyring accounts', async () => { + const getAllAccounts = jest + .fn() + .mockImplementationOnce(() => ['0x1', '0x2', '0x3', '0x4']); + + const { methodImplementation } = getPermissionSpecifications({ + getAllAccounts, + })[RestrictedMethods.eth_accounts]; + + expect(await methodImplementation()).toStrictEqual([ + '0x1', + '0x2', + '0x3', + '0x4', + ]); + }); + }); + + describe('validator', () => { + it('accepts valid permissions', () => { + const getAllAccounts = jest.fn(); + const { validator } = getPermissionSpecifications({ + getAllAccounts, + })[RestrictedMethods.eth_accounts]; + + expect(() => + validator({ + caveats: [ + { + type: CaveatTypes.restrictReturnedAccounts, + value: ['0x1', '0x2'], + }, + ], + date: 1, + id: expect.any(String), + invoker: 'foo.bar', + parentCapability: 'eth_accounts', + }), + ).not.toThrow(); + }); + + it('rejects invalid caveats', () => { + const getAllAccounts = jest.fn(); + const { validator } = getPermissionSpecifications({ + getAllAccounts, + })[RestrictedMethods.eth_accounts]; + + [null, [], [1, 2], [{ type: 'foobar' }]].forEach( + (invalidCaveatsValue) => { + expect(() => + validator({ + caveats: invalidCaveatsValue, + date: 1, + id: expect.any(String), + invoker: 'foo.bar', + parentCapability: 'eth_accounts', + }), + ).toThrow(/Invalid caveats./u); + }, + ); + }); + }); + }); + }); + + describe('unrestricted methods', () => { + it('defines the unrestricted methods', () => { + expect(Array.isArray(unrestrictedMethods)).toBe(true); + expect(Object.isFrozen(unrestrictedMethods)).toBe(true); + }); + }); +}); diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index 31135df0aeb..fa2ec4d6a93 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -17,6 +17,7 @@ import { store } from '../../store'; import { removeBookmark } from '../../actions/bookmarks'; import setOnboardingWizardStep from '../../actions/wizard'; import { v1 as random } from 'uuid'; +import { getPermittedAccounts } from '../Permissions'; const Engine = ImportedEngine as any; let appVersion = ''; @@ -32,9 +33,6 @@ interface RPCMethodsMiddleParameters { hostname: string; getProviderState: () => any; navigation: any; - getApprovedHosts: any; - setApprovedHosts: (approvedHosts: any) => void; - approveHost: (fullHostname: string) => void; url: { current: string }; title: { current: string }; icon: { current: string }; @@ -55,17 +53,34 @@ interface RPCMethodsMiddleParameters { injectHomePageScripts: (bookmarks?: []) => void; } -export const checkActiveAccountAndChainId = ({ +export const checkActiveAccountAndChainId = async ({ address, chainId, - activeAccounts, + // activeAccounts, + isWalletConnect, + hostname, }: any) => { + let isInvalidAccount = true; if (address) { - if ( - !activeAccounts || - !activeAccounts.length || - address.toLowerCase() !== activeAccounts?.[0]?.toLowerCase() - ) { + if (isWalletConnect) { + const selectedAddress = + Engine.context.PreferencesController.state.selectedAddress; + if (address.toLowerCase() !== selectedAddress.toLowerCase()) { + isInvalidAccount = false; + } + } else { + // For Browser use permissions + const accounts = await getPermittedAccounts(hostname); + const normalizedAccounts = accounts.map((_address: string) => + _address.toLowerCase(), + ); + const normalizedAddress = address.toLowerCase(); + + if (!normalizedAccounts.includes(normalizedAddress)) { + isInvalidAccount = false; + } + } + if (isInvalidAccount) { throw ethErrors.rpc.invalidParams({ message: `Invalid parameters: must provide an Ethereum address.`, }); @@ -115,9 +130,6 @@ export const getRpcMethodMiddleware = ({ hostname, getProviderState, navigation, - getApprovedHosts, - setApprovedHosts, - approveHost, // Website info url, title, @@ -137,20 +149,6 @@ export const getRpcMethodMiddleware = ({ }: RPCMethodsMiddleParameters) => // all user facing RPC calls not implemented by the provider createAsyncMiddleware(async (req: any, res: any, next: any) => { - const getAccounts = (): string[] => { - const { - privacy: { privacyMode }, - } = store.getState(); - - const selectedAddress = - Engine.context.PreferencesController.state.selectedAddress?.toLowerCase(); - - const isEnabled = - isWalletConnect || !privacyMode || getApprovedHosts()[hostname]; - - return isEnabled && selectedAddress ? [selectedAddress] : []; - }; - const checkTabActive = () => { if (!tabId) return true; const { browser } = store.getState(); @@ -230,55 +228,52 @@ export const getRpcMethodMiddleware = ({ }, eth_requestAccounts: async () => { const { params } = req; - const { - privacy: { privacyMode }, - } = store.getState(); - - let { selectedAddress } = Engine.context.PreferencesController.state; - selectedAddress = selectedAddress?.toLowerCase(); - - if ( - isWalletConnect || - !privacyMode || - ((!params || !params.force) && getApprovedHosts()[hostname]) - ) { + if (isWalletConnect) { + let { selectedAddress } = Engine.context.PreferencesController.state; + selectedAddress = selectedAddress?.toLowerCase(); res.result = [selectedAddress]; } else { - try { - await requestUserApproval({ - type: ApprovalTypes.CONNECT_ACCOUNTS, - requestData: { hostname }, - }); - const fullHostname = new URL(url.current).hostname; - approveHost?.(fullHostname); - setApprovedHosts?.({ - ...getApprovedHosts?.(), - [fullHostname]: true, - }); - - res.result = selectedAddress ? [selectedAddress] : []; - } catch (e) { - throw ethErrors.provider.userRejectedRequest( - 'User denied account authorization.', - ); + // Check against permitted accounts. + const permittedAccounts = await getPermittedAccounts(hostname); + if (!params?.force && permittedAccounts.length) { + res.result = permittedAccounts; + } else { + try { + checkTabActive(); + await Engine.context.ApprovalController.clear( + ethErrors.provider.userRejectedRequest(), + ); + await Engine.context.PermissionController.requestPermissions( + { + // origin: url.current, + origin: hostname, + }, + { eth_accounts: {} }, + { id: random() }, + ); + const acc = await getPermittedAccounts(hostname); + res.result = acc; + } catch (e) { + throw ethErrors.provider.userRejectedRequest( + 'User denied account authorization.', + ); + } } } }, eth_accounts: async () => { - res.result = await getAccounts(); + res.result = await getPermittedAccounts(hostname); }, - eth_coinbase: async () => { - const accounts = await getAccounts(); - res.result = accounts.length > 0 ? accounts[0] : null; + res.result = await getPermittedAccounts(hostname); }, - eth_sendTransaction: () => { + eth_sendTransaction: async () => { checkTabActive(); - checkActiveAccountAndChainId({ + await checkActiveAccountAndChainId({ address: req.params[0].from, chainId: req.params[0].chainId, - activeAccounts: getAccounts(), + isWalletConnect, }); next(); }, @@ -298,12 +293,12 @@ export const getRpcMethodMiddleware = ({ }; checkTabActive(); - checkActiveAccountAndChainId({ - address: req.params[0].from, - activeAccounts: getAccounts(), - }); if (req.params[1].length === 66 || req.params[1].length === 67) { + await checkActiveAccountAndChainId({ + address: req.params[0].from, + isWalletConnect, + }); const rawSig = await MessageManager.addUnapprovedMessageAsync({ data: req.params[1], from: req.params[0], @@ -342,9 +337,9 @@ export const getRpcMethodMiddleware = ({ }; checkTabActive(); - checkActiveAccountAndChainId({ + await checkActiveAccountAndChainId({ address: params.from, - activeAccounts: getAccounts(), + isWalletConnect, }); const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({ @@ -367,9 +362,9 @@ export const getRpcMethodMiddleware = ({ }; checkTabActive(); - checkActiveAccountAndChainId({ + await checkActiveAccountAndChainId({ address: req.params[1], - activeAccounts: getAccounts(), + isWalletConnect, }); const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( @@ -400,10 +395,10 @@ export const getRpcMethodMiddleware = ({ }; checkTabActive(); - checkActiveAccountAndChainId({ + await checkActiveAccountAndChainId({ address: req.params[0], chainId, - activeAccounts: getAccounts(), + isWalletConnect, }); const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( @@ -434,10 +429,10 @@ export const getRpcMethodMiddleware = ({ }; checkTabActive(); - checkActiveAccountAndChainId({ + await checkActiveAccountAndChainId({ address: req.params[0], chainId, - activeAccounts: getAccounts(), + isWalletConnect, }); const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( @@ -605,7 +600,7 @@ export const getRpcMethodMiddleware = ({ metamask_getProviderState: async () => { res.result = { ...getProviderState(), - accounts: await getAccounts(), + accounts: await getPermittedAccounts(hostname), }; }, diff --git a/app/core/WalletConnect.js b/app/core/WalletConnect.js index 82fd81c2a21..b1b55e3af7e 100644 --- a/app/core/WalletConnect.js +++ b/app/core/WalletConnect.js @@ -312,9 +312,6 @@ class WalletConnect { hostname: WALLET_CONNECT_ORIGIN + this.hostname, getProviderState, navigation: null, //props.navigation, - getApprovedHosts: () => null, - setApprovedHosts: () => null, - approveHost: () => null, //props.approveHost, // Website info url: this.url, title: this.title, diff --git a/app/reducers/privacy/index.js b/app/reducers/privacy/index.js index ac6d9825711..9d5237384e0 100644 --- a/app/reducers/privacy/index.js +++ b/app/reducers/privacy/index.js @@ -1,6 +1,5 @@ const initialState = { approvedHosts: {}, - privacyMode: true, thirdPartyApiMode: true, revealSRPTimestamps: [], }; @@ -27,11 +26,6 @@ const privacyReducer = (state = initialState, action) => { ...state, approvedHosts: {}, }; - case 'SET_PRIVACY_MODE': - return { - ...state, - privacyMode: action.enabled, - }; case 'SET_THIRD_PARTY_API_MODE': return { ...state, diff --git a/package.json b/package.json index 095885e0d87..c2779e116bc 100644 --- a/package.json +++ b/package.json @@ -168,6 +168,7 @@ "eth-json-rpc-filters": "4.2.2", "eth-json-rpc-infura": "5.1.0", "eth-json-rpc-middleware": "4.3.0", + "eth-rpc-errors": "^4.0.3", "eth-url-parser": "1.0.4", "ethereumjs-abi": "0.6.6", "ethereumjs-util": "6.1.0", diff --git a/yarn.lock b/yarn.lock index 673dd41ab48..241e704f1f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8319,7 +8319,7 @@ eth-rpc-errors@^3.0.0: dependencies: fast-safe-stringify "^2.0.6" -eth-rpc-errors@^4.0.0, eth-rpc-errors@^4.0.2: +eth-rpc-errors@^4.0.0, eth-rpc-errors@^4.0.2, eth-rpc-errors@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz#6ddb6190a4bf360afda82790bb7d9d5e724f423a" integrity sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg== From 3c264d81dfba63316dfa7a7a7b3492dd4f0c6138 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 28 Sep 2022 16:12:23 -0700 Subject: [PATCH 08/91] [PS] Connect ps to browser (#5051) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values * Organize account hooks and utils * Export ENS cache. Update account util to return ENS name. * Update account selector * Remove account selector styles * Hook up permission system controller logic * Connect PS to browser. Pass from address to transactions. --- app/actions/modals/index.js | 3 +- app/components/UI/AccountApproval/index.js | 4 +- .../__snapshots__/index.test.tsx.snap | 1 - app/components/UI/AccountInfoCard/index.js | 26 +- .../AccountRightButton.types.ts | 5 + .../__snapshots__/index.test.tsx.snap | 2 +- app/components/UI/AccountRightButton/index.js | 78 ------ .../UI/AccountRightButton/index.tsx | 95 +++++++ .../UI/ApproveTransactionReview/index.js | 22 +- .../__snapshots__/index.test.tsx.snap | 2 +- app/components/UI/MessageSign/index.js | 21 +- app/components/UI/Navbar/index.js | 19 +- app/components/UI/NetworkList/index.js | 8 +- app/components/UI/NetworkModal/index.tsx | 6 +- .../__snapshots__/index.test.tsx.snap | 2 +- app/components/UI/PersonalSign/index.js | 21 +- .../UI/QRHardware/QRSigningDetails.tsx | 7 +- .../UI/QRHardware/QRSigningModal/index.tsx | 5 + app/components/UI/SignatureRequest/index.js | 10 +- app/components/UI/TransactionReview/index.js | 10 +- .../__snapshots__/index.test.tsx.snap | 2 +- app/components/UI/TypedSign/index.js | 19 +- app/components/Views/ActivityView/index.js | 55 ++-- app/components/Views/BrowserTab/index.js | 264 +++++++++--------- .../NetworksSettings/NetworkSettings/index.js | 13 +- app/reducers/modals/index.js | 2 + 26 files changed, 384 insertions(+), 318 deletions(-) create mode 100644 app/components/UI/AccountRightButton/AccountRightButton.types.ts delete mode 100644 app/components/UI/AccountRightButton/index.js create mode 100644 app/components/UI/AccountRightButton/index.tsx diff --git a/app/actions/modals/index.js b/app/actions/modals/index.js index b8088bb9f94..1a538a46da9 100644 --- a/app/actions/modals/index.js +++ b/app/actions/modals/index.js @@ -1,7 +1,8 @@ // eslint-disable-next-line import/prefer-default-export -export function toggleNetworkModal() { +export function toggleNetworkModal(shouldNetworkSwitchPopToWallet = true) { return { type: 'TOGGLE_NETWORK_MODAL', + shouldNetworkSwitchPopToWallet, }; } diff --git a/app/components/UI/AccountApproval/index.js b/app/components/UI/AccountApproval/index.js index cbb79681bec..5702bcc5081 100644 --- a/app/components/UI/AccountApproval/index.js +++ b/app/components/UI/AccountApproval/index.js @@ -201,7 +201,7 @@ class AccountApproval extends PureComponent { }; render = () => { - const { currentPageInformation } = this.props; + const { currentPageInformation, selectedAddress } = this.props; const colors = this.context.colors || mockTheme.colors; const styles = createStyles(colors); @@ -211,7 +211,7 @@ class AccountApproval extends PureComponent { {strings('accountApproval.action')} {strings('accountApproval.warning')} - + `; diff --git a/app/components/UI/AccountInfoCard/index.js b/app/components/UI/AccountInfoCard/index.js index 96677161c34..2e00e62e274 100644 --- a/app/components/UI/AccountInfoCard/index.js +++ b/app/components/UI/AccountInfoCard/index.js @@ -84,6 +84,10 @@ const createStyles = (colors) => class AccountInfoCard extends PureComponent { static propTypes = { + /** + * A string that represents the from address. + */ + fromAddress: PropTypes.string.isRequired, /** * Map of accounts to information objects including balances */ @@ -92,10 +96,6 @@ class AccountInfoCard extends PureComponent { * List of accounts from the PreferencesController */ identities: PropTypes.object, - /** - * A string that represents the selected address - */ - selectedAddress: PropTypes.string, /** * A number that specifies the ETH/USD conversion rate */ @@ -124,8 +124,8 @@ class AccountInfoCard extends PureComponent { componentDidMount() { const { KeyringController } = Engine.context; - const { selectedAddress } = this.props; - KeyringController.getAccountKeyringType(selectedAddress).then((type) => { + const { fromAddress } = this.props; + KeyringController.getAccountKeyringType(fromAddress).then((type) => { if (type === QR_HARDWARE_WALLET_DEVICE) { this.setState({ isHardwareKeyring: true }); } @@ -135,21 +135,23 @@ class AccountInfoCard extends PureComponent { render() { const { accounts, - selectedAddress, identities, conversionRate, currentCurrency, operation, ticker, showFiatBalance = true, + fromAddress, } = this.props; const { isHardwareKeyring } = this.state; const colors = this.context.colors || mockTheme.colors; const styles = createStyles(colors); - const weiBalance = hexToBN(accounts[selectedAddress].balance); + const weiBalance = accounts?.[fromAddress]?.balance + ? hexToBN(accounts[fromAddress].balance) + : 0; const balance = `(${renderFromWei(weiBalance)} ${getTicker(ticker)})`; - const accountLabel = renderAccountName(selectedAddress, identities); - const address = renderShortAddress(selectedAddress); + const accountLabel = renderAccountName(fromAddress, identities); + const address = renderShortAddress(fromAddress); const dollarBalance = weiToFiat( weiBalance, conversionRate, @@ -159,7 +161,7 @@ class AccountInfoCard extends PureComponent { return ( @@ -211,8 +213,6 @@ class AccountInfoCard extends PureComponent { const mapStateToProps = (state) => ({ accounts: state.engine.backgroundState.AccountTrackerController.accounts, - selectedAddress: - state.engine.backgroundState.PreferencesController.selectedAddress, identities: state.engine.backgroundState.PreferencesController.identities, conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate, diff --git a/app/components/UI/AccountRightButton/AccountRightButton.types.ts b/app/components/UI/AccountRightButton/AccountRightButton.types.ts new file mode 100644 index 00000000000..4c094001361 --- /dev/null +++ b/app/components/UI/AccountRightButton/AccountRightButton.types.ts @@ -0,0 +1,5 @@ +export interface AccountRightButtonProps { + selectedAddress: string; + onPress: () => void; + isNetworkVisible?: boolean; +} diff --git a/app/components/UI/AccountRightButton/__snapshots__/index.test.tsx.snap b/app/components/UI/AccountRightButton/__snapshots__/index.test.tsx.snap index 4d9b41a0236..0a875c9e777 100644 --- a/app/components/UI/AccountRightButton/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/AccountRightButton/__snapshots__/index.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AccountRightButton should render correctly 1`] = ``; +exports[`AccountRightButton should render correctly 1`] = ``; diff --git a/app/components/UI/AccountRightButton/index.js b/app/components/UI/AccountRightButton/index.js deleted file mode 100644 index 8b0927478a0..00000000000 --- a/app/components/UI/AccountRightButton/index.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { TouchableOpacity, StyleSheet } from 'react-native'; -import Identicon from '../Identicon'; -import Device from '../../../util/device'; -import AnalyticsV2 from '../../../util/analyticsV2'; -import { withNavigation } from '@react-navigation/compat'; -import Routes from '../../../constants/navigation/Routes'; - -const styles = StyleSheet.create({ - leftButton: { - marginTop: 12, - marginRight: Device.isAndroid() ? 7 : 18, - marginLeft: Device.isAndroid() ? 7 : 0, - marginBottom: 12, - alignItems: 'center', - justifyContent: 'center', - }, -}); - -/** - * UI PureComponent that renders on the top right of the navbar - * showing an identicon for the selectedAddress - */ -class AccountRightButton extends PureComponent { - static propTypes = { - /** - * Selected address as string - */ - address: PropTypes.string, - /** - * List of accounts from the AccountTrackerController - */ - accounts: PropTypes.object, - /** - * Navigation object. - */ - navigation: PropTypes.object, - }; - - openAccountSelector = () => { - const { accounts, navigation } = this.props; - navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.ACCOUNT_SELECTOR, - }); - // Track Event: "Opened Acount Switcher" - AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.BROWSER_OPEN_ACCOUNT_SWITCH, - { - number_of_accounts: Object.keys(accounts ?? {}).length, - }, - ); - }; - - render = () => { - const { address } = this.props; - return ( - - - - ); - }; -} - -const mapStateToProps = (state) => ({ - address: state.engine.backgroundState.PreferencesController.selectedAddress, - accounts: state.engine.backgroundState.AccountTrackerController.accounts, -}); - -export default connect( - mapStateToProps, - null, -)(withNavigation(AccountRightButton)); diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx new file mode 100644 index 00000000000..f1b1fa8522f --- /dev/null +++ b/app/components/UI/AccountRightButton/index.tsx @@ -0,0 +1,95 @@ +import React, { useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { TouchableOpacity, StyleSheet } from 'react-native'; +import Device from '../../../util/device'; +import AvatarAccount, { + AvatarAccountType, +} from '../../../component-library/components/Avatars/AvatarAccount'; +import { AccountRightButtonProps } from './AccountRightButton.types'; +import AvatarNetwork from '../../../component-library/components/Avatars/AvatarNetwork'; +import { + getNetworkImageSource, + getNetworkNameFromProvider, +} from '../../../util/networks'; +import { toggleNetworkModal } from '../../../actions/modals'; +import { BadgeVariants } from '../../../component-library/components/Badges/Badge'; +import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrapper'; + +const styles = StyleSheet.create({ + leftButton: { + marginTop: 12, + marginRight: Device.isAndroid() ? 7 : 16, + marginLeft: Device.isAndroid() ? 7 : 0, + marginBottom: 12, + alignItems: 'center', + justifyContent: 'center', + }, +}); + +/** + * UI PureComponent that renders on the top right of the navbar + * showing an identicon for the selectedAddress + */ +const AccountRightButton = ({ + selectedAddress, + onPress, + isNetworkVisible, +}: AccountRightButtonProps) => { + const accountAvatarType = useSelector((state: any) => + state.settings.useBlockieIcon + ? AvatarAccountType.Blockies + : AvatarAccountType.JazzIcon, + ); + /** + * Current network + */ + const networkProvider = useSelector( + (state: any) => state.engine.backgroundState.NetworkController.provider, + ); + const dispatch = useDispatch(); + let onPressButton = onPress; + if (!selectedAddress && isNetworkVisible) { + onPressButton = () => dispatch(toggleNetworkModal(false)); + } + const networkName = useMemo( + () => getNetworkNameFromProvider(networkProvider), + [networkProvider], + ); + + const networkImageSource = useMemo( + () => getNetworkImageSource(networkProvider.chainId), + [networkProvider.chainId], + ); + + const renderAvatarAccount = () => ( + + ); + + return ( + + {selectedAddress ? ( + isNetworkVisible ? ( + + {renderAvatarAccount()} + + ) : ( + renderAvatarAccount() + ) + ) : ( + + )} + + ); +}; + +export default AccountRightButton; diff --git a/app/components/UI/ApproveTransactionReview/index.js b/app/components/UI/ApproveTransactionReview/index.js index 79c8c2f7d4d..1c76a81b7cd 100644 --- a/app/components/UI/ApproveTransactionReview/index.js +++ b/app/components/UI/ApproveTransactionReview/index.js @@ -201,10 +201,6 @@ class ApproveTransactionReview extends PureComponent { getApproveNavbar('approve.title', navigation); static propTypes = { - /** - * A string that represents the selected address - */ - selectedAddress: PropTypes.string, /** * Callback triggered when this transaction is cancelled */ @@ -412,12 +408,7 @@ class ApproveTransactionReview extends PureComponent { getAnalyticsParams = () => { try { - const { - activeTabUrl, - transaction, - onSetAnalyticsParams, - selectedAddress, - } = this.props; + const { activeTabUrl, transaction, onSetAnalyticsParams } = this.props; const { tokenSymbol, originalApproveAmount, encodedAmount } = this.state; const { NetworkController } = Engine.context; const { chainId, type } = NetworkController?.state?.provider || {}; @@ -426,7 +417,7 @@ class ApproveTransactionReview extends PureComponent { ); const unlimited = encodedAmount === UINT256_HEX_MAX_VALUE; const params = { - account_type: getAddressAccountType(selectedAddress), + account_type: getAddressAccountType(transaction?.from), dapp_host_name: transaction?.origin, dapp_url: isDapp ? activeTabUrl : undefined, network_name: type, @@ -661,7 +652,7 @@ class ApproveTransactionReview extends PureComponent { primaryCurrency, gasError, activeTabUrl, - transaction: { origin }, + transaction: { origin, from }, network, over, EIP1559GasData, @@ -776,7 +767,7 @@ class ApproveTransactionReview extends PureComponent { confirmDisabled={Boolean(gasError) || transactionConfirmed} > - + {showFeeMarket ? ( ); @@ -991,8 +983,6 @@ class ApproveTransactionReview extends PureComponent { const mapStateToProps = (state) => ({ accounts: state.engine.backgroundState.AccountTrackerController.accounts, - selectedAddress: - state.engine.backgroundState.PreferencesController.selectedAddress, conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate, ticker: state.engine.backgroundState.NetworkController.provider.ticker, diff --git a/app/components/UI/MessageSign/__snapshots__/index.test.tsx.snap b/app/components/UI/MessageSign/__snapshots__/index.test.tsx.snap index 4c07d5402d6..7a57787c8d0 100644 --- a/app/components/UI/MessageSign/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/MessageSign/__snapshots__/index.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MessageSign should render correctly 1`] = ` - */ class MessageSign extends PureComponent { static propTypes = { - /** - * A string that represents the selected address - */ - selectedAddress: PropTypes.string, /** * react-navigation object used for switching between screens */ @@ -76,12 +71,15 @@ class MessageSign extends PureComponent { getAnalyticsParams = () => { try { - const { currentPageInformation, selectedAddress } = this.props; + const { + currentPageInformation, + messageParams: { from }, + } = this.props; const { NetworkController } = Engine.context; const { chainId, type } = NetworkController?.state?.provider || {}; const url = new URL(currentPageInformation?.url); return { - account_type: getAddressAccountType(selectedAddress), + account_type: getAddressAccountType(from), dapp_host_name: url?.host, dapp_url: currentPageInformation?.url, network_name: type, @@ -216,6 +214,7 @@ class MessageSign extends PureComponent { navigation, showExpandedMessage, toggleExpandedMessage, + messageParams: { from }, } = this.props; const styles = this.getStyles(); @@ -236,6 +235,7 @@ class MessageSign extends PureComponent { toggleExpandedMessage={toggleExpandedMessage} type="ethSign" showWarning + fromAddress={from} > {this.renderMessageText()} @@ -246,9 +246,4 @@ class MessageSign extends PureComponent { MessageSign.contextType = ThemeContext; -const mapStateToProps = (state) => ({ - selectedAddress: - state.engine.backgroundState.PreferencesController.selectedAddress, -}); - -export default connect(mapStateToProps)(MessageSign); +export default MessageSign; diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index abcf80187ca..ee2d3eb17bd 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -122,6 +122,8 @@ export default function getNavbarOptions( disableNetwork = false, drawerRef, themeColors, + selectedAddress, + onRightButtonPress, ) { const innerStyles = StyleSheet.create({ headerStyle: { @@ -153,7 +155,12 @@ export default function getNavbarOptions( /> ), - headerRight: () => , + headerRight: () => ( + + ), headerStyle: innerStyles.headerStyle, headerTintColor: themeColors.primary.default, }; @@ -587,6 +594,10 @@ export function getBrowserViewNavbarOptions( const error = route.params?.error ?? ''; const icon = route.params?.icon; + const setAccountsPermissionsVisible = + route.params?.setAccountsPermissionsVisible; + const connectedAccounts = route.params?.connectedAccounts; + if (url && !isHomepage(url)) { isHttps = url && url.toLowerCase().substr(0, 6) === 'https:'; const urlObj = new URL(url); @@ -639,7 +650,11 @@ export function getBrowserViewNavbarOptions( ), headerRight: () => ( - + ), headerStyle: innerStyles.headerStyle, diff --git a/app/components/UI/NetworkList/index.js b/app/components/UI/NetworkList/index.js index 4630b0095e4..1dc6e8613a2 100644 --- a/app/components/UI/NetworkList/index.js +++ b/app/components/UI/NetworkList/index.js @@ -166,6 +166,10 @@ export class NetworkList extends PureComponent { * react-navigation object used for switching between screens */ navigation: PropTypes.object, + /** + * Boolean indicating if switching network action should result in popping back to the wallet. + */ + shouldNetworkSwitchPopToWallet: PropTypes.bool, }; getOtherNetworks = () => getAllNetworks().slice(1); @@ -373,12 +377,13 @@ export class NetworkList extends PureComponent { } goToNetworkSettings = () => { + const { shouldNetworkSwitchPopToWallet } = this.props; this.props.onClose(false); this.props.navigation.navigate('SettingsView', { screen: 'SettingsFlow', params: { screen: 'NetworkSettings', - params: { isFullScreenModal: true }, + params: { isFullScreenModal: true, shouldNetworkSwitchPopToWallet }, }, }); }; @@ -431,6 +436,7 @@ const mapStateToProps = (state) => ({ state.engine.backgroundState.PreferencesController.frequentRpcList, thirdPartyApiMode: state.privacy.thirdPartyApiMode, networkOnboardedState: state.networkOnboarded.networkOnboardedState, + shouldNetworkSwitchPopToWallet: state.modals.shouldNetworkSwitchPopToWallet, }); NetworkList.contextType = ThemeContext; diff --git a/app/components/UI/NetworkModal/index.tsx b/app/components/UI/NetworkModal/index.tsx index 8895cbbefd9..89a9964bf98 100644 --- a/app/components/UI/NetworkModal/index.tsx +++ b/app/components/UI/NetworkModal/index.tsx @@ -106,6 +106,7 @@ interface NetworkProps { onClose: () => void; network: any; navigation: any; + shouldNetworkSwitchPopToWallet: boolean; } const NetworkModals = (props: NetworkProps) => { @@ -121,6 +122,7 @@ const NetworkModals = (props: NetworkProps) => { formattedRpcUrl, rpcPrefs: { blockExplorerUrl, imageUrl }, }, + shouldNetworkSwitchPopToWallet, } = props; const [showDetails, setShowDetails] = React.useState(false); @@ -198,7 +200,9 @@ const NetworkModals = (props: NetworkProps) => { CurrencyRateController.setNativeCurrency(ticker); NetworkController.setRpcTarget(url.href, decimalChainId, ticker, nickname); closeModal(); - navigation.navigate('WalletView'); + shouldNetworkSwitchPopToWallet + ? navigation.navigate('WalletView') + : navigation.goBack(); dispatch(networkSwitched({ networkUrl: url.href, networkStatus: true })); }; diff --git a/app/components/UI/PersonalSign/__snapshots__/index.test.tsx.snap b/app/components/UI/PersonalSign/__snapshots__/index.test.tsx.snap index e8a189a4fc0..3f3d6f012fa 100644 --- a/app/components/UI/PersonalSign/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/PersonalSign/__snapshots__/index.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PersonalSign should render correctly 1`] = ` - */ class PersonalSign extends PureComponent { static propTypes = { - /** - * A string that represents the selected address - */ - selectedAddress: PropTypes.string, /** * react-navigation object used for switching between screens */ @@ -80,13 +75,16 @@ class PersonalSign extends PureComponent { getAnalyticsParams = () => { try { - const { currentPageInformation, selectedAddress } = this.props; + const { + currentPageInformation, + messageParams: { from }, + } = this.props; const { NetworkController } = Engine.context; const { chainId, type } = NetworkController?.state?.provider || {}; const url = new URL(currentPageInformation?.url); return { - account_type: getAddressAccountType(selectedAddress), + account_type: getAddressAccountType(from), dapp_host_name: url?.host, dapp_url: currentPageInformation?.url, network_name: type, @@ -240,6 +238,7 @@ class PersonalSign extends PureComponent { currentPageInformation, toggleExpandedMessage, showExpandedMessage, + messageParams: { from }, } = this.props; const styles = this.getStyles(); @@ -259,6 +258,7 @@ class PersonalSign extends PureComponent { toggleExpandedMessage={toggleExpandedMessage} truncateMessage={this.state.truncateMessage} type="personalSign" + fromAddress={from} > {this.renderMessageText()} @@ -269,9 +269,4 @@ class PersonalSign extends PureComponent { PersonalSign.contextType = ThemeContext; -const mapStateToProps = (state) => ({ - selectedAddress: - state.engine.backgroundState.PreferencesController.selectedAddress, -}); - -export default connect(mapStateToProps)(PersonalSign); +export default PersonalSign; diff --git a/app/components/UI/QRHardware/QRSigningDetails.tsx b/app/components/UI/QRHardware/QRSigningDetails.tsx index 77617b15485..76f42754ad8 100644 --- a/app/components/UI/QRHardware/QRSigningDetails.tsx +++ b/app/components/UI/QRHardware/QRSigningDetails.tsx @@ -44,6 +44,7 @@ interface IQRSigningDetails { showHint?: boolean; shouldStartAnimated?: boolean; bypassAndroidCameraAccessCheck?: boolean; + fromAddress: string; } const createStyles = (colors: any) => @@ -122,6 +123,7 @@ const QRSigningDetails = ({ showHint = true, shouldStartAnimated = true, bypassAndroidCameraAccessCheck = true, + fromAddress, }: IQRSigningDetails) => { const { colors } = useTheme(); const styles = createStyles(colors); @@ -288,7 +290,10 @@ const QRSigningDetails = ({ ]} > - + {renderAlert()} {renderCameraAlert()} diff --git a/app/components/UI/QRHardware/QRSigningModal/index.tsx b/app/components/UI/QRHardware/QRSigningModal/index.tsx index 9786f7783fd..8d26c7ff9d9 100644 --- a/app/components/UI/QRHardware/QRSigningModal/index.tsx +++ b/app/components/UI/QRHardware/QRSigningModal/index.tsx @@ -4,6 +4,8 @@ import { IQRState } from '../types'; import { StyleSheet, View } from 'react-native'; import QRSigningDetails from '../QRSigningDetails'; import { useTheme } from '../../../../util/theme'; +import { useSelector } from 'react-redux'; +import { getNormalizedTxState } from '../../../../util/transactions'; interface IQRSigningModalProps { isVisible: boolean; @@ -39,6 +41,8 @@ const QRSigningModal = ({ const { colors } = useTheme(); const styles = createStyles(colors); const [isModalCompleteShow, setModalCompleteShow] = useState(false); + const { from } = useSelector(getNormalizedTxState); + return ( diff --git a/app/components/UI/SignatureRequest/index.js b/app/components/UI/SignatureRequest/index.js index 849864d4444..5d65f854abc 100644 --- a/app/components/UI/SignatureRequest/index.js +++ b/app/components/UI/SignatureRequest/index.js @@ -149,6 +149,10 @@ class SignatureRequest extends PureComponent { * Expands the message box on press. */ toggleExpandedMessage: PropTypes.func, + /** + * Active address of account that triggered signing. + */ + fromAddress: PropTypes.string, isSigningQRObject: PropTypes.bool, QRState: PropTypes.object, }; @@ -224,6 +228,7 @@ class SignatureRequest extends PureComponent { currentPageInformation, truncateMessage, toggleExpandedMessage, + fromAddress, } = this.props; const styles = this.getStyles(); const url = currentPageInformation.url; @@ -232,7 +237,7 @@ class SignatureRequest extends PureComponent { return ( - + ); diff --git a/app/components/UI/TransactionReview/index.js b/app/components/UI/TransactionReview/index.js index 311d5c6c836..9a60c8367c7 100644 --- a/app/components/UI/TransactionReview/index.js +++ b/app/components/UI/TransactionReview/index.js @@ -385,6 +385,7 @@ class TransactionReview extends PureComponent { dappSuggestedGasWarning, gasSelected, chainId, + transaction: { from }, } = this.props; const { actionKey, @@ -433,7 +434,7 @@ class TransactionReview extends PureComponent { onStartShouldSetResponder={() => true} > - + @@ -494,6 +499,7 @@ class TransactionReview extends PureComponent { showCancelButton showHint={false} bypassAndroidCameraAccessCheck={false} + fromAddress={from} /> ); diff --git a/app/components/UI/TypedSign/__snapshots__/index.test.tsx.snap b/app/components/UI/TypedSign/__snapshots__/index.test.tsx.snap index 31243e40695..ecdb5cea9fc 100644 --- a/app/components/UI/TypedSign/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/TypedSign/__snapshots__/index.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TypedSign should render correctly 1`] = ` - */ class TypedSign extends PureComponent { static propTypes = { - /** - * A string that represents the selected address - */ - selectedAddress: PropTypes.string, /** * react-navigation object used for switching between screens */ @@ -86,13 +81,12 @@ class TypedSign extends PureComponent { getAnalyticsParams = () => { try { - const { currentPageInformation, messageParams, selectedAddress } = - this.props; + const { currentPageInformation, messageParams } = this.props; const { NetworkController } = Engine.context; const { chainId, type } = NetworkController?.state?.provider || {}; const url = new URL(currentPageInformation?.url); return { - account_type: getAddressAccountType(selectedAddress), + account_type: getAddressAccountType(messageParams.from), dapp_host_name: url?.host, dapp_url: currentPageInformation?.url, network_name: type, @@ -249,6 +243,7 @@ class TypedSign extends PureComponent { currentPageInformation, showExpandedMessage, toggleExpandedMessage, + messageParams: { from }, } = this.props; const { truncateMessage } = this.state; const messageWrapperStyles = []; @@ -283,6 +278,7 @@ class TypedSign extends PureComponent { currentPageInformation={currentPageInformation} truncateMessage={truncateMessage} type="typedSign" + fromAddress={from} > ({ - selectedAddress: - state.engine.backgroundState.PreferencesController.selectedAddress, -}); - -export default connect(mapStateToProps)(TypedSign); +export default TypedSign; diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js index 6299d9b318c..2e70eb84f9d 100644 --- a/app/components/Views/ActivityView/index.js +++ b/app/components/Views/ActivityView/index.js @@ -1,8 +1,7 @@ import React, { useEffect, useContext } from 'react'; -import PropTypes from 'prop-types'; import { View, StyleSheet } from 'react-native'; import ScrollableTabView from 'react-native-scrollable-tab-view'; -import { connect } from 'react-redux'; +import { useSelector } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { getHasOrders } from '../../../reducers/fiatOrders'; import getNavbarOptions from '../../UI/Navbar'; @@ -13,6 +12,8 @@ import FiatOrdersView from '../FiatOrdersView'; import ErrorBoundary from '../ErrorBoundary'; import { DrawerContext } from '../../Nav/Main/MainNavigator'; import { useTheme } from '../../../util/theme'; +import Routes from '../../../constants/navigation/Routes'; +import AnalyticsV2 from '../../../util/analyticsV2'; const styles = StyleSheet.create({ wrapper: { @@ -20,19 +21,49 @@ const styles = StyleSheet.create({ }, }); -function ActivityView({ hasOrders }) { +const ActivityView = () => { const { drawerRef } = useContext(DrawerContext); const { colors } = useTheme(); const navigation = useNavigation(); + const selectedAddress = useSelector( + (state) => + state.engine.backgroundState.PreferencesController.selectedAddress, + ); + const hasOrders = useSelector((state) => getHasOrders(state) || false); + const accounts = useSelector( + (state) => state.engine.backgroundState.AccountTrackerController.accounts, + ); + + const openAccountSelector = () => { + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_SELECTOR, + }); + // Track Event: "Opened Acount Switcher" + AnalyticsV2.trackEvent( + AnalyticsV2.ANALYTICS_EVENTS.BROWSER_OPEN_ACCOUNT_SWITCH, + { + number_of_accounts: Object.keys(accounts ?? {}).length, + }, + ); + }; useEffect( () => { const title = hasOrders ?? false ? 'activity_view.title' : 'transactions_view.title'; - navigation.setOptions(getNavbarOptions(title, false, drawerRef, colors)); + navigation.setOptions( + getNavbarOptions( + title, + false, + drawerRef, + colors, + selectedAddress, + openAccountSelector, + ), + ); }, /* eslint-disable-next-line */ - [navigation, hasOrders, colors], + [navigation, hasOrders, colors, selectedAddress, openAccountSelector], ); const renderTabBar = () => (hasOrders ? : ); @@ -53,18 +84,6 @@ function ActivityView({ hasOrders }) { ); -} - -ActivityView.defaultProps = { - hasOrders: false, }; -ActivityView.propTypes = { - hasOrders: PropTypes.bool, -}; - -const mapStateToProps = (state) => ({ - hasOrders: getHasOrders(state), -}); - -export default connect(mapStateToProps)(ActivityView); +export default ActivityView; diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index 494461048df..bbcbafc4836 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -1,4 +1,10 @@ -import React, { useState, useRef, useEffect, useCallback } from 'react'; +import React, { + useState, + useRef, + useEffect, + useCallback, + useContext, +} from 'react'; import { Text, StyleSheet, @@ -16,7 +22,7 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc import BrowserBottomBar from '../../UI/BrowserBottomBar'; import PropTypes from 'prop-types'; import Share from 'react-native-share'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import BackgroundBridge from '../../../core/BackgroundBridge'; import Engine from '../../../core/Engine'; import PhishingModal from '../../UI/PhishingModal'; @@ -35,7 +41,6 @@ import { strings } from '../../../../locales/i18n'; import URL from 'url-parse'; import Modal from 'react-native-modal'; import WebviewError from '../../UI/WebviewError'; -import { approveHost } from '../../../actions/privacy'; import { addBookmark } from '../../../actions/bookmarks'; import { addToHistory, addToWhitelist } from '../../../actions/browser'; import Device from '../../../util/device'; @@ -44,7 +49,6 @@ import SearchApi from 'react-native-search-api'; import Analytics from '../../../core/Analytics/Analytics'; import AnalyticsV2, { trackErrorAsAnalytics } from '../../../util/analyticsV2'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; -import { toggleNetworkModal } from '../../../actions/modals'; import setOnboardingWizardStep from '../../../actions/wizard'; import OnboardingWizard from '../../UI/OnboardingWizard'; import DrawerStatusTracker from '../../../core/DrawerStatusTracker'; @@ -62,6 +66,18 @@ import { MM_ETHERSCAN_URL, } from '../../../constants/urls'; import sanitizeUrlInput from '../../../util/url/sanitizeUrlInput'; +import { + getPermittedAccounts, + getPermittedAccountsByHostname, +} from '../../../core/Permissions'; +import Routes from '../../../constants/navigation/Routes'; +import { isEqual } from 'lodash'; +import { + ToastContext, + ToastVariant, +} from '../../../component-library/components/Toast'; +import { useAccounts } from '../../hooks/useAccounts'; +import getAccountNameWithENS from '../../../util/accounts'; const { HOMEPAGE_URL, USER_AGENT, NOTIFICATION_NAMES } = AppConstants; const HOMEPAGE_HOST = new URL(HOMEPAGE_URL)?.hostname; @@ -204,12 +220,6 @@ const createStyles = (colors, shadows) => const sessionENSNames = {}; const ensIgnoreList = []; -let approvedHosts = {}; - -const getApprovedHosts = () => approvedHosts; -const setApprovedHosts = (hosts) => { - approvedHosts = hosts; -}; export const BrowserTab = (props) => { const [backEnabled, setBackEnabled] = useState(false); @@ -233,6 +243,18 @@ export const BrowserTab = (props) => { const backgroundBridges = useRef([]); const fromHomepage = useRef(false); const wizardScrollAdjusted = useRef(false); + const { toastRef } = useContext(ToastContext); + const { accounts, ensByAccountAddress } = useAccounts(); + const permittedAccountsList = useSelector((state) => { + const permissionsControllerState = + state.engine.backgroundState.PermissionController; + const hostname = new URL(url.current).hostname; + const permittedAcc = getPermittedAccountsByHostname( + permissionsControllerState, + hostname, + ); + return permittedAcc; + }, isEqual); const { colors, shadows } = useTheme(); const styles = createStyles(colors, shadows); @@ -240,9 +262,8 @@ export const BrowserTab = (props) => { /** * Is the current tab the active tab */ - const isTabActive = useCallback( - () => props.activeTab === props.id, - [props.activeTab, props.id], + const isTabActive = useSelector( + (state) => state.browser.activeTab === props.id, ); /** @@ -301,57 +322,22 @@ export const BrowserTab = (props) => { return currentHost === HOMEPAGE_HOST; }, []); - const notifyAllConnections = useCallback( - (payload, restricted = true) => { - const fullHostname = new URL(url.current).hostname; - - // TODO:permissions move permissioning logic elsewhere - backgroundBridges.current.forEach((bridge) => { - if ( - bridge.hostname === fullHostname && - (!props.privacyMode || !restricted || approvedHosts[bridge.hostname]) - ) { - bridge.sendNotification(payload); - } - }); - }, - [props.privacyMode], - ); - - /** - * Manage hosts that were approved to connect with the user accounts - */ - useEffect(() => { - const { approvedHosts: approvedHostsProps, selectedAddress } = props; - - approvedHosts = approvedHostsProps; - - const numApprovedHosts = Object.keys(approvedHosts).length; - - // this will happen if the approved hosts were cleared - if (numApprovedHosts === 0) { - notifyAllConnections( - { - method: NOTIFICATION_NAMES.accountsChanged, - params: [], - }, - false, - ); // notification should be sent regardless of approval status - } + const notifyAllConnections = useCallback((payload, restricted = true) => { + const fullHostname = new URL(url.current).hostname; - if (numApprovedHosts > 0) { - notifyAllConnections({ - method: NOTIFICATION_NAMES.accountsChanged, - params: [selectedAddress], - }); - } - }, [notifyAllConnections, props, props.approvedHosts, props.selectedAddress]); + // TODO:permissions move permissioning logic elsewhere + backgroundBridges.current.forEach((bridge) => { + if (bridge.hostname === fullHostname) { + bridge.sendNotification(payload); + } + }); + }, []); /** * Dismiss the text selection on the current website */ const dismissTextSelectionIfNeeded = useCallback(() => { - if (isTabActive() && Device.isAndroid()) { + if (isTabActive && Device.isAndroid()) { const { current } = webviewRef; if (current) { setTimeout(() => { @@ -579,7 +565,6 @@ export const BrowserTab = (props) => { * Set initial url, dapp scripts and engine. Similar to componentDidMount */ useEffect(() => { - approvedHosts = props.approvedHosts; const initialUrl = props.initialUrl || HOMEPAGE_URL; go(initialUrl, true); @@ -619,7 +604,7 @@ export const BrowserTab = (props) => { */ useEffect(() => { const handleAndroidBackPress = () => { - if (!isTabActive()) return false; + if (!isTabActive) return false; goBack(); return true; }; @@ -673,7 +658,31 @@ export const BrowserTab = (props) => { /** * Handles state changes for when the url changes */ - const changeUrl = (siteInfo) => { + const changeUrl = async (siteInfo) => { + if (siteInfo.url !== url.current) { + const hostname = new URL(siteInfo.url).hostname; + const permittedAccounts = await getPermittedAccounts(hostname); + const activeAccountAddress = permittedAccounts?.[0]; + if (activeAccountAddress) { + const accountName = getAccountNameWithENS({ + accountAddress: activeAccountAddress, + accounts, + ensByAccountAddress, + }); + // Show active account toast + toastRef?.current?.showToast({ + variant: ToastVariant.Account, + labelOptions: [ + { + label: accountName, + isBold: true, + }, + { label: strings('toast.now_active') }, + ], + accountAddress: activeAccountAddress, + }); + } + } url.current = siteInfo.url; title.current = siteInfo.title; if (siteInfo.icon) icon.current = siteInfo.icon; @@ -686,7 +695,7 @@ export const BrowserTab = (props) => { setBackEnabled(siteInfo.canGoBack); setForwardEnabled(siteInfo.canGoForward); - isTabActive() && + isTabActive && props.navigation.setParams({ url: getMaskedUrl(siteInfo.url), icon: siteInfo.icon, @@ -942,9 +951,6 @@ export const BrowserTab = (props) => { hostname, getProviderState, navigation: props.navigation, - getApprovedHosts, - setApprovedHosts, - approveHost: props.approveHost, // Website info url, title, @@ -964,6 +970,23 @@ export const BrowserTab = (props) => { backgroundBridges.current.push(newBridge); }; + const sendActiveAccount = useCallback(async () => { + const hostname = new URL(url.current).hostname; + const accounts = await getPermittedAccounts(hostname); + + notifyAllConnections({ + method: NOTIFICATION_NAMES.accountsChanged, + params: accounts, + }); + + if (isTabActive) { + props.navigation.setParams({ + connectedAccounts: accounts, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [notifyAllConnections, permittedAccountsList, isTabActive]); + /** * Website started to load */ @@ -985,6 +1008,7 @@ export const BrowserTab = (props) => { setError(false); changeUrl(nativeEvent); + sendActiveAccount(); icon.current = null; if (isHomepage(nativeEvent.url)) { @@ -1003,19 +1027,46 @@ export const BrowserTab = (props) => { * Enable the header to toggle the url modal and update other header data */ useEffect(() => { - if (props.activeTab === props.id) { - props.navigation.setParams({ - showUrlModal: toggleUrlModal, - url: getMaskedUrl(url.current), - icon: icon.current, - error, - }); - } + const updateNavbar = async () => { + if (isTabActive) { + const hostname = new URL(url.current).hostname; + const accounts = await getPermittedAccounts(hostname); + props.navigation.setParams({ + showUrlModal: toggleUrlModal, + url: getMaskedUrl(url.current), + icon: icon.current, + error, + setAccountsPermissionsVisible: () => { + props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_PERMISSIONS, + params: { + hostInfo: { + metadata: { + // origin: url.current, + origin: url.current && new URL(url.current).hostname, + }, + }, + }, + }); + }, + connectedAccounts: accounts, + }); + } + }; + + updateNavbar(); /* we do not want to depend on the entire props object - since we are changing it here, this would give us a circular dependency and infinite re renders */ // eslint-disable-next-line react-hooks/exhaustive-deps - }, [error, props.activeTab, props.id, toggleUrlModal]); + }, [error, isTabActive, toggleUrlModal]); + + /** + * Check whenever permissions change / account changes for Dapp + */ + useEffect(() => { + sendActiveAccount(); + }, [sendActiveAccount, permittedAccountsList]); /** * Allow list updates do not propigate through the useCallbacks this updates a ref that is use in the callbacks @@ -1071,18 +1122,6 @@ export const BrowserTab = (props) => { AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.BROWSER_SHARE_SITE); }; - /** - * Track change network event - */ - const trackSwitchNetworkEvent = ({ from }) => { - AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.BROWSER_SWITCH_NETWORK, - { - from_chain_id: from, - }, - ); - }; - /** * Track reload site event */ @@ -1217,16 +1256,6 @@ export const BrowserTab = (props) => { trackNewTabEvent(); }; - /** - * Handle switch network press - */ - const switchNetwork = () => { - const { toggleNetworkModal, network } = props; - toggleOptionsIfNeeded(); - toggleNetworkModal(); - trackSwitchNetworkEvent({ from: network }); - }; - /** * Render options menu */ @@ -1256,18 +1285,6 @@ export const BrowserTab = (props) => { {renderNonHomeOptions()} - @@ -1342,7 +1359,7 @@ export const BrowserTab = (props) => { return ( @@ -1376,10 +1393,10 @@ export const BrowserTab = (props) => { {updateAllowList()} {renderProgressBar()} - {isTabActive() && renderPhishingModal()} - {isTabActive() && renderOptions()} - {isTabActive() && renderBottomBar()} - {isTabActive() && renderOnboardingWizard()} + {isTabActive && renderPhishingModal()} + {isTabActive && renderOptions()} + {isTabActive && renderBottomBar()} + {isTabActive && renderOnboardingWizard()} ); @@ -1398,14 +1415,6 @@ BrowserTab.propTypes = { * InitialUrl */ initialUrl: PropTypes.string, - /** - * Called to approve account access for a given hostname - */ - approveHost: PropTypes.func, - /** - * Map of hostnames with approved account access - */ - approvedHosts: PropTypes.object, /** * Protocol string to append to URLs that have none */ @@ -1426,10 +1435,6 @@ BrowserTab.propTypes = { * A string representing the network id */ network: PropTypes.string, - /** - * Indicates whether privacy mode is enabled - */ - privacyMode: PropTypes.bool, /** * A string that represents the selected address */ @@ -1443,10 +1448,6 @@ BrowserTab.propTypes = { * For ex. deeplinks */ url: PropTypes.string, - /** - * Function to toggle the network switcher modal - */ - toggleNetworkModal: PropTypes.func, /** * Function to open a new tab */ @@ -1498,25 +1499,20 @@ BrowserTab.defaultProps = { }; const mapStateToProps = (state) => ({ - approvedHosts: state.privacy.approvedHosts, bookmarks: state.bookmarks, ipfsGateway: state.engine.backgroundState.PreferencesController.ipfsGateway, network: state.engine.backgroundState.NetworkController.network, selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress?.toLowerCase(), - privacyMode: state.privacy.privacyMode, searchEngine: state.settings.searchEngine, whitelist: state.browser.whitelist, - activeTab: state.browser.activeTab, wizardStep: state.wizard.step, }); const mapDispatchToProps = (dispatch) => ({ - approveHost: (hostname) => dispatch(approveHost(hostname)), addBookmark: (bookmark) => dispatch(addBookmark(bookmark)), addToBrowserHistory: ({ url, name }) => dispatch(addToHistory({ url, name })), addToWhitelist: (url) => dispatch(addToWhitelist(url)), - toggleNetworkModal: () => dispatch(toggleNetworkModal()), setOnboardingWizardStep: (step) => dispatch(setOnboardingWizardStep(step)), }); diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js index 2bdfc933688..895095bb4f5 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js @@ -420,7 +420,9 @@ class NetworkSettings extends PureComponent { enableAction, } = this.state; const ticker = this.state.ticker && this.state.ticker.toUpperCase(); - const { navigation, networkOnboardedState } = this.props; + const { navigation, route, networkOnboardedState } = this.props; + const shouldNetworkSwitchPopToWallet = + route.params?.shouldNetworkSwitchPopToWallet ?? true; // Check if CTA is disabled const isCtaDisabled = !enableAction || this.disabledByRpcUrl() || this.disabledByChainId(); @@ -497,7 +499,9 @@ class NetworkSettings extends PureComponent { nativeToken, showNetworkOnboarding, }); - navigation.navigate('WalletView'); + shouldNetworkSwitchPopToWallet + ? navigation.navigate('WalletView') + : navigation.goBack(); } }; @@ -967,6 +971,8 @@ class NetworkSettings extends PureComponent { render() { const { navigation, route } = this.props; const network = route.params?.network; + const shouldNetworkSwitchPopToWallet = + route.params?.shouldNetworkSwitchPopToWallet ?? true; const colors = this.context.colors || mockTheme.colors; const styles = createStyles(colors); @@ -994,6 +1000,9 @@ class NetworkSettings extends PureComponent { onClose={this.onCancel} network={this.state.popularNetwork} navigation={navigation} + shouldNetworkSwitchPopToWallet={ + shouldNetworkSwitchPopToWallet + } /> )} diff --git a/app/reducers/modals/index.js b/app/reducers/modals/index.js index 1f2d416d1d0..2cef39f3510 100644 --- a/app/reducers/modals/index.js +++ b/app/reducers/modals/index.js @@ -1,5 +1,6 @@ const initialState = { networkModalVisible: false, + shouldNetworkSwitchPopToWallet: true, collectibleContractModalVisible: false, receiveModalVisible: false, receiveAsset: undefined, @@ -13,6 +14,7 @@ const modalsReducer = (state = initialState, action) => { return { ...state, networkModalVisible: !state.networkModalVisible, + shouldNetworkSwitchPopToWallet: action.shouldNetworkSwitchPopToWallet, }; case 'TOGGLE_RECEIVE_MODAL': { return { From caf410f78ff1f8991c6cd285171cce3214405bd3 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 28 Sep 2022 16:12:41 -0700 Subject: [PATCH 09/91] [PS] Create first time connect sheet (#5052) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values * Organize account hooks and utils * Export ENS cache. Update account util to return ENS name. * Update account selector * Remove account selector styles * Hook up permission system controller logic * Connect PS to browser. Pass from address to transactions. * Create first time connect sheet --- app/components/Nav/App/index.js | 5 + .../Views/AccountConnect/AccountConnect.tsx | 302 ++++++++++++++++++ .../AccountConnect/AccountConnect.types.ts | 22 ++ .../AccountConnectMultiSelector.styles.ts | 41 +++ .../AccountConnectMultiSelector.tsx | 191 +++++++++++ .../AccountConnectMultiSelector.types.ts | 21 ++ .../AccountConnectMultiSelector/index.ts | 1 + .../AccountConnectSingle.styles.ts | 42 +++ .../AccountConnectSingle.tsx | 160 ++++++++++ .../AccountConnectSingle.types.ts | 22 ++ .../AccountConnectSingle/index.ts | 1 + .../AccountConnectSingleSelector.styles.ts | 14 + .../AccountConnectSingleSelector.tsx | 111 +++++++ .../AccountConnectSingleSelector.types.ts | 15 + .../AccountConnectSingleSelector/index.ts | 1 + app/components/Views/AccountConnect/index.ts | 2 + app/constants/navigation/Routes.ts | 1 + 17 files changed, 952 insertions(+) create mode 100644 app/components/Views/AccountConnect/AccountConnect.tsx create mode 100644 app/components/Views/AccountConnect/AccountConnect.types.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.styles.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx create mode 100644 app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectMultiSelector/index.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.styles.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx create mode 100644 app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectSingle/index.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.styles.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx create mode 100644 app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts create mode 100644 app/components/Views/AccountConnect/AccountConnectSingleSelector/index.ts create mode 100644 app/components/Views/AccountConnect/index.ts diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index 4b638861481..3a008a2bcff 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -55,6 +55,7 @@ import Toast, { ToastContext, } from '../../../component-library/components/Toast'; import AccountSelector from '../../../components/Views/AccountSelector'; +import AccountConnect from '../../../components/Views/AccountConnect'; import { TurnOffRememberMeModal } from '../../../components/UI/TurnOffRememberMeModal'; const Stack = createStackNavigator(); @@ -361,6 +362,10 @@ const App = ({ userLoggedIn }) => { name={Routes.SHEET.ACCOUNT_SELECTOR} component={AccountSelector} /> + { + const Engine = UntypedEngine as any; + const { hostInfo } = props.route.params; + const [isLoading, setIsLoading] = useState(false); + const prevSelectedAddress = useRef(); + const shouldAutoSwitchSelectedAccount = useRef(true); + const selectedWalletAddress = useSelector( + (state: any) => + state.engine.backgroundState.PreferencesController.selectedAddress, + ); + const [selectedAddresses, setSelectedAddresses] = useState([ + selectedWalletAddress, + ]); + const sheetRef = useRef(null); + const [screen, setScreen] = useState( + AccountConnectScreens.SingleConnect, + ); + const { accounts, ensByAccountAddress } = useAccounts({ + isLoading, + }); + const { toastRef } = useContext(ToastContext); + const origin: string = useSelector(getActiveTabUrl, isEqual); + // TODO - Once we can pass metadata to permission system, pass origin instead of hostname into this component. + const hostname = hostInfo.metadata.origin; + // const hostname = useMemo(() => new URL(origin).hostname, [origin]); + const secureIcon = useMemo( + () => + (getUrlObj(origin) as URL).protocol === 'https:' + ? IconName.LockFilled + : IconName.WarningFilled, + [origin], + ); + /** + * Get image url from favicon api. + */ + const favicon: ImageSourcePropType = useMemo(() => { + const iconUrl = `https://api.faviconkit.com/${hostname}/64`; + return { uri: iconUrl }; + }, [hostname]); + + const dismissSheet = useCallback( + () => sheetRef?.current?.hide?.(), + [sheetRef], + ); + + const dismissSheetWithCallback = useCallback( + (callback?: () => void) => sheetRef?.current?.hide?.(callback), + [sheetRef], + ); + + const onConnect = useCallback( + async () => { + const selectedAccounts: SelectedAccount[] = selectedAddresses.map( + (address, index) => ({ address, lastUsed: Date.now() - index }), + ); + const request = { + ...hostInfo, + metadata: { + ...hostInfo.metadata, + origin: hostname, + }, + approvedAccounts: selectedAccounts, + }; + const connectedAccountLength = selectedAccounts.length; + const activeAddress = selectedAccounts[0].address; + const activeAccountName = getAccountNameWithENS({ + accountAddress: activeAddress, + accounts, + ensByAccountAddress, + }); + + try { + setIsLoading(true); + await Engine.context.PermissionController.acceptPermissionsRequest( + request, + ); + let labelOptions: ToastOptions['labelOptions'] = []; + if (connectedAccountLength > 1) { + labelOptions = [ + { label: `${connectedAccountLength}`, isBold: true }, + { + label: `${strings('toast.accounts_connected')}`, + }, + { label: `\n${activeAccountName}`, isBold: true }, + { label: strings('toast.now_active') }, + ]; + } else { + labelOptions = [ + { label: activeAccountName, isBold: true }, + { label: strings('toast.connected') }, + ]; + } + toastRef?.current?.showToast({ + variant: ToastVariant.Account, + labelOptions, + accountAddress: activeAddress, + }); + } catch (e: any) { + Logger.error(e, 'Error while trying to connect to a dApp.'); + } finally { + setIsLoading(false); + dismissSheet(); + } + }, + /* eslint-disable-next-line */ + [selectedAddresses, hostInfo, accounts, ensByAccountAddress, hostname], + ); + + const onCreateAccount = useCallback(async (isMultiSelect?: boolean) => { + const { KeyringController, PreferencesController } = Engine.context; + try { + shouldAutoSwitchSelectedAccount.current = !isMultiSelect; + setIsLoading(true); + const { addedAccountAddress } = await KeyringController.addNewAccount(); + PreferencesController.setSelectedAddress(addedAccountAddress); + AnalyticsV2.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); + } catch (e: any) { + Logger.error(e, 'error while trying to add a new account'); + } finally { + setIsLoading(false); + } + /* eslint-disable-next-line */ + }, []); + + // This useEffect is used for auto selecting the newly created account post account creation. + useEffect(() => { + if (isLoading && prevSelectedAddress.current !== selectedWalletAddress) { + shouldAutoSwitchSelectedAccount.current && + setSelectedAddresses([selectedWalletAddress]); + prevSelectedAddress.current = selectedWalletAddress; + } + if (!prevSelectedAddress.current) { + prevSelectedAddress.current = selectedWalletAddress; + } + }, [selectedWalletAddress, isLoading, selectedAddresses]); + + const renderSingleConnectScreen = useCallback(() => { + const selectedAddress = selectedAddresses[0]; + const selectedAccount = accounts.find( + (account) => account.address === selectedAddress, + ); + const ensName = ensByAccountAddress[selectedAddress]; + const defaultSelectedAccount: Account | undefined = selectedAccount + ? { + ...selectedAccount, + name: + isDefaultAccountName(selectedAccount.name) && ensName + ? ensName + : selectedAccount.name, + } + : undefined; + return ( + + ); + }, [ + accounts, + ensByAccountAddress, + selectedAddresses, + onConnect, + isLoading, + setScreen, + dismissSheet, + setSelectedAddresses, + favicon, + hostname, + secureIcon, + ]); + + const renderSingleConnectSelectorScreen = useCallback( + () => ( + onCreateAccount()} + onDismissSheetWithCallback={dismissSheetWithCallback} + /> + ), + [ + accounts, + ensByAccountAddress, + selectedAddresses, + isLoading, + onCreateAccount, + dismissSheetWithCallback, + setSelectedAddresses, + setScreen, + ], + ); + + const renderMultiConnectSelectorScreen = useCallback( + () => ( + onCreateAccount(true)} + favicon={favicon} + hostname={hostname} + secureIcon={secureIcon} + /> + ), + [ + accounts, + ensByAccountAddress, + selectedAddresses, + setSelectedAddresses, + onConnect, + isLoading, + onCreateAccount, + dismissSheetWithCallback, + favicon, + hostname, + secureIcon, + ], + ); + + const renderConnectScreens = useCallback(() => { + switch (screen) { + case AccountConnectScreens.SingleConnect: + return renderSingleConnectScreen(); + case AccountConnectScreens.SingleConnectSelector: + return renderSingleConnectSelectorScreen(); + case AccountConnectScreens.MultiConnectSelector: + return renderMultiConnectSelectorScreen(); + } + }, [ + screen, + renderSingleConnectScreen, + renderSingleConnectSelectorScreen, + renderMultiConnectSelectorScreen, + ]); + + return ( + + {renderConnectScreens()} + + ); +}; + +export default AccountConnect; diff --git a/app/components/Views/AccountConnect/AccountConnect.types.ts b/app/components/Views/AccountConnect/AccountConnect.types.ts new file mode 100644 index 00000000000..2607ae0db3e --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnect.types.ts @@ -0,0 +1,22 @@ +/** + * Enum to track states of the connect screen. + */ +export enum AccountConnectScreens { + SingleConnect = 'SingleConnect', + SingleConnectSelector = 'SingleConnectSelector', + MultiConnectSelector = 'MultiConnectSelector', +} + +/** + * AccountConnect props. + */ +export interface AccountConnectProps { + /** + * Props that are passed in while navigating to screen. + */ + route: { + params: { + hostInfo: any; + }; + }; +} diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.styles.ts b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.styles.ts new file mode 100644 index 00000000000..3b20bc32622 --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.styles.ts @@ -0,0 +1,41 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +// External dependencies. +import { Theme } from '../../../../util/theme/models'; + +/** + * Style sheet function for AccountConnectMultiSelector screen. + * @returns StyleSheet object. + */ +const styleSheet = (params: { theme: Theme }) => { + const { colors } = params.theme; + + return StyleSheet.create({ + body: { + paddingHorizontal: 16, + }, + description: { + textAlign: 'center', + marginVertical: 16, + color: colors.text.alternative, + }, + ctaButtonsContainer: { + marginTop: 24, + flexDirection: 'row', + marginBottom: 16, + }, + button: { flex: 1 }, + buttonSeparator: { + width: 16, + }, + selectAllButton: { + marginBottom: 16, + }, + disabled: { + opacity: 0.5, + }, + }); +}; + +export default styleSheet; diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx new file mode 100644 index 00000000000..93c46212576 --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx @@ -0,0 +1,191 @@ +// Third party dependencies. +import React, { useCallback } from 'react'; +import { View } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; + +// External dependencies. +import SheetActions from '../../../../component-library/components-temp/SheetActions'; +import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader'; +import { strings } from '../../../../../locales/i18n'; +import TagUrl from '../../../../component-library/components/Tags/TagUrl'; +import Text from '../../../../component-library/components/Text'; +import { useStyles } from '../../../../component-library/hooks'; +import ButtonPrimary, { + ButtonPrimaryVariant, +} from '../../../../component-library/components/Buttons/ButtonPrimary'; +import ButtonSecondary, { + ButtonSecondaryVariant, +} from '../../../../component-library/components/Buttons/ButtonSecondary'; +import { ButtonBaseSize } from '../../../../component-library/components/Buttons/ButtonBase'; +import AccountSelectorList from '../../../UI/AccountSelectorList'; +import ButtonLink from '../../../../component-library/components/Buttons/ButtonLink'; +import AnalyticsV2 from '../../../../util/analyticsV2'; +import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics'; + +// Internal dependencies. +import styleSheet from './AccountConnectMultiSelector.styles'; +import { AccountConnectMultiSelectorProps } from './AccountConnectMultiSelector.types'; + +const AccountConnectMultiSelector = ({ + accounts, + ensByAccountAddress, + selectedAddresses, + onSelectAddress, + isLoading, + onDismissSheetWithCallback, + onConnect, + onCreateAccount, + hostname, + favicon, + secureIcon, +}: AccountConnectMultiSelectorProps) => { + const { styles } = useStyles(styleSheet, {}); + const navigation = useNavigation(); + + const onOpenImportAccount = useCallback(() => { + onDismissSheetWithCallback(() => { + navigation.navigate('ImportPrivateKeyView'); + // Is this where we want to track importing an account or within ImportPrivateKeyView screen? + AnalyticsV2.trackEvent( + ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + ); + }); + }, [navigation, onDismissSheetWithCallback]); + + const onOpenConnectHardwareWallet = useCallback(() => { + onDismissSheetWithCallback(() => { + navigation.navigate('ConnectQRHardwareFlow'); + // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? + AnalyticsV2.trackEvent( + AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, + ); + }); + }, [navigation, onDismissSheetWithCallback]); + + const renderSheetActions = useCallback( + () => ( + + ), + [ + isLoading, + onCreateAccount, + onOpenImportAccount, + onOpenConnectHardwareWallet, + ], + ); + + const renderSelectAllButton = useCallback( + () => + Boolean(accounts.length) && ( + { + if (isLoading) return; + const allSelectedAccountAddresses = accounts.map( + ({ address }) => address, + ); + onSelectAddress(allSelectedAccountAddresses); + }} + style={{ + ...styles.selectAllButton, + ...(isLoading && styles.disabled), + }} + > + {strings('accounts.select_all')} + + ), + [accounts, isLoading, onSelectAddress, styles], + ); + + const renderCtaButtons = useCallback(() => { + const isConnectDisabled = Boolean(!selectedAddresses.length) || isLoading; + + return ( + + onDismissSheetWithCallback()} + size={ButtonBaseSize.Lg} + style={styles.button} + /> + + + + ); + }, [ + onConnect, + isLoading, + selectedAddresses, + onDismissSheetWithCallback, + styles, + ]); + + return ( + <> + + + + + {strings('accounts.connect_description')} + + {renderSelectAllButton()} + + { + const selectedAddressIndex = selectedAddresses.indexOf(accAddress); + // Reconstruct selected addresses. + const newAccountAddresses = accounts.reduce((acc, { address }) => { + if (accAddress === address) { + selectedAddressIndex === -1 && acc.push(address); + } else if (selectedAddresses.includes(address)) { + acc.push(address); + } + return acc; + }, [] as string[]); + onSelectAddress(newAccountAddresses); + }} + accounts={accounts} + ensByAccountAddress={ensByAccountAddress} + isLoading={isLoading} + selectedAddresses={selectedAddresses} + isMultiSelect + /> + {renderSheetActions()} + {renderCtaButtons()} + + ); +}; + +export default AccountConnectMultiSelector; diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts new file mode 100644 index 00000000000..1b4f25c2612 --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts @@ -0,0 +1,21 @@ +// Third party dependencies. +import { ImageSourcePropType } from 'react-native'; + +// External dependencies. +import { UseAccounts } from '../../../hooks/useAccounts'; +import { IconName } from '../../../../component-library/components/Icon'; + +/** + * AccountConnectMultiSelector props. + */ +export interface AccountConnectMultiSelectorProps extends UseAccounts { + selectedAddresses: string[]; + onSelectAddress: (addresses: string[]) => void; + isLoading?: boolean; + onDismissSheetWithCallback: (callback?: () => void) => void; + onCreateAccount: () => void; + onConnect: () => void; + hostname: string; + favicon: ImageSourcePropType; + secureIcon: IconName; +} diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/index.ts b/app/components/Views/AccountConnect/AccountConnectMultiSelector/index.ts new file mode 100644 index 00000000000..dff6d7365b6 --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/index.ts @@ -0,0 +1 @@ +export { default } from './AccountConnectMultiSelector'; diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.styles.ts b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.styles.ts new file mode 100644 index 00000000000..91dbd31247c --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.styles.ts @@ -0,0 +1,42 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +// External dependencies. +import { Theme } from '../../../../util/theme/models'; + +/** + * Style sheet function for AccountConnectSingle screen. + * @returns StyleSheet object. + */ +const styleSheet = (params: { theme: Theme }) => { + const { colors } = params.theme; + + return StyleSheet.create({ + body: { + paddingHorizontal: 16, + }, + description: { + textAlign: 'center', + marginVertical: 16, + color: colors.text.alternative, + }, + sheetActionContainer: { + marginTop: 16, + }, + ctaButtonsContainer: { + marginTop: 24, + flexDirection: 'row', + marginBottom: 16, + }, + button: { flex: 1 }, + buttonSeparator: { + width: 16, + }, + downCaretContainer: { justifyContent: 'center', flex: 1 }, + disabled: { + opacity: 0.5, + }, + }); +}; + +export default styleSheet; diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx new file mode 100644 index 00000000000..3e5864075cd --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx @@ -0,0 +1,160 @@ +// Third party dependencies. +import React, { useCallback } from 'react'; +import { View } from 'react-native'; +import { useSelector } from 'react-redux'; +import { KeyringTypes } from '@metamask/controllers'; + +// External dependencies. +import SheetActions from '../../../../component-library/components-temp/SheetActions'; +import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader'; +import { strings } from '../../../../../locales/i18n'; +import Cell, { + CellVariants, +} from '../../../../component-library/components/Cells/Cell'; +import TagUrl from '../../../../component-library/components/Tags/TagUrl'; +import Text from '../../../../component-library/components/Text'; +import { useStyles } from '../../../../component-library/hooks'; +import ButtonPrimary, { + ButtonPrimaryVariant, +} from '../../../../component-library/components/Buttons/ButtonPrimary'; +import ButtonSecondary, { + ButtonSecondaryVariant, +} from '../../../../component-library/components/Buttons/ButtonSecondary'; +import { ButtonBaseSize } from '../../../../component-library/components/Buttons/ButtonBase'; +import { AvatarVariants } from '../../../../component-library/components/Avatars'; +import { AvatarAccountType } from '../../../../component-library/components/Avatars/AvatarAccount'; +import { formatAddress } from '../../../../util/address'; +import Icon, { IconName } from '../../../../component-library/components/Icon'; +import { AccountConnectScreens } from '../AccountConnect.types'; + +// Internal dependencies. +import { AccountConnectSingleProps } from './AccountConnectSingle.types'; +import styleSheet from './AccountConnectSingle.styles'; + +const AccountConnectSingle = ({ + defaultSelectedAccount, + onSetScreen, + onSetSelectedAddresses, + onConnect, + onDismissSheet, + isLoading, + favicon, + hostname, + secureIcon, +}: AccountConnectSingleProps) => { + const { styles } = useStyles(styleSheet, {}); + const accountAvatarType = useSelector((state: any) => + state.settings.useBlockieIcon + ? AvatarAccountType.Blockies + : AvatarAccountType.JazzIcon, + ); + + const getTagLabel = useCallback((type: KeyringTypes) => { + let label = ''; + switch (type) { + case KeyringTypes.qr: + label = strings('transaction.hardware'); + break; + case KeyringTypes.simple: + label = strings('accounts.imported'); + break; + } + return label; + }, []); + + const renderSheetAction = useCallback( + () => ( + + { + onSetSelectedAddresses([]); + onSetScreen(AccountConnectScreens.MultiConnectSelector); + }, + disabled: isLoading, + }, + ]} + /> + + ), + [onSetScreen, onSetSelectedAddresses, isLoading, styles], + ); + + const renderCtaButtons = useCallback( + () => ( + + + + + + ), + [onDismissSheet, onConnect, isLoading, styles], + ); + + const renderSelectedAccount = useCallback(() => { + if (!defaultSelectedAccount) return null; + const { name, address, type, balanceError } = defaultSelectedAccount; + const shortAddress = formatAddress(address, 'short'); + const tagLabel = getTagLabel(type); + + return ( + onSetScreen(AccountConnectScreens.SingleConnectSelector)} + avatarProps={{ + variant: AvatarVariants.Account, + type: accountAvatarType, + accountAddress: address, + }} + tagLabel={tagLabel} + disabled={isLoading} + style={isLoading && styles.disabled} + > + + + + + ); + }, [ + accountAvatarType, + onSetScreen, + defaultSelectedAccount, + isLoading, + styles, + getTagLabel, + ]); + + return ( + <> + + + + + {strings('accounts.connect_description')} + + {renderSelectedAccount()} + + {renderSheetAction()} + {renderCtaButtons()} + + ); +}; + +export default AccountConnectSingle; diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts new file mode 100644 index 00000000000..96c3239443c --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts @@ -0,0 +1,22 @@ +// Third party dependencies. +import { ImageSourcePropType } from 'react-native'; + +// External dependencies. +import { AccountConnectScreens } from '../AccountConnect.types'; +import { Account } from '../../../hooks/useAccounts'; +import { IconName } from '../../../../component-library/components/Icon'; + +/** + * AccountConnectSingle props. + */ +export interface AccountConnectSingleProps { + defaultSelectedAccount: Account | undefined; + onConnect: () => void; + isLoading?: boolean; + onDismissSheet: () => void; + onSetScreen: (screen: AccountConnectScreens) => void; + onSetSelectedAddresses: (addresses: string[]) => void; + hostname: string; + favicon: ImageSourcePropType; + secureIcon: IconName; +} diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/index.ts b/app/components/Views/AccountConnect/AccountConnectSingle/index.ts new file mode 100644 index 00000000000..98f745c1417 --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectSingle/index.ts @@ -0,0 +1 @@ +export { default } from './AccountConnectSingle'; diff --git a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.styles.ts b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.styles.ts new file mode 100644 index 00000000000..9abe3f330e0 --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.styles.ts @@ -0,0 +1,14 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +/** + * Style sheet function for AccountConnectSingleSelector screen. + * @returns StyleSheet object. + */ +const styleSheet = StyleSheet.create({ + sheetActionContainer: { + marginBottom: 16, + }, +}); + +export default styleSheet; diff --git a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx new file mode 100644 index 00000000000..10718019e8e --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx @@ -0,0 +1,111 @@ +// Third party dependencies. +import React, { useCallback } from 'react'; +import { View } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; + +// External dependencies. +import SheetActions from '../../../../component-library/components-temp/SheetActions'; +import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader'; +import AccountSelectorList from '../../../../components/UI/AccountSelectorList'; +import { strings } from '../../../../../locales/i18n'; +import AnalyticsV2 from '../../../../util/analyticsV2'; +import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics'; +import { AccountConnectScreens } from '../AccountConnect.types'; + +// Internal dependencies. +import { AccountConnectSingleSelectorProps } from './AccountConnectSingleSelector.types'; +import styles from './AccountConnectSingleSelector.styles'; + +const AccountConnectSingleSelector = ({ + accounts, + ensByAccountAddress, + selectedAddresses, + isLoading, + onCreateAccount, + onDismissSheetWithCallback, + onSetScreen, + onSetSelectedAddresses, +}: AccountConnectSingleSelectorProps) => { + const navigation = useNavigation(); + + const onBack = useCallback( + () => onSetScreen(AccountConnectScreens.SingleConnect), + [onSetScreen], + ); + + const onSelectAccount = useCallback( + (address: string) => { + onSetScreen(AccountConnectScreens.SingleConnect); + onSetSelectedAddresses([address]); + }, + [onSetScreen, onSetSelectedAddresses], + ); + + const onOpenImportAccount = useCallback(() => { + onDismissSheetWithCallback(() => { + navigation.navigate('ImportPrivateKeyView'); + // Is this where we want to track importing an account or within ImportPrivateKeyView screen? + AnalyticsV2.trackEvent( + ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + ); + }); + }, [navigation, onDismissSheetWithCallback]); + + const onOpenConnectHardwareWallet = useCallback(() => { + onDismissSheetWithCallback(() => { + navigation.navigate('ConnectQRHardwareFlow'); + // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? + AnalyticsV2.trackEvent( + AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, + ); + }); + }, [navigation, onDismissSheetWithCallback]); + + const renderSheetActions = useCallback( + () => ( + + + + ), + [ + isLoading, + onCreateAccount, + onOpenImportAccount, + onOpenConnectHardwareWallet, + ], + ); + + return ( + <> + + + {renderSheetActions()} + + ); +}; + +export default AccountConnectSingleSelector; diff --git a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts new file mode 100644 index 00000000000..1fbaad3e91c --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts @@ -0,0 +1,15 @@ +// External dependencies. +import { UseAccounts } from '../../../hooks/useAccounts'; +import { AccountConnectScreens } from '../AccountConnect.types'; + +/** + * AccountConnectSingleSelector props. + */ +export interface AccountConnectSingleSelectorProps extends UseAccounts { + selectedAddresses: string[]; + isLoading?: boolean; + onCreateAccount: () => void; + onSetScreen: (screen: AccountConnectScreens) => void; + onSetSelectedAddresses: (addresses: string[]) => void; + onDismissSheetWithCallback: (callback?: () => void) => void; +} diff --git a/app/components/Views/AccountConnect/AccountConnectSingleSelector/index.ts b/app/components/Views/AccountConnect/AccountConnectSingleSelector/index.ts new file mode 100644 index 00000000000..d57b62b5e4d --- /dev/null +++ b/app/components/Views/AccountConnect/AccountConnectSingleSelector/index.ts @@ -0,0 +1 @@ +export { default } from './AccountConnectSingleSelector'; diff --git a/app/components/Views/AccountConnect/index.ts b/app/components/Views/AccountConnect/index.ts new file mode 100644 index 00000000000..3fdb783588e --- /dev/null +++ b/app/components/Views/AccountConnect/index.ts @@ -0,0 +1,2 @@ +export { default } from './AccountConnect'; +export type { AccountConnectProps } from './AccountConnect.types'; diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index c76cca2ebbb..d89200b14d5 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -37,6 +37,7 @@ const Routes = { }, SHEET: { ACCOUNT_SELECTOR: 'AccountSelector', + ACCOUNT_CONNECT: 'AccountConnect', }, }; From c2d05153cd8716a6adc9e589dc40d07ca80aecf5 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 28 Sep 2022 16:15:22 -0700 Subject: [PATCH 10/91] [PS] Create permissions sheet (#5053) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values * Organize account hooks and utils * Export ENS cache. Update account util to return ENS name. * Update account selector * Remove account selector styles * Hook up permission system controller logic * Connect PS to browser. Pass from address to transactions. * Create first time connect sheet * Create permissions sheet --- app/components/Nav/App/index.js | 5 + .../AccountPermissions/AccountPermissions.tsx | 293 ++++++++++++++++++ .../AccountPermissions.types.ts | 24 ++ .../AccountPermissionsConnected.styles.ts | 32 ++ .../AccountPermissionsConnected.tsx | 158 ++++++++++ .../AccountPermissionsConnected.types.ts | 21 ++ .../AccountPermissionsConnected/index.ts | 1 + .../AccountPermissionsRevoke.styles.ts | 39 +++ .../AccountPermissionsRevoke.tsx | 162 ++++++++++ .../AccountPermissionsRevoke.types.ts | 20 ++ .../AccountPermissionsRevoke/index.ts | 1 + .../Views/AccountPermissions/index.ts | 2 + app/constants/navigation/Routes.ts | 1 + 13 files changed, 759 insertions(+) create mode 100644 app/components/Views/AccountPermissions/AccountPermissions.tsx create mode 100644 app/components/Views/AccountPermissions/AccountPermissions.types.ts create mode 100644 app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.styles.ts create mode 100644 app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx create mode 100644 app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.types.ts create mode 100644 app/components/Views/AccountPermissions/AccountPermissionsConnected/index.ts create mode 100644 app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.styles.ts create mode 100644 app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx create mode 100644 app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.types.ts create mode 100644 app/components/Views/AccountPermissions/AccountPermissionsRevoke/index.ts create mode 100644 app/components/Views/AccountPermissions/index.ts diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index 3a008a2bcff..c3a5abaff46 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -56,6 +56,7 @@ import Toast, { } from '../../../component-library/components/Toast'; import AccountSelector from '../../../components/Views/AccountSelector'; import AccountConnect from '../../../components/Views/AccountConnect'; +import AccountPermissions from '../../../components/Views/AccountPermissions'; import { TurnOffRememberMeModal } from '../../../components/UI/TurnOffRememberMeModal'; const Stack = createStackNavigator(); @@ -366,6 +367,10 @@ const App = ({ userLoggedIn }) => { name={Routes.SHEET.ACCOUNT_CONNECT} component={AccountConnect} /> + { + const Engine = UntypedEngine as any; + const { + hostInfo: { + metadata: { origin: hostname }, + }, + } = props.route.params; + const origin: string = useSelector(getActiveTabUrl, isEqual); + // TODO - Once we can pass metadata to permission system, pass origin instead of hostname into this component. + // const hostname = useMemo(() => new URL(origin).hostname, [origin]); + const secureIcon = useMemo( + () => + (getUrlObj(origin) as URL).protocol === 'https:' + ? IconName.LockFilled + : IconName.WarningFilled, + [origin], + ); + /** + * Get image url from favicon api. + */ + const favicon: ImageSourcePropType = useMemo(() => { + const iconUrl = `https://api.faviconkit.com/${hostname}/64`; + return { uri: iconUrl }; + }, [hostname]); + + const { toastRef } = useContext(ToastContext); + const [isLoading, setIsLoading] = useState(false); + const permittedAccountsList = useSelector( + (state: any) => state.engine.backgroundState.PermissionController, + ); + const permittedAccountsByHostname = getPermittedAccountsByHostname( + permittedAccountsList, + hostname, + ); + const [selectedAddresses, setSelectedAddresses] = useState([]); + const sheetRef = useRef(null); + const [permissionsScreen, setPermissionsScreen] = + useState(AccountPermissionsScreens.Connected); + const { accounts, ensByAccountAddress } = useAccounts({ + isLoading, + }); + const activeAddress: string = permittedAccountsByHostname[0]; + + const dismissSheet = useCallback( + () => sheetRef?.current?.hide?.(), + [sheetRef], + ); + + const dismissSheetWithCallback = useCallback( + (callback?: () => void) => sheetRef?.current?.hide?.(callback), + [sheetRef], + ); + + const accountsFilteredByPermissions = useMemo(() => { + const accountsByPermittedStatus: Record< + 'permitted' | 'unpermitted', + Account[] + > = { + permitted: [], + unpermitted: [], + }; + + accounts.forEach((account) => { + if (permittedAccountsByHostname.includes(account.address)) { + accountsByPermittedStatus.permitted.push(account); + } else { + accountsByPermittedStatus.unpermitted.push(account); + } + }); + + return accountsByPermittedStatus; + }, [accounts, permittedAccountsByHostname]); + + const onCreateAccount = useCallback( + async () => { + const { KeyringController, PreferencesController } = Engine.context; + try { + setIsLoading(true); + const { addedAccountAddress } = await KeyringController.addNewAccount(); + PreferencesController.setSelectedAddress(addedAccountAddress); + AnalyticsV2.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); + } catch (e: any) { + Logger.error(e, 'Error while trying to add a new account.'); + } finally { + setIsLoading(false); + } + }, + /* eslint-disable-next-line */ + [setIsLoading], + ); + + const onConnect = useCallback(async () => { + try { + setIsLoading(true); + const newActiveAddress = await addPermittedAccounts( + hostname, + selectedAddresses, + ); + const activeAccountName = getAccountNameWithENS({ + accountAddress: newActiveAddress, + accounts, + ensByAccountAddress, + }); + const connectedAccountLength = selectedAddresses.length; + let labelOptions: ToastOptions['labelOptions'] = []; + if (connectedAccountLength > 1) { + labelOptions = [ + { label: `${connectedAccountLength}`, isBold: true }, + { + label: `${strings('toast.accounts_connected')}\n`, + }, + ]; + } + labelOptions = labelOptions.concat([ + { label: activeAccountName, isBold: true }, + { label: strings('toast.now_active') }, + ]); + toastRef?.current?.showToast({ + variant: ToastVariant.Account, + labelOptions, + accountAddress: newActiveAddress, + }); + } catch (e: any) { + Logger.error(e, 'Error while trying to connect to a dApp.'); + } finally { + setIsLoading(false); + dismissSheetWithCallback(); + } + }, [ + selectedAddresses, + accounts, + setIsLoading, + dismissSheetWithCallback, + hostname, + ensByAccountAddress, + toastRef, + ]); + + const renderConnectedScreen = useCallback( + () => ( + + ), + [ + ensByAccountAddress, + activeAddress, + isLoading, + accountsFilteredByPermissions, + setSelectedAddresses, + setPermissionsScreen, + dismissSheet, + favicon, + hostname, + secureIcon, + ], + ); + + const renderConnectScreen = useCallback( + () => ( + + ), + [ + ensByAccountAddress, + selectedAddresses, + isLoading, + accountsFilteredByPermissions, + dismissSheetWithCallback, + onConnect, + onCreateAccount, + favicon, + hostname, + secureIcon, + ], + ); + + const renderRevokeScreen = useCallback( + () => ( + + ), + [ + ensByAccountAddress, + isLoading, + permittedAccountsByHostname, + accountsFilteredByPermissions, + setPermissionsScreen, + dismissSheet, + favicon, + hostname, + secureIcon, + ], + ); + + const renderPermissionsScreens = useCallback(() => { + switch (permissionsScreen) { + case AccountPermissionsScreens.Connected: + return renderConnectedScreen(); + case AccountPermissionsScreens.Connect: + return renderConnectScreen(); + case AccountPermissionsScreens.Revoke: + return renderRevokeScreen(); + } + }, [ + permissionsScreen, + renderConnectedScreen, + renderConnectScreen, + renderRevokeScreen, + ]); + + return ( + + {renderPermissionsScreens()} + + ); +}; + +export default AccountPermissions; diff --git a/app/components/Views/AccountPermissions/AccountPermissions.types.ts b/app/components/Views/AccountPermissions/AccountPermissions.types.ts new file mode 100644 index 00000000000..91f90afae91 --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissions.types.ts @@ -0,0 +1,24 @@ +/** + * Enum to track states of the permissions screen. + */ +export enum AccountPermissionsScreens { + Connected = 'Connected', + Connect = 'Connect', + Revoke = 'Revoke', +} + +/** + * AccountPermissions props. + */ +export interface AccountPermissionsProps { + /** + * Props that are passed in while navigating to screen. + */ + route: { + params: { + hostInfo: { + metadata: { origin: string }; + }; + }; + }; +} diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.styles.ts b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.styles.ts new file mode 100644 index 00000000000..59842f6047e --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.styles.ts @@ -0,0 +1,32 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +/** + * Style sheet function for AccountPermissionsConnected screen. + * @returns StyleSheet object. + */ +const styleSheet = StyleSheet.create({ + body: { + paddingHorizontal: 16, + }, + networkPicker: { + marginVertical: 16, + }, + sheetActionContainer: { + marginVertical: 16, + }, + ctaButtonsContainer: { + marginTop: 24, + flexDirection: 'row', + }, + button: { flex: 1 }, + buttonSeparator: { + width: 16, + }, + downCaretContainer: { justifyContent: 'center', flex: 1 }, + disabled: { + opacity: 0.5, + }, +}); + +export default styleSheet; diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx new file mode 100644 index 00000000000..8c08f6c2a98 --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -0,0 +1,158 @@ +// Third party dependencies. +import React, { useCallback, useContext, useMemo } from 'react'; +import { View } from 'react-native'; +import { useDispatch, useSelector } from 'react-redux'; + +// External dependencies. +import SheetActions from '../../../../component-library/components-temp/SheetActions'; +import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader'; +import { strings } from '../../../../../locales/i18n'; +import TagUrl from '../../../../component-library/components/Tags/TagUrl'; +import PickerNetwork from '../../../../component-library/components/Pickers/PickerNetwork'; +import { + getNetworkNameFromProvider, + getNetworkImageSource, +} from '../../../../util/networks'; +import AccountSelectorList from '../../../../components/UI/AccountSelectorList'; +import { toggleNetworkModal } from '../../../../actions/modals'; +import { AccountPermissionsScreens } from '../AccountPermissions.types'; +import { switchActiveAccounts } from '../../../../core/Permissions'; +import { + ToastContext, + ToastVariant, +} from '../../../../component-library/components/Toast'; +import getAccountNameWithENS from '../../../../util/accounts'; +import AnalyticsV2 from '../../../../util/analyticsV2'; + +// Internal dependencies. +import { AccountPermissionsConnectedProps } from './AccountPermissionsConnected.types'; +import styles from './AccountPermissionsConnected.styles'; + +const AccountPermissionsConnected = ({ + ensByAccountAddress, + accounts, + isLoading, + selectedAddresses, + onSetPermissionsScreen, + onSetSelectedAddresses, + onDismissSheet, + hostname, + favicon, + secureIcon, +}: AccountPermissionsConnectedProps) => { + const dispatch = useDispatch(); + const networkController = useSelector( + (state: any) => state.engine.backgroundState.NetworkController, + ); + const networkName = useMemo( + () => getNetworkNameFromProvider(networkController.provider), + [networkController.provider], + ); + const networkImageSource = useMemo( + () => getNetworkImageSource(networkController.provider.chainId), + [networkController.provider.chainId], + ); + const activeAddress = selectedAddresses[0]; + const { toastRef } = useContext(ToastContext); + + const onConnectMoreAccounts = useCallback(() => { + onSetSelectedAddresses([]); + onSetPermissionsScreen(AccountPermissionsScreens.Connect); + }, [onSetSelectedAddresses, onSetPermissionsScreen]); + + const openRevokePermissions = () => + onSetPermissionsScreen(AccountPermissionsScreens.Revoke); + + const switchActiveAccount = useCallback( + (address: string) => { + if (address !== activeAddress) { + switchActiveAccounts(hostname, address); + } + onDismissSheet(); + const activeAccountName = getAccountNameWithENS({ + accountAddress: address, + accounts, + ensByAccountAddress, + }); + toastRef?.current?.showToast({ + variant: ToastVariant.Account, + labelOptions: [ + { + label: activeAccountName, + isBold: true, + }, + { label: strings('toast.now_active') }, + ], + accountAddress: address, + }); + }, + [ + activeAddress, + onDismissSheet, + accounts, + ensByAccountAddress, + hostname, + toastRef, + ], + ); + + const switchNetwork = useCallback(() => { + dispatch(toggleNetworkModal(false)); + AnalyticsV2.trackEvent( + AnalyticsV2.ANALYTICS_EVENTS.BROWSER_SWITCH_NETWORK, + { + from_chain_id: networkController.network, + }, + ); + }, [networkController.network, dispatch]); + + const renderSheetAction = useCallback( + () => ( + + + + ), + [onConnectMoreAccounts, isLoading], + ); + + return ( + <> + + + + + + + {renderSheetAction()} + + ); +}; + +export default AccountPermissionsConnected; diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.types.ts b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.types.ts new file mode 100644 index 00000000000..a401dece0a7 --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.types.ts @@ -0,0 +1,21 @@ +// Third party dependencies. +import { ImageSourcePropType } from 'react-native'; + +// External dependencies. +import { UseAccounts } from '../../../hooks/useAccounts'; +import { AccountPermissionsScreens } from '../AccountPermissions.types'; +import { IconName } from '../../../../component-library/components/Icon'; + +/** + * AccountPermissionsConnected props. + */ +export interface AccountPermissionsConnectedProps extends UseAccounts { + isLoading?: boolean; + selectedAddresses: string[]; + onSetSelectedAddresses: (addresses: string[]) => void; + onSetPermissionsScreen: (screen: AccountPermissionsScreens) => void; + onDismissSheet: () => void; + hostname: string; + favicon: ImageSourcePropType; + secureIcon: IconName; +} diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/index.ts b/app/components/Views/AccountPermissions/AccountPermissionsConnected/index.ts new file mode 100644 index 00000000000..5ba4a52b76a --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/index.ts @@ -0,0 +1 @@ +export { default } from './AccountPermissionsConnected'; diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.styles.ts b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.styles.ts new file mode 100644 index 00000000000..2c78fc3a4e6 --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.styles.ts @@ -0,0 +1,39 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +// External dependencies. +import { Theme } from '../../../../util/theme/models'; + +/** + * Style sheet function for AccountPermissionsRevoke screen. + * @returns StyleSheet object. + */ +const styleSheet = (params: { theme: Theme }) => { + const { colors } = params.theme; + + return StyleSheet.create({ + body: { + paddingHorizontal: 16, + }, + description: { + textAlign: 'center', + marginVertical: 16, + color: colors.text.alternative, + }, + sheetActionContainer: { + marginVertical: 16, + }, + ctaButtonsContainer: { + marginTop: 24, + flexDirection: 'row', + }, + button: { flex: 1 }, + buttonSeparator: { + width: 16, + }, + downCaretContainer: { justifyContent: 'center', flex: 1 }, + disconnectButton: { alignSelf: 'center' }, + }); +}; + +export default styleSheet; diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx new file mode 100644 index 00000000000..2b7bc27dec2 --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx @@ -0,0 +1,162 @@ +// Third party dependencies. +import React, { useCallback, useContext } from 'react'; +import { View } from 'react-native'; + +// External dependencies. +import SheetActions from '../../../../component-library/components-temp/SheetActions'; +import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader'; +import { strings } from '../../../../../locales/i18n'; +import TagUrl from '../../../../component-library/components/Tags/TagUrl'; +import Text from '../../../../component-library/components/Text'; +import { useStyles } from '../../../../component-library/hooks'; +import ButtonSecondary, { + ButtonSecondaryVariant, +} from '../../../../component-library/components/Buttons/ButtonSecondary'; +import { ButtonBaseSize } from '../../../../component-library/components/Buttons/ButtonBase'; +import AccountSelectorList from '../../../../components/UI/AccountSelectorList'; +import { ButtonTertiaryVariant } from '../../../../component-library/components/Buttons/ButtonTertiary'; +import { removePermittedAccount } from '../../../../core/Permissions'; +import UntypedEngine from '../../../../core/Engine'; +import Logger from '../../../../util/Logger'; +import { + ToastContext, + ToastVariant, + ToastOptions, +} from '../../../../component-library/components/Toast'; +import { AccountPermissionsScreens } from '../AccountPermissions.types'; +import getAccountNameWithENS from '../../../../util/accounts'; + +// Internal dependencies. +import { AccountPermissionsRevokeProps } from './AccountPermissionsRevoke.types'; +import styleSheet from './AccountPermissionsRevoke.styles'; + +const AccountPermissionsRevoke = ({ + ensByAccountAddress, + accounts, + isLoading, + permittedAddresses, + onSetPermissionsScreen, + onDismissSheet, + hostname, + favicon, + secureIcon, +}: AccountPermissionsRevokeProps) => { + const Engine = UntypedEngine as any; + const { styles } = useStyles(styleSheet, {}); + const activeAddress = permittedAddresses[0]; + const { toastRef } = useContext(ToastContext); + + const revokeAllAccounts = useCallback( + async () => { + try { + await Engine.context.PermissionController.revokeAllPermissions( + hostname, + ); + onDismissSheet(); + toastRef?.current?.showToast({ + variant: ToastVariant.Plain, + labelOptions: [{ label: strings('toast.revoked_all') }], + }); + } catch (e) { + Logger.log(`Failed to revoke all accounts for ${hostname}`, e); + } + }, + /* eslint-disable-next-line */ + [onDismissSheet, hostname, toastRef], + ); + + const renderSheetAction = useCallback( + () => ( + + + + ), + [revokeAllAccounts, isLoading, styles], + ); + + return ( + <> + + onSetPermissionsScreen(AccountPermissionsScreens.Connected) + } + /> + + + + {strings('accounts.connect_description')} + + + ( + { + if (permittedAddresses.length === 1) { + // Dismiss and show toast + revokeAllAccounts(); + } else { + const labelOptions: ToastOptions['labelOptions'] = [ + { + label: name, + isBold: true, + }, + { label: strings('toast.revoked') }, + ]; + if (activeAddress === address) { + const nextActiveAddress = permittedAddresses[1]; + const newActiveAccountName = getAccountNameWithENS({ + accountAddress: nextActiveAddress, + accounts, + ensByAccountAddress, + }); + removePermittedAccount(hostname, address); + labelOptions.push( + { + label: `\n${newActiveAccountName}`, + isBold: true, + }, + { label: strings('toast.now_active') }, + ); + toastRef?.current?.showToast({ + variant: ToastVariant.Account, + labelOptions, + accountAddress: nextActiveAddress, + }); + } else { + // Just disconnect + removePermittedAccount(hostname, address); + toastRef?.current?.showToast({ + variant: ToastVariant.Plain, + labelOptions, + }); + } + } + }} + label={strings('accounts.disconnect')} + size={ButtonBaseSize.Sm} + style={styles.disconnectButton} + /> + )} + isSelectionDisabled + selectedAddresses={[]} + accounts={accounts} + ensByAccountAddress={ensByAccountAddress} + isLoading={isLoading} + /> + {renderSheetAction()} + + ); +}; + +export default AccountPermissionsRevoke; diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.types.ts b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.types.ts new file mode 100644 index 00000000000..670df381fa0 --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.types.ts @@ -0,0 +1,20 @@ +// Third party dependencies. +import { ImageSourcePropType } from 'react-native'; + +// External dependencies. +import { IconName } from 'app/component-library/components/Icon'; +import { UseAccounts } from '../../../hooks/useAccounts'; +import { AccountPermissionsScreens } from '../AccountPermissions.types'; + +/** + * AccountPermissionsRevoke props. + */ +export interface AccountPermissionsRevokeProps extends UseAccounts { + isLoading?: boolean; + permittedAddresses: string[]; + onSetPermissionsScreen: (screen: AccountPermissionsScreens) => void; + onDismissSheet: () => void; + hostname: string; + favicon: ImageSourcePropType; + secureIcon: IconName; +} diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/index.ts b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/index.ts new file mode 100644 index 00000000000..2351584ab27 --- /dev/null +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/index.ts @@ -0,0 +1 @@ +export { default } from './AccountPermissionsRevoke'; diff --git a/app/components/Views/AccountPermissions/index.ts b/app/components/Views/AccountPermissions/index.ts new file mode 100644 index 00000000000..0e9f2932718 --- /dev/null +++ b/app/components/Views/AccountPermissions/index.ts @@ -0,0 +1,2 @@ +export { default } from './AccountPermissions'; +export type { AccountPermissionsProps } from './AccountPermissions.types'; diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index d89200b14d5..2097d039419 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -38,6 +38,7 @@ const Routes = { SHEET: { ACCOUNT_SELECTOR: 'AccountSelector', ACCOUNT_CONNECT: 'AccountConnect', + ACCOUNT_PERMISSIONS: 'AccountPermissions', }, }; From 90e59f0f4dd01d6c19ba6a215b2ecf4b3d01dc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <1649425+jpcloureiro@users.noreply.github.com> Date: Thu, 29 Sep 2022 01:04:36 +0100 Subject: [PATCH 11/91] [PS] Tab bar bottom navigation (#4949) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values * Organize account hooks and utils * Export ENS cache. Update account util to return ENS name. * Update account selector * Remove account selector styles * Hook up permission system controller logic * Connect PS to browser. Pass from address to transactions. * Create first time connect sheet * Create permissions sheet * Add translations to permission system * (BottomTabBar): implement new BottomTabBar * (ActivityView): move nav stack to wallet * (Transactions): update navigation header * (ActivityView): fix back button onPress * (ActivityView): fix getNavbarOptions args * (BrowserBottomBar): remove bottomPadding * (Transactions): fix empty screen height * (Transactions): setup main modal stack navigator Co-authored-by: Cal Leung --- .../components/Navigation/TabBar/TabBar.tsx | 2 + app/components/Nav/Main/MainNavigator.js | 54 +++++++++++-------- app/components/UI/BrowserBottomBar/index.js | 10 ++-- app/components/UI/DrawerView/index.js | 24 ++------- app/components/UI/Navbar/index.js | 38 ++++++------- app/components/UI/Transactions/index.js | 1 - app/components/Views/ActivityView/index.js | 17 +++--- 7 files changed, 67 insertions(+), 79 deletions(-) diff --git a/app/component-library/components/Navigation/TabBar/TabBar.tsx b/app/component-library/components/Navigation/TabBar/TabBar.tsx index 83f50e5012d..b02fe01e4f9 100644 --- a/app/component-library/components/Navigation/TabBar/TabBar.tsx +++ b/app/component-library/components/Navigation/TabBar/TabBar.tsx @@ -21,6 +21,8 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { const renderTabBarItem = useCallback( (route: { name: string; key: string }, index: number) => { const label = descriptors[route.key].options.tabBarLabel as TabBarLabel; + if (!label) return null; + const key = `tab-bar-item-${label}`; const isSelected = state.index === index; const icon = ICON_BY_TAB_BAR_LABEL[label]; diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index 968431de493..6ffd341c1ab 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -67,6 +67,7 @@ import Region from '../../UI/FiatOnRampAggregator/Views/Region'; import ThemeSettings from '../../Views/ThemeSettings'; import { colors as importedColors } from '../../../styles/common'; import OrderDetails from '../../UI/FiatOnRampAggregator/Views/OrderDetails'; +import TabBar from '../../../component-library/components/Navigation/TabBar'; import BrowserUrlModal from '../../Views/BrowserUrlModal'; import Routes from '../../../constants/navigation/Routes'; @@ -78,9 +79,6 @@ const styles = StyleSheet.create({ width: 125, height: 50, }, - hidden: { - opacity: 0, - }, }); const clearStackNavigatorOptions = { @@ -187,6 +185,16 @@ const WalletTabStackFlow = () => ( ); +const TransactionsHome = () => ( + + + + +); + const WalletTabModalFlow = () => ( @@ -214,43 +222,42 @@ const BrowserFlow = () => ( ); -const TransactionsHome = () => ( - - - - -); - export const DrawerContext = React.createContext({ drawerRef: null }); const HomeTabs = () => { const drawerRef = useRef(null); + const options = { + home: { + tabBarLabel: 'Wallet', + }, + browser: { + tabBarLabel: 'Browser', + }, + }; + return ( ( + + )} > - @@ -616,9 +623,10 @@ const MainNavigator = () => ( }), }} /> - + + StyleSheet.create({ bottomBar: { backgroundColor: colors.background.default, flexDirection: 'row', - paddingBottom: - Device.isIphoneX() && Device.isIos() - ? defaultBottomBarPadding + HOME_INDICATOR_HEIGHT - : defaultBottomBarPadding, flex: 0, borderTopWidth: Device.isAndroid() ? 0 : StyleSheet.hairlineWidth, borderColor: colors.border.muted, diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js index 293115f553d..be5526600bd 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -681,6 +681,7 @@ class DrawerView extends PureComponent { }); }; + // NOTE: do we need this event? trackOpenBrowserEvent = () => { const { network } = this.props; AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.BROWSER_OPENED, { @@ -704,13 +705,16 @@ class DrawerView extends PureComponent { goToBrowser = () => { this.props.navigation.navigate(Routes.BROWSER_TAB_HOME); this.hideDrawer(); + // Q: duplicated analytic event? this.trackOpenBrowserEvent(); + // NOTE: do we need this event? this.trackEvent(ANALYTICS_EVENT_OPTS.NAVIGATION_TAPS_BROWSER); }; showWallet = () => { this.props.navigation.navigate('WalletTabHome'); this.hideDrawer(); + // NOTE: do we need this event? this.trackEvent(ANALYTICS_EVENTS_V2.WALLET_OPENED); }; @@ -940,26 +944,6 @@ class DrawerView extends PureComponent { } return [ [ - { - name: strings('drawer.browser'), - icon: this.getIcon('globe'), - selectedIcon: this.getSelectedIcon('globe'), - action: this.goToBrowser, - routeNames: ['BrowserView', 'AddBookmark'], - }, - { - name: strings('drawer.wallet'), - icon: this.getImageIcon('wallet'), - selectedIcon: this.getSelectedImageIcon('wallet'), - action: this.showWallet, - routeNames: [ - 'Wallet', - 'WalletView', - 'Asset', - 'AddAsset', - 'Collectible', - ], - }, { name: strings('drawer.transaction_activity'), icon: this.getFeatherIcon('list'), diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index ee2d3eb17bd..2a0c974a15f 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -11,7 +11,6 @@ import { View, StyleSheet, Image, - Keyboard, InteractionManager, } from 'react-native'; import { fontStyles, colors as importedColors } from '../../../styles/common'; @@ -117,13 +116,12 @@ const metamask_fox = require('../../../images/fox.png'); // eslint-disable-line * @param {bool} disableNetwork - Boolean that specifies if the network can be changed, defaults to false * @returns {Object} - Corresponding navbar options containing headerTitle, headerLeft, headerTruncatedBackTitle and headerRight */ -export default function getNavbarOptions( +export function getTransactionsNavbarOptions( title, - disableNetwork = false, - drawerRef, themeColors, + navigation, selectedAddress, - onRightButtonPress, + handleRightButtonPress, ) { const innerStyles = StyleSheet.create({ headerStyle: { @@ -134,31 +132,33 @@ export default function getNavbarOptions( headerIcon: { color: themeColors.primary.default, }, + headerButtonText: { + color: themeColors.primary.default, + fontSize: 14, + ...fontStyles.normal, + }, }); - function onPress() { - Keyboard.dismiss(); - drawerRef.current?.showDrawer?.(); - trackEvent(ANALYTICS_EVENT_OPTS.COMMON_TAPS_HAMBURGER_MENU); + function handleLeftButtonPress() { + return navigation?.pop(); } return { - headerTitle: () => ( - - ), + headerTitle: () => , headerLeft: () => ( - - + + + {strings('navigation.close')} + ), headerRight: () => ( ), headerStyle: innerStyles.headerStyle, diff --git a/app/components/UI/Transactions/index.js b/app/components/UI/Transactions/index.js index 6ed00cce63c..d07a74e5c21 100644 --- a/app/components/UI/Transactions/index.js +++ b/app/components/UI/Transactions/index.js @@ -57,7 +57,6 @@ const createStyles = (colors) => margin: 0, }, emptyContainer: { - flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: colors.background.default, diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js index 2e70eb84f9d..e462e3b4961 100644 --- a/app/components/Views/ActivityView/index.js +++ b/app/components/Views/ActivityView/index.js @@ -1,16 +1,15 @@ -import React, { useEffect, useContext } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { View, StyleSheet } from 'react-native'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import { useSelector } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { getHasOrders } from '../../../reducers/fiatOrders'; -import getNavbarOptions from '../../UI/Navbar'; +import { getTransactionsNavbarOptions } from '../../UI/Navbar'; import TransactionsView from '../TransactionsView'; import TabBar from '../../Base/TabBar'; import { strings } from '../../../../locales/i18n'; import FiatOrdersView from '../FiatOrdersView'; import ErrorBoundary from '../ErrorBoundary'; -import { DrawerContext } from '../../Nav/Main/MainNavigator'; import { useTheme } from '../../../util/theme'; import Routes from '../../../constants/navigation/Routes'; import AnalyticsV2 from '../../../util/analyticsV2'; @@ -22,7 +21,6 @@ const styles = StyleSheet.create({ }); const ActivityView = () => { - const { drawerRef } = useContext(DrawerContext); const { colors } = useTheme(); const navigation = useNavigation(); const selectedAddress = useSelector( @@ -34,7 +32,7 @@ const ActivityView = () => { (state) => state.engine.backgroundState.AccountTrackerController.accounts, ); - const openAccountSelector = () => { + const openAccountSelector = useCallback(() => { navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.ACCOUNT_SELECTOR, }); @@ -45,18 +43,17 @@ const ActivityView = () => { number_of_accounts: Object.keys(accounts ?? {}).length, }, ); - }; + }, [navigation, accounts]); useEffect( () => { const title = hasOrders ?? false ? 'activity_view.title' : 'transactions_view.title'; navigation.setOptions( - getNavbarOptions( + getTransactionsNavbarOptions( title, - false, - drawerRef, colors, + navigation, selectedAddress, openAccountSelector, ), @@ -84,6 +81,6 @@ const ActivityView = () => { ); -}; +} export default ActivityView; From a64253f49ef484874c8bea265ec2943c220e6a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <1649425+jpcloureiro@users.noreply.github.com> Date: Thu, 29 Sep 2022 01:09:01 +0100 Subject: [PATCH 12/91] [PS] Browser nav bar facelift (#4907) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values * Organize account hooks and utils * Export ENS cache. Update account util to return ENS name. * Update account selector * Remove account selector styles * Hook up permission system controller logic * Connect PS to browser. Pass from address to transactions. * Create first time connect sheet * Create permissions sheet * Add translations to permission system * (BottomTabBar): implement new BottomTabBar * (ActivityView): move nav stack to wallet * (Transactions): update navigation header * (ActivityView): fix back button onPress * (ActivityView): fix getNavbarOptions args * (BrowserBottomBar): remove bottomPadding * (Transactions): fix empty screen height * (Transactions): setup main modal stack navigator * (BrowserNav): implement new PS browser navbar * (Navbar): fix AccountRightButton onPress event Co-authored-by: Cal Leung --- .../UI/AccountRightButton/index.tsx | 1 + .../UI/BrowserUrlBar/BrowserUrlBar.styles.ts | 20 +++++ .../UI/BrowserUrlBar/BrowserUrlBar.tsx | 55 ++++++++++++ .../UI/BrowserUrlBar/BrowserUrlBar.types.ts | 5 ++ app/components/UI/BrowserUrlBar/index.ts | 1 + app/components/UI/Navbar/index.js | 88 ++++--------------- app/components/Views/Browser/index.js | 2 +- 7 files changed, 100 insertions(+), 72 deletions(-) create mode 100644 app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts create mode 100644 app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx create mode 100644 app/components/UI/BrowserUrlBar/BrowserUrlBar.types.ts create mode 100644 app/components/UI/BrowserUrlBar/index.ts diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx index f1b1fa8522f..ffc43e3348f 100644 --- a/app/components/UI/AccountRightButton/index.tsx +++ b/app/components/UI/AccountRightButton/index.tsx @@ -48,6 +48,7 @@ const AccountRightButton = ({ ); const dispatch = useDispatch(); let onPressButton = onPress; + if (!selectedAddress && isNetworkVisible) { onPressButton = () => dispatch(toggleNetworkModal(false)); } diff --git a/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts b/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts new file mode 100644 index 00000000000..ac085b04568 --- /dev/null +++ b/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts @@ -0,0 +1,20 @@ +import { StyleSheet } from 'react-native'; + +import { Theme } from '../../../util/theme/models'; + +const styleSheet = (params: { theme: Theme }) => + StyleSheet.create({ + main: { + flexDirection: 'row', + marginLeft: 23, + marginRight: 23, + }, + text: { + flex: 1, + marginLeft: 6, + color: params.theme.colors.icon.alternative, + width: 260, + }, + }); + +export default styleSheet; diff --git a/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx b/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx new file mode 100644 index 00000000000..b5aa0f6da6a --- /dev/null +++ b/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { TouchableOpacity, View } from 'react-native'; + +import { useStyles } from '../../../component-library/hooks'; +import { isGatewayUrl } from '../../../lib/ens-ipfs/resolver'; +import AppConstants from '../../../core/AppConstants'; +import Icon, { IconName } from '../../../component-library/components/Icon'; +import Text from '../../../component-library/components/Text'; + +import { BrowserUrlBarProps } from './BrowserUrlBar.types'; +import stylesheet from './BrowserUrlBar.styles'; + +const BrowserUrlBar = ({ url, route, onPress }: BrowserUrlBarProps) => { + const getDappMainUrl = () => { + if (!url) return; + + const urlObj = new URL(url); + const ensUrl = route.params?.currentEnsName ?? ''; + + if ( + isGatewayUrl(urlObj) && + url.search(`${AppConstants.IPFS_OVERRIDE_PARAM}=false`) === -1 && + Boolean(ensUrl) + ) { + return ensUrl.toLowerCase().replace(/^www\./, ''); + } + return urlObj.host.toLowerCase().replace(/^www\./, ''); + }; + + const isHttps = url && url.toLowerCase().substr(0, 6) === 'https:'; + + const secureConnectionIcon = isHttps + ? IconName.LockFilled + : IconName.LockSlashFilled; + + const mainUrl = getDappMainUrl(); + + const { styles, theme } = useStyles(stylesheet, {}); + + return ( + + + + + {mainUrl} + + + + ); +}; + +export default BrowserUrlBar; diff --git a/app/components/UI/BrowserUrlBar/BrowserUrlBar.types.ts b/app/components/UI/BrowserUrlBar/BrowserUrlBar.types.ts new file mode 100644 index 00000000000..342f173b7d0 --- /dev/null +++ b/app/components/UI/BrowserUrlBar/BrowserUrlBar.types.ts @@ -0,0 +1,5 @@ +export interface BrowserUrlBarProps { + url: string; + route: any; + onPress: () => void; +} diff --git a/app/components/UI/BrowserUrlBar/index.ts b/app/components/UI/BrowserUrlBar/index.ts new file mode 100644 index 00000000000..5abf0923371 --- /dev/null +++ b/app/components/UI/BrowserUrlBar/index.ts @@ -0,0 +1 @@ +export { default } from './BrowserUrlBar'; diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index 2a0c974a15f..25ab6bbfa0d 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -3,7 +3,6 @@ import React from 'react'; import NavbarTitle from '../NavbarTitle'; import ModalNavbarTitle from '../ModalNavbarTitle'; import AccountRightButton from '../AccountRightButton'; -import NavbarBrowserTitle from '../NavbarBrowserTitle'; import { Alert, Text, @@ -18,7 +17,6 @@ import IonicIcon from 'react-native-vector-icons/Ionicons'; import AntIcon from 'react-native-vector-icons/AntDesign'; import EvilIcons from 'react-native-vector-icons/EvilIcons'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; -import URL from 'url-parse'; import { strings } from '../../../../locales/i18n'; import AppConstants from '../../../core/AppConstants'; import DeeplinkManager from '../../../core/DeeplinkManager'; @@ -26,12 +24,9 @@ import Analytics from '../../../core/Analytics/Analytics'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; import { importAccountFromPrivateKey } from '../../../util/address'; import Device from '../../../util/device'; -import { isGatewayUrl } from '../../../lib/ens-ipfs/resolver'; -import { getHost } from '../../../util/browser'; import { BACK_ARROW_BUTTON_ID } from '../../../constants/test-ids'; import PickerNetwork from '../../../component-library/components/Pickers/PickerNetwork'; - -const { HOMEPAGE_URL } = AppConstants; +import BrowserUrlBar from '../BrowserUrlBar'; const trackEvent = (event) => { InteractionManager.runAfterInteractions(() => { @@ -84,7 +79,12 @@ const styles = StyleSheet.create({ }, browserRightButton: { flex: 1, - marginRight: Device.isAndroid() ? 10 : 0, + marginRight: Device.isAndroid() ? 17 : 18, + marginLeft: Device.isAndroid() ? 7 : 0, + marginTop: 12, + marginBottom: 12, + alignItems: 'center', + justifyContent: 'center', }, disabled: { opacity: 0.3, @@ -569,12 +569,7 @@ export function getSendFlowTitle(title, navigation, route, themeColors) { * @param {Object} navigation - Navigation object required to push new views * @returns {Object} - Corresponding navbar options containing headerTitle, headerLeft and headerRight */ -export function getBrowserViewNavbarOptions( - navigation, - route, - drawerRef, - themeColors, -) { +export function getBrowserViewNavbarOptions(route, themeColors) { const innerStyles = StyleSheet.create({ headerStyle: { backgroundColor: themeColors.background.default, @@ -587,75 +582,26 @@ export function getBrowserViewNavbarOptions( }); const url = route.params?.url ?? ''; - let host = null; - let isHttps = false; - const isHomepage = (url) => getHost(url) === getHost(HOMEPAGE_URL); - const error = route.params?.error ?? ''; - const icon = route.params?.icon; + const handleUrlPress = () => route.params?.showUrlModal?.(); const setAccountsPermissionsVisible = route.params?.setAccountsPermissionsVisible; - const connectedAccounts = route.params?.connectedAccounts; - - if (url && !isHomepage(url)) { - isHttps = url && url.toLowerCase().substr(0, 6) === 'https:'; - const urlObj = new URL(url); - //Using host so the port number will be displayed on the address bar - host = urlObj.host.toLowerCase().replace(/^www\./, ''); - if ( - isGatewayUrl(urlObj) && - url.search(`${AppConstants.IPFS_OVERRIDE_PARAM}=false`) === -1 - ) { - const ensUrl = route.params?.currentEnsName ?? ''; - if (ensUrl) { - host = ensUrl.toLowerCase().replace(/^www\./, ''); - } - } - } else { - host = strings('browser.title'); - } - function onPress() { - Keyboard.dismiss(); - drawerRef.current?.showDrawer?.(); - trackEvent(ANALYTICS_EVENT_OPTS.COMMON_TAPS_HAMBURGER_MENU); - } + const connectedAccounts = route.params?.connectedAccounts; return { gestureEnabled: false, headerLeft: () => ( - - - - ), - headerTitle: () => ( - + ), + headerTitle: null, headerRight: () => ( - - - + ), headerStyle: innerStyles.headerStyle, }; diff --git a/app/components/Views/Browser/index.js b/app/components/Views/Browser/index.js index cf65893aa7f..969f5dc95fd 100644 --- a/app/components/Views/Browser/index.js +++ b/app/components/Views/Browser/index.js @@ -47,7 +47,7 @@ const Browser = (props) => { useEffect( () => { navigation.setOptions( - getBrowserViewNavbarOptions(navigation, route, drawerRef, colors), + getBrowserViewNavbarOptions(route, colors), ); }, /* eslint-disable-next-line */ From 7b62ec356166f68d529a00054c23e676edfa311a Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Mon, 3 Oct 2022 09:26:19 -0700 Subject: [PATCH 13/91] Feature/5068 code clean up cal (#5073) * Feature/4768 action selector sheet (#4891) * Create AccountSelectionList * Create temp loader component * Create temp sheet actions component * Fix AvatarGroup positioning * Provide reserved overlay spacing for bottom sheet * Fix sheet header typo * Make analytics params optional * Rename AccountSelectorList * Create new account selector * Add AccountSelector to navigation stack * Replace all account selection logic * Improve caching logic for ENS name fetch. Introduce 1 hour cache to optimize for network calls. Fetching from middle out beginning with selected account. * Add lodash debounce to prevent taps in quick succession on bottom sheet * Throw away any debounce innvocation when bottom sheet is unmounted * Introduce loading to account selector. Readd test IDS * Remove old account list * Clean up useAccounts * Update snapshots * Fix logic after pulling from main * Update snapshots * [PS] Connect ps to browser (#5051) * Add new logos * Update styles. Add missing exports. * Patch controllers. Introduce react-native-get-random-values * Organize account hooks and utils * Export ENS cache. Update account util to return ENS name. * Update account selector * Remove account selector styles * Hook up permission system controller logic * Connect PS to browser. Pass from address to transactions. * Update toast copy * Fix misc * Hide invalid fiat balance * Comment permission util for clarity * Move account type out of Toast * Update imports * Replace account depedency in browser tab * Update styles * Prevent auto account selection on account creation * Remove spacing from toast translation strings * Update browser icon * Remove all permissions when deleting wallet * Fix AvatarNetwork showfallback * Open separate browser from warning * Remove unnecessary padding on browser * Show account switch toast when switching between tabs * Fix unrecognized network logos * Update snapshots * Pull full screen modal/sheets to Root modal flow in nav stack * Fix browser tab test * Fix test * Fix NetworkList test * Remove redundant useAccounts --- .../Avatars/AvatarNetwork/AvatarNetwork.tsx | 6 +- .../Card/__snapshots__/Card.test.tsx.snap | 2 +- .../components/Icon/assets/explore-filled.svg | 3 +- .../Navigation/TabBar/TabBar.styles.ts | 13 +- .../components/Tags/TagUrl/TagUrl.styles.ts | 7 +- .../components/Tags/TagUrl/TagUrl.tsx | 8 +- .../TagUrl/__snapshots__/TagUrl.test.tsx.snap | 6 +- .../components/Toast/README.md | 1 + .../components/Toast/Toast.constants.ts | 4 + .../components/Toast/Toast.stories.tsx | 2 + .../components/Toast/Toast.tsx | 10 +- .../components/Toast/Toast.types.ts | 4 + app/components/Nav/App/index.js | 42 +++++- app/components/Nav/Main/MainNavigator.js | 28 ---- app/components/Nav/Main/index.js | 2 +- .../UI/AccountRightButton/index.tsx | 2 +- .../AccountSelectorList.tsx | 2 +- app/components/UI/AssetOverview/index.js | 7 +- .../__snapshots__/index.test.tsx.snap | 1 - .../UI/BrowserUrlBar/BrowserUrlBar.styles.ts | 1 + .../UI/BrowserUrlBar/BrowserUrlBar.tsx | 10 +- app/components/UI/Navbar/index.js | 9 -- .../__snapshots__/index.test.tsx.snap | 1 + app/components/UI/NetworkList/index.js | 14 +- app/components/UI/NetworkList/index.test.tsx | 3 + .../UI/Tabs/__snapshots__/index.test.tsx.snap | 3 +- app/components/UI/Tabs/index.js | 3 +- app/components/UI/Tokens/index.js | 5 +- .../Views/AccountConnect/AccountConnect.tsx | 50 +++---- .../AccountConnect/AccountConnect.types.ts | 4 +- .../AccountConnectMultiSelector.tsx | 2 +- .../AccountConnectSingle.tsx | 2 +- .../AccountPermissions/AccountPermissions.tsx | 32 +++-- .../AccountPermissionsConnected.tsx | 5 +- .../AccountPermissionsConnected.types.ts | 2 + .../AccountPermissionsRevoke.tsx | 8 +- .../AccountPermissionsRevoke.types.ts | 4 +- .../AccountSelector/AccountSelector.types.ts | 2 +- app/components/Views/ActivityView/index.js | 2 +- app/components/Views/Asset/index.js | 9 +- app/components/Views/AssetDetails/index.tsx | 61 ++++---- app/components/Views/Browser/index.js | 69 +++++++-- .../__snapshots__/index.test.tsx.snap | 135 +++++------------- app/components/Views/BrowserTab/index.js | 47 +----- .../Views/BrowserTab/index.test.tsx | 34 ++++- .../Views/Settings/NetworksSettings/index.js | 8 +- .../hooks/useAccounts/useAccounts.ts | 11 +- app/core/Engine.js | 4 + app/core/Permissions/index.ts | 7 + app/core/RPCMethods/RPCMethodMiddleware.ts | 1 - 50 files changed, 380 insertions(+), 318 deletions(-) diff --git a/app/component-library/components/Avatars/AvatarNetwork/AvatarNetwork.tsx b/app/component-library/components/Avatars/AvatarNetwork/AvatarNetwork.tsx index 7b54358a17d..19d02a3af0f 100644 --- a/app/component-library/components/Avatars/AvatarNetwork/AvatarNetwork.tsx +++ b/app/component-library/components/Avatars/AvatarNetwork/AvatarNetwork.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/prop-types */ // Third party dependencies. -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Image, ImageSourcePropType } from 'react-native'; // External dependencies. @@ -30,6 +30,10 @@ const AvatarNetwork = ({ const onError = useCallback(() => setShowFallback(true), [setShowFallback]); + useEffect(() => { + setShowFallback(!imageSource); + }, [imageSource]); + return ( {showFallback ? ( diff --git a/app/component-library/components/Cards/Card/__snapshots__/Card.test.tsx.snap b/app/component-library/components/Cards/Card/__snapshots__/Card.test.tsx.snap index dffa904dbf4..67591a7ddf0 100644 --- a/app/component-library/components/Cards/Card/__snapshots__/Card.test.tsx.snap +++ b/app/component-library/components/Cards/Card/__snapshots__/Card.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Card - Snapshot should render correctly 1`] = ` - - + + diff --git a/app/component-library/components/Navigation/TabBar/TabBar.styles.ts b/app/component-library/components/Navigation/TabBar/TabBar.styles.ts index fa1368d670a..a555a6f830b 100644 --- a/app/component-library/components/Navigation/TabBar/TabBar.styles.ts +++ b/app/component-library/components/Navigation/TabBar/TabBar.styles.ts @@ -1,6 +1,9 @@ // Third party dependencies. import { StyleSheet } from 'react-native'; +// External dependencies. +import { Theme } from '../../../../util/theme/models'; + // Internal dependencies. import { TabBarStyleSheetVars } from './TabBar.types'; @@ -12,15 +15,19 @@ import { TabBarStyleSheetVars } from './TabBar.types'; * @param params.vars Inputs that the style sheet depends on. * @returns StyleSheet object. */ -const styleSheet = (params: { vars: TabBarStyleSheetVars }) => { - const { vars } = params; - const { bottomInset } = vars; +const styleSheet = (params: { vars: TabBarStyleSheetVars; theme: Theme }) => { + const { + vars: { bottomInset }, + theme: { colors }, + } = params; return StyleSheet.create({ base: { flexDirection: 'row', alignItems: 'center', height: 82, marginBottom: bottomInset, + borderTopWidth: 0.5, + borderColor: colors.border.muted, }, }); }; diff --git a/app/component-library/components/Tags/TagUrl/TagUrl.styles.ts b/app/component-library/components/Tags/TagUrl/TagUrl.styles.ts index 661ba237054..41373df52b4 100644 --- a/app/component-library/components/Tags/TagUrl/TagUrl.styles.ts +++ b/app/component-library/components/Tags/TagUrl/TagUrl.styles.ts @@ -35,8 +35,10 @@ const styleSheet = (params: { theme: Theme; vars: TagUrlStyleSheetVars }) => { } as ViewStyle, style, ) as ViewStyle, + favicon: { + marginRight: 8, + }, label: { - marginLeft: 8, color: colors.text.alternative, flexShrink: 1, }, @@ -44,7 +46,8 @@ const styleSheet = (params: { theme: Theme; vars: TagUrlStyleSheetVars }) => { marginLeft: 16, }, icon: { - marginLeft: 8, + color: colors.icon.alternative, + marginRight: 4, }, }); }; diff --git a/app/component-library/components/Tags/TagUrl/TagUrl.tsx b/app/component-library/components/Tags/TagUrl/TagUrl.tsx index caa7b5806ba..6a657421dbf 100644 --- a/app/component-library/components/Tags/TagUrl/TagUrl.tsx +++ b/app/component-library/components/Tags/TagUrl/TagUrl.tsx @@ -27,9 +27,13 @@ const TagUrl = ({ const { styles } = useStyles(styleSheet, { style }); return ( - + {iconName ? ( - + ) : null} {label} diff --git a/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap b/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap index 544a93bedbd..ec2cc971dc2 100644 --- a/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap +++ b/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap @@ -24,13 +24,17 @@ exports[`TagUrl should render correctly 1`] = ` } } size="32" + style={ + Object { + "marginRight": 8, + } + } /> { { label: LABEL_CHUNK_2, isBold: true }, ], accountAddress: ACCOUNT_ADDRESS, + accountAvatarType: ACCOUNT_AVATAR_TYPE, linkButtonOptions: { label: LINK_LABEL, onPress: ONPRESS_HANDLER, diff --git a/app/component-library/components/Toast/Toast.constants.ts b/app/component-library/components/Toast/Toast.constants.ts index 2175cfd3837..6bf3eda8f71 100644 --- a/app/component-library/components/Toast/Toast.constants.ts +++ b/app/component-library/components/Toast/Toast.constants.ts @@ -1,6 +1,10 @@ /* eslint-disable import/prefer-default-export */ +// External dependencies. +import { AvatarAccountType } from '../Avatars/AvatarAccount'; + export const TEST_ACCOUNT_ADDRESS = '0x10e08af911f2e489480fb2855b24771745d0198b50f5c55891369844a8c57092'; export const TEST_NETWORK_IMAGE_URL = 'https://assets.coingecko.com/coins/images/279/small/ethereum.png?1595348880'; +export const TEST_AVATAR_TYPE = AvatarAccountType.JazzIcon; diff --git a/app/component-library/components/Toast/Toast.stories.tsx b/app/component-library/components/Toast/Toast.stories.tsx index c856d8bc9df..b45c11d3307 100644 --- a/app/component-library/components/Toast/Toast.stories.tsx +++ b/app/component-library/components/Toast/Toast.stories.tsx @@ -18,6 +18,7 @@ import { ToastContext, ToastContextWrapper } from './Toast.context'; import { ToastVariant } from './Toast.types'; import { TEST_ACCOUNT_ADDRESS, + TEST_AVATAR_TYPE, TEST_NETWORK_IMAGE_URL, } from './Toast.constants'; @@ -38,6 +39,7 @@ const ToastExample = () => { { label: ' Account 2.', isBold: true }, ], accountAddress: TEST_ACCOUNT_ADDRESS, + accountAvatarType: TEST_AVATAR_TYPE, }); }} /> diff --git a/app/component-library/components/Toast/Toast.tsx b/app/component-library/components/Toast/Toast.tsx index 3907d5c9d16..4f8b2918617 100644 --- a/app/component-library/components/Toast/Toast.tsx +++ b/app/component-library/components/Toast/Toast.tsx @@ -25,7 +25,7 @@ import Animated, { import { useSafeAreaInsets } from 'react-native-safe-area-context'; // External dependencies. -import AvatarAccount, { AvatarAccountType } from '../Avatars/AvatarAccount'; +import AvatarAccount from '../Avatars/AvatarAccount'; import AvatarNetwork from '../Avatars/AvatarNetwork'; import { AvatarBaseSize } from '../Avatars/AvatarBase'; import Text, { TextVariant } from '../Texts/Text'; @@ -40,7 +40,6 @@ import { ToastVariant, } from './Toast.types'; import styles from './Toast.styles'; -import { useSelector } from 'react-redux'; const visibilityDuration = 2750; const animationDuration = 250; @@ -62,11 +61,6 @@ const Toast = forwardRef((_, ref: React.ForwardedRef) => { /* eslint-disable-next-line */ [], ); - const accountAvatarType = useSelector((state: any) => - state.settings.useBlockieIcon - ? AvatarAccountType.Blockies - : AvatarAccountType.JazzIcon, - ); const showToast = (options: ToastOptions) => { let timeoutDuration = 0; @@ -138,7 +132,7 @@ const Toast = forwardRef((_, ref: React.ForwardedRef) => { case ToastVariant.Plain: return null; case ToastVariant.Account: { - const { accountAddress } = toastOptions; + const { accountAddress, accountAvatarType } = toastOptions; return ( ({ + overlayStyle: { + opacity: 0, + }, + }), + }, + animationEnabled: false, +}; const Stack = createStackNavigator(); /** @@ -341,15 +358,22 @@ const App = ({ userLoggedIn }) => { return null; }; - const RootModalFlow = () => ( + const DetectedTokensFlow = () => ( + + + + ); + + const RootModalFlow = () => ( + { name={Routes.MODAL.TURN_OFF_REMEMBER_ME} component={TurnOffRememberMeModal} /> + + + ); diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index 6ffd341c1ab..fe14a3b326e 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -16,12 +16,8 @@ import AppInformation from '../../Views/Settings/AppInformation'; import Contacts from '../../Views/Settings/Contacts'; import Wallet from '../../Views/Wallet'; import Asset from '../../Views/Asset'; -import AssetOptions from '../../Views/AssetOptions'; import AssetDetails from '../../Views/AssetDetails'; import AddAsset from '../../Views/AddAsset'; -import AssetHideConfirmation from '../../Views/AssetHideConfirmation'; -import DetectedTokens from '../../Views/DetectedTokens'; -import DetectedTokensConfirmation from '../../Views/DetectedTokensConfirmation'; import Collectible from '../../Views/Collectible'; import Send from '../../Views/Send'; import SendTo from '../../Views/SendFlow/SendTo'; @@ -94,20 +90,6 @@ const clearStackNavigatorOptions = { animationEnabled: false, }; -const DetectedTokensFlow = () => ( - - - - -); - const WalletModalFlow = () => ( ( component={Wallet} options={{ headerShown: true, animationEnabled: false }} /> - ); @@ -146,11 +127,6 @@ const AssetModalFlow = (props) => ( component={AssetStackFlow} initialParams={props.route.params} /> - ); /* eslint-enable react/prop-types */ @@ -198,10 +174,6 @@ const TransactionsHome = () => ( const WalletTabModalFlow = () => ( - ); diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 7491423d185..0502d1c6f17 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -230,7 +230,7 @@ const Main = (props) => { variant: ToastVariant.Network, labelOptions: [ { - label: networkName, + label: `${networkName} `, isBold: true, }, { label: strings('toast.now_active') }, diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx index ffc43e3348f..f315724b68d 100644 --- a/app/components/UI/AccountRightButton/index.tsx +++ b/app/components/UI/AccountRightButton/index.tsx @@ -48,7 +48,7 @@ const AccountRightButton = ({ ); const dispatch = useDispatch(); let onPressButton = onPress; - + if (!selectedAddress && isNetworkVisible) { onPressButton = () => dispatch(toggleNetworkModal(false)); } diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 2d0552726d8..f152734a5c6 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -10,7 +10,7 @@ import Cell, { CellVariants, } from '../../../component-library/components/Cells/Cell'; import { useStyles } from '../../../component-library/hooks'; -import Text from '../../../component-library/components/Text'; +import Text from '../../../component-library/components/Texts/Text'; import AvatarGroup from '../../../component-library/components/Avatars/AvatarGroup'; import { formatAddress } from '../../../util/address'; import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; diff --git a/app/components/UI/AssetOverview/index.js b/app/components/UI/AssetOverview/index.js index c2b5f12d814..0afab4b7072 100644 --- a/app/components/UI/AssetOverview/index.js +++ b/app/components/UI/AssetOverview/index.js @@ -223,11 +223,10 @@ class AssetOverview extends PureComponent { }; goToBrowserUrl(url) { - this.props.navigation.navigate(Routes.BROWSER_TAB_HOME, { - screen: Routes.BROWSER_VIEW, + this.props.navigation.navigate('Webview', { + screen: 'SimpleWebview', params: { - newTabUrl: url, - timestamp: Date.now(), + url, }, }); } diff --git a/app/components/UI/BrowserBottomBar/__snapshots__/index.test.tsx.snap b/app/components/UI/BrowserBottomBar/__snapshots__/index.test.tsx.snap index aabbed68a4c..6a729dc2e4e 100644 --- a/app/components/UI/BrowserBottomBar/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/BrowserBottomBar/__snapshots__/index.test.tsx.snap @@ -11,7 +11,6 @@ exports[`BrowserBottomBar should render correctly 1`] = ` "flex": 0, "flexDirection": "row", "justifyContent": "space-between", - "paddingBottom": 18, } } > diff --git a/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts b/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts index ac085b04568..c85791b5485 100644 --- a/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts +++ b/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts @@ -8,6 +8,7 @@ const styleSheet = (params: { theme: Theme }) => flexDirection: 'row', marginLeft: 23, marginRight: 23, + alignItems: 'center', }, text: { flex: 1, diff --git a/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx b/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx index b5aa0f6da6a..ac7cec58be5 100644 --- a/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx +++ b/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { TouchableOpacity, View } from 'react-native'; - import { useStyles } from '../../../component-library/hooks'; import { isGatewayUrl } from '../../../lib/ens-ipfs/resolver'; import AppConstants from '../../../core/AppConstants'; -import Icon, { IconName } from '../../../component-library/components/Icon'; -import Text from '../../../component-library/components/Text'; - +import Icon, { + IconName, + IconSize, +} from '../../../component-library/components/Icon'; +import Text from '../../../component-library/components/Texts/Text'; import { BrowserUrlBarProps } from './BrowserUrlBar.types'; import stylesheet from './BrowserUrlBar.styles'; @@ -43,6 +44,7 @@ const BrowserUrlBar = ({ url, route, onPress }: BrowserUrlBarProps) => { {mainUrl} diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index 25ab6bbfa0d..ecefcb3897b 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -77,15 +77,6 @@ const styles = StyleSheet.create({ paddingRight: Device.isAndroid() ? 22 : 18, marginTop: 5, }, - browserRightButton: { - flex: 1, - marginRight: Device.isAndroid() ? 17 : 18, - marginLeft: Device.isAndroid() ? 7 : 0, - marginTop: 12, - marginBottom: 12, - alignItems: 'center', - justifyContent: 'center', - }, disabled: { opacity: 0.3, }, diff --git a/app/components/UI/NetworkList/__snapshots__/index.test.tsx.snap b/app/components/UI/NetworkList/__snapshots__/index.test.tsx.snap index 764d8cac2f7..9d3e59ac90b 100644 --- a/app/components/UI/NetworkList/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/NetworkList/__snapshots__/index.test.tsx.snap @@ -22,5 +22,6 @@ exports[`NetworkList should render correctly 1`] = ` "type": "mainnet", } } + shouldNetworkSwitchPopToWallet={false} /> `; diff --git a/app/components/UI/NetworkList/index.js b/app/components/UI/NetworkList/index.js index 1dc6e8613a2..4bc1e890ac7 100644 --- a/app/components/UI/NetworkList/index.js +++ b/app/components/UI/NetworkList/index.js @@ -30,6 +30,8 @@ import { NETWORK_SCROLL_ID, } from '../../../constants/test-ids'; import ImageIcon from '../ImageIcon'; +import AvatarNetwork from '../../../component-library/components/Avatars/AvatarNetwork'; +import { AvatarBaseSize } from '../../../component-library/components/Avatars/AvatarBase'; const createStyles = (colors) => StyleSheet.create({ @@ -262,6 +264,7 @@ export class NetworkList extends PureComponent { i, network, isCustomRpc, + color, ) => { const styles = this.getStyles(); @@ -276,7 +279,11 @@ export class NetworkList extends PureComponent { (image ? ( ) : ( - + ))} {!isCustomRpc && (image ? ( @@ -285,7 +292,7 @@ export class NetworkList extends PureComponent { style={styles.networkIcon} /> ) : ( - + {name[0]} ))} @@ -303,7 +310,7 @@ export class NetworkList extends PureComponent { const colors = this.context.colors || mockTheme.colors; return this.getOtherNetworks().map((network, i) => { - const { name, imageSource } = Networks[network]; + const { name, imageSource, color } = Networks[network]; const isCustomRpc = false; const selected = provider.type === network ? ( @@ -317,6 +324,7 @@ export class NetworkList extends PureComponent { i, network, isCustomRpc, + color, ); }); }; diff --git a/app/components/UI/NetworkList/index.test.tsx b/app/components/UI/NetworkList/index.test.tsx index fefb406253a..4d19be6e771 100644 --- a/app/components/UI/NetworkList/index.test.tsx +++ b/app/components/UI/NetworkList/index.test.tsx @@ -20,6 +20,9 @@ const initialState = { networkOnboarded: { networkOnboardedState: [{ network: 'mainnet', onboarded: true }], }, + modals: { + shouldNetworkSwitchPopToWallet: false, + }, }; const store = mockStore(initialState); diff --git a/app/components/UI/Tabs/__snapshots__/index.test.tsx.snap b/app/components/UI/Tabs/__snapshots__/index.test.tsx.snap index e828469a62b..3fa051a042e 100644 --- a/app/components/UI/Tabs/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/Tabs/__snapshots__/index.test.tsx.snap @@ -46,8 +46,7 @@ exports[`Tabs should render correctly 1`] = ` Object { "backgroundColor": "#FFFFFF", "flexDirection": "row", - "height": 80, - "marginBottom": 0, + "height": 50, "paddingHorizontal": 20, "paddingTop": 17, "shadowColor": "#0000001A", diff --git a/app/components/UI/Tabs/index.js b/app/components/UI/Tabs/index.js index fcdee02e617..61a7f7fdbc4 100644 --- a/app/components/UI/Tabs/index.js +++ b/app/components/UI/Tabs/index.js @@ -84,11 +84,10 @@ const createStyles = (colors, shadows) => tabActions: { paddingHorizontal: 20, flexDirection: 'row', - marginBottom: Device.isIphoneX() ? 0 : 0, paddingTop: 17, ...shadows.size.md, backgroundColor: colors.background.default, - height: Device.isIphoneX() ? 80 : 50, + height: 50, }, tabs: { flex: 1, diff --git a/app/components/UI/Tokens/index.js b/app/components/UI/Tokens/index.js index ca2e342e8d2..5260bce555f 100644 --- a/app/components/UI/Tokens/index.js +++ b/app/components/UI/Tokens/index.js @@ -28,6 +28,7 @@ import { ThemeContext, mockTheme } from '../../../util/theme'; import Text from '../../Base/Text'; import NotificationManager from '../../../core/NotificationManager'; import { getDecimalChainId, isTestNet } from '../../../util/networks'; +import Routes from '../../../constants/navigation/Routes'; const createStyles = (colors) => StyleSheet.create({ @@ -323,7 +324,9 @@ class Tokens extends PureComponent { showDetectedTokens = () => { const { NetworkController } = Engine.context; const { detectedTokens } = this.props; - this.props.navigation.navigate('DetectedTokens'); + this.props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: 'DetectedTokens', + }); InteractionManager.runAfterInteractions(() => { AnalyticsV2.trackEvent( AnalyticsV2.ANALYTICS_EVENTS.TOKEN_IMPORT_CLICKED, diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 6d3a6689c92..636560f09c0 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useContext, - useEffect, useMemo, useRef, useState, @@ -32,6 +31,8 @@ import { IconName } from '../../../component-library/components/Icon'; import { getActiveTabUrl } from '../../../util/transactions'; import { getUrlObj } from '../../../util/browser'; import { strings } from '../../../../locales/i18n'; +import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; +import { safeToChecksumAddress } from '../../../util/address'; // Internal dependencies. import { @@ -46,8 +47,6 @@ const AccountConnect = (props: AccountConnectProps) => { const Engine = UntypedEngine as any; const { hostInfo } = props.route.params; const [isLoading, setIsLoading] = useState(false); - const prevSelectedAddress = useRef(); - const shouldAutoSwitchSelectedAccount = useRef(true); const selectedWalletAddress = useSelector( (state: any) => state.engine.backgroundState.PreferencesController.selectedAddress, @@ -63,6 +62,11 @@ const AccountConnect = (props: AccountConnectProps) => { isLoading, }); const { toastRef } = useContext(ToastContext); + const accountAvatarType = useSelector((state: any) => + state.settings.useBlockieIcon + ? AvatarAccountType.Blockies + : AvatarAccountType.JazzIcon, + ); const origin: string = useSelector(getActiveTabUrl, isEqual); // TODO - Once we can pass metadata to permission system, pass origin instead of hostname into this component. const hostname = hostInfo.metadata.origin; @@ -71,7 +75,7 @@ const AccountConnect = (props: AccountConnectProps) => { () => (getUrlObj(origin) as URL).protocol === 'https:' ? IconName.LockFilled - : IconName.WarningFilled, + : IconName.LockSlashFilled, [origin], ); /** @@ -121,23 +125,24 @@ const AccountConnect = (props: AccountConnectProps) => { let labelOptions: ToastOptions['labelOptions'] = []; if (connectedAccountLength > 1) { labelOptions = [ - { label: `${connectedAccountLength}`, isBold: true }, + { label: `${connectedAccountLength} `, isBold: true }, { label: `${strings('toast.accounts_connected')}`, }, - { label: `\n${activeAccountName}`, isBold: true }, + { label: `\n${activeAccountName} `, isBold: true }, { label: strings('toast.now_active') }, ]; } else { labelOptions = [ - { label: activeAccountName, isBold: true }, - { label: strings('toast.connected') }, + { label: `${activeAccountName} `, isBold: true }, + { label: strings('toast.connected_and_active') }, ]; } toastRef?.current?.showToast({ variant: ToastVariant.Account, labelOptions, accountAddress: activeAddress, + accountAvatarType, }); } catch (e: any) { Logger.error(e, 'Error while trying to connect to a dApp.'); @@ -147,16 +152,25 @@ const AccountConnect = (props: AccountConnectProps) => { } }, /* eslint-disable-next-line */ - [selectedAddresses, hostInfo, accounts, ensByAccountAddress, hostname], + [ + selectedAddresses, + hostInfo, + accounts, + ensByAccountAddress, + hostname, + accountAvatarType, + ], ); const onCreateAccount = useCallback(async (isMultiSelect?: boolean) => { - const { KeyringController, PreferencesController } = Engine.context; + const { KeyringController } = Engine.context; try { - shouldAutoSwitchSelectedAccount.current = !isMultiSelect; setIsLoading(true); const { addedAccountAddress } = await KeyringController.addNewAccount(); - PreferencesController.setSelectedAddress(addedAccountAddress); + const checksummedAddress = safeToChecksumAddress( + addedAccountAddress, + ) as string; + !isMultiSelect && setSelectedAddresses([checksummedAddress]); AnalyticsV2.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); } catch (e: any) { Logger.error(e, 'error while trying to add a new account'); @@ -166,18 +180,6 @@ const AccountConnect = (props: AccountConnectProps) => { /* eslint-disable-next-line */ }, []); - // This useEffect is used for auto selecting the newly created account post account creation. - useEffect(() => { - if (isLoading && prevSelectedAddress.current !== selectedWalletAddress) { - shouldAutoSwitchSelectedAccount.current && - setSelectedAddresses([selectedWalletAddress]); - prevSelectedAddress.current = selectedWalletAddress; - } - if (!prevSelectedAddress.current) { - prevSelectedAddress.current = selectedWalletAddress; - } - }, [selectedWalletAddress, isLoading, selectedAddresses]); - const renderSingleConnectScreen = useCallback(() => { const selectedAddress = selectedAddresses[0]; const selectedAccount = accounts.find( diff --git a/app/components/Views/AccountConnect/AccountConnect.types.ts b/app/components/Views/AccountConnect/AccountConnect.types.ts index 2607ae0db3e..9e98bffc0d8 100644 --- a/app/components/Views/AccountConnect/AccountConnect.types.ts +++ b/app/components/Views/AccountConnect/AccountConnect.types.ts @@ -16,7 +16,9 @@ export interface AccountConnectProps { */ route: { params: { - hostInfo: any; + hostInfo: { + metadata: { origin: string }; + }; }; }; } diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx index 93c46212576..a25051a7e86 100644 --- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx @@ -8,7 +8,7 @@ import SheetActions from '../../../../component-library/components-temp/SheetAct import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader'; import { strings } from '../../../../../locales/i18n'; import TagUrl from '../../../../component-library/components/Tags/TagUrl'; -import Text from '../../../../component-library/components/Text'; +import Text from '../../../../component-library/components/Texts/Text'; import { useStyles } from '../../../../component-library/hooks'; import ButtonPrimary, { ButtonPrimaryVariant, diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx index 3e5864075cd..9cbe42c2ffd 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx @@ -12,7 +12,7 @@ import Cell, { CellVariants, } from '../../../../component-library/components/Cells/Cell'; import TagUrl from '../../../../component-library/components/Tags/TagUrl'; -import Text from '../../../../component-library/components/Text'; +import Text from '../../../../component-library/components/Texts/Text'; import { useStyles } from '../../../../component-library/hooks'; import ButtonPrimary, { ButtonPrimaryVariant, diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index 1bbabbf78cd..719f9427bba 100644 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -34,6 +34,7 @@ import { IconName } from '../../../component-library/components/Icon'; import { getUrlObj } from '../../../util/browser'; import { getActiveTabUrl } from '../../../util/transactions'; import { strings } from '../../../../locales/i18n'; +import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; // Internal dependencies. import { @@ -50,6 +51,11 @@ const AccountPermissions = (props: AccountPermissionsProps) => { metadata: { origin: hostname }, }, } = props.route.params; + const accountAvatarType = useSelector((state: any) => + state.settings.useBlockieIcon + ? AvatarAccountType.Blockies + : AvatarAccountType.JazzIcon, + ); const origin: string = useSelector(getActiveTabUrl, isEqual); // TODO - Once we can pass metadata to permission system, pass origin instead of hostname into this component. // const hostname = useMemo(() => new URL(origin).hostname, [origin]); @@ -57,7 +63,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { () => (getUrlObj(origin) as URL).protocol === 'https:' ? IconName.LockFilled - : IconName.WarningFilled, + : IconName.LockSlashFilled, [origin], ); /** @@ -118,11 +124,10 @@ const AccountPermissions = (props: AccountPermissionsProps) => { const onCreateAccount = useCallback( async () => { - const { KeyringController, PreferencesController } = Engine.context; + const { KeyringController } = Engine.context; try { setIsLoading(true); - const { addedAccountAddress } = await KeyringController.addNewAccount(); - PreferencesController.setSelectedAddress(addedAccountAddress); + await KeyringController.addNewAccount(); AnalyticsV2.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); } catch (e: any) { Logger.error(e, 'Error while trying to add a new account.'); @@ -150,20 +155,24 @@ const AccountPermissions = (props: AccountPermissionsProps) => { let labelOptions: ToastOptions['labelOptions'] = []; if (connectedAccountLength > 1) { labelOptions = [ - { label: `${connectedAccountLength}`, isBold: true }, + { label: `${connectedAccountLength} `, isBold: true }, { label: `${strings('toast.accounts_connected')}\n`, }, + { label: `${activeAccountName} `, isBold: true }, + { label: strings('toast.now_active') }, + ]; + } else { + labelOptions = [ + { label: `${activeAccountName} `, isBold: true }, + { label: strings('toast.connected_and_active') }, ]; } - labelOptions = labelOptions.concat([ - { label: activeAccountName, isBold: true }, - { label: strings('toast.now_active') }, - ]); toastRef?.current?.showToast({ variant: ToastVariant.Account, labelOptions, accountAddress: newActiveAddress, + accountAvatarType, }); } catch (e: any) { Logger.error(e, 'Error while trying to connect to a dApp.'); @@ -179,6 +188,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { hostname, ensByAccountAddress, toastRef, + accountAvatarType, ]); const renderConnectedScreen = useCallback( @@ -194,6 +204,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { favicon={favicon} hostname={hostname} secureIcon={secureIcon} + accountAvatarType={accountAvatarType} /> ), [ @@ -207,6 +218,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { favicon, hostname, secureIcon, + accountAvatarType, ], ); @@ -252,6 +264,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { favicon={favicon} hostname={hostname} secureIcon={secureIcon} + accountAvatarType={accountAvatarType} /> ), [ @@ -264,6 +277,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { favicon, hostname, secureIcon, + accountAvatarType, ], ); diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx index 8c08f6c2a98..5fe8003e0d4 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -39,6 +39,7 @@ const AccountPermissionsConnected = ({ hostname, favicon, secureIcon, + accountAvatarType, }: AccountPermissionsConnectedProps) => { const dispatch = useDispatch(); const networkController = useSelector( @@ -78,12 +79,13 @@ const AccountPermissionsConnected = ({ variant: ToastVariant.Account, labelOptions: [ { - label: activeAccountName, + label: `${activeAccountName} `, isBold: true, }, { label: strings('toast.now_active') }, ], accountAddress: address, + accountAvatarType, }); }, [ @@ -93,6 +95,7 @@ const AccountPermissionsConnected = ({ ensByAccountAddress, hostname, toastRef, + accountAvatarType, ], ); diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.types.ts b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.types.ts index a401dece0a7..25011f5bb3f 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.types.ts +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.types.ts @@ -5,6 +5,7 @@ import { ImageSourcePropType } from 'react-native'; import { UseAccounts } from '../../../hooks/useAccounts'; import { AccountPermissionsScreens } from '../AccountPermissions.types'; import { IconName } from '../../../../component-library/components/Icon'; +import { AvatarAccountType } from '../../../../component-library/components/Avatars/AvatarAccount'; /** * AccountPermissionsConnected props. @@ -18,4 +19,5 @@ export interface AccountPermissionsConnectedProps extends UseAccounts { hostname: string; favicon: ImageSourcePropType; secureIcon: IconName; + accountAvatarType: AvatarAccountType; } diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx index 2b7bc27dec2..84f8d2d9d6c 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx @@ -7,7 +7,7 @@ import SheetActions from '../../../../component-library/components-temp/SheetAct import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader'; import { strings } from '../../../../../locales/i18n'; import TagUrl from '../../../../component-library/components/Tags/TagUrl'; -import Text from '../../../../component-library/components/Text'; +import Text from '../../../../component-library/components/Texts/Text'; import { useStyles } from '../../../../component-library/hooks'; import ButtonSecondary, { ButtonSecondaryVariant, @@ -40,6 +40,7 @@ const AccountPermissionsRevoke = ({ hostname, favicon, secureIcon, + accountAvatarType, }: AccountPermissionsRevokeProps) => { const Engine = UntypedEngine as any; const { styles } = useStyles(styleSheet, {}); @@ -108,7 +109,7 @@ const AccountPermissionsRevoke = ({ } else { const labelOptions: ToastOptions['labelOptions'] = [ { - label: name, + label: `${name} `, isBold: true, }, { label: strings('toast.revoked') }, @@ -123,7 +124,7 @@ const AccountPermissionsRevoke = ({ removePermittedAccount(hostname, address); labelOptions.push( { - label: `\n${newActiveAccountName}`, + label: `\n${newActiveAccountName} `, isBold: true, }, { label: strings('toast.now_active') }, @@ -132,6 +133,7 @@ const AccountPermissionsRevoke = ({ variant: ToastVariant.Account, labelOptions, accountAddress: nextActiveAddress, + accountAvatarType, }); } else { // Just disconnect diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.types.ts b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.types.ts index 670df381fa0..c06d4da6155 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.types.ts +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.types.ts @@ -2,9 +2,10 @@ import { ImageSourcePropType } from 'react-native'; // External dependencies. -import { IconName } from 'app/component-library/components/Icon'; +import { IconName } from '../../../../component-library/components/Icon'; import { UseAccounts } from '../../../hooks/useAccounts'; import { AccountPermissionsScreens } from '../AccountPermissions.types'; +import { AvatarAccountType } from '../../../../component-library/components/Avatars/AvatarAccount'; /** * AccountPermissionsRevoke props. @@ -17,4 +18,5 @@ export interface AccountPermissionsRevokeProps extends UseAccounts { hostname: string; favicon: ImageSourcePropType; secureIcon: IconName; + accountAvatarType: AvatarAccountType; } diff --git a/app/components/Views/AccountSelector/AccountSelector.types.ts b/app/components/Views/AccountSelector/AccountSelector.types.ts index a107ba5d844..87affc7d0a7 100644 --- a/app/components/Views/AccountSelector/AccountSelector.types.ts +++ b/app/components/Views/AccountSelector/AccountSelector.types.ts @@ -1,5 +1,5 @@ // External dependencies. -import { UseAccountsParams } from 'app/components/hooks/useAccounts'; +import { UseAccountsParams } from '../../../components/hooks/useAccounts'; /** * AccountSelectorProps props. diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js index e462e3b4961..c7a35420cd0 100644 --- a/app/components/Views/ActivityView/index.js +++ b/app/components/Views/ActivityView/index.js @@ -81,6 +81,6 @@ const ActivityView = () => { ); -} +}; export default ActivityView; diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js index f783c3407bc..53a1dbd552e 100644 --- a/app/components/Views/Asset/index.js +++ b/app/components/Views/Asset/index.js @@ -28,6 +28,7 @@ import { findBlockExplorerForRpc, isMainnetByChainId, } from '../../../util/networks'; +import Routes from '../../../constants/navigation/Routes'; const createStyles = (colors) => StyleSheet.create({ @@ -135,8 +136,12 @@ class Asset extends PureComponent { colors, shouldShowMoreOptionsInNavBar ? () => - navigation.navigate('AssetOptions', { - isNativeCurrency: isNativeToken, + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: 'AssetOptions', + params: { + isNativeCurrency: isNativeToken, + address: route.params?.address, + }, }) : undefined, true, diff --git a/app/components/Views/AssetDetails/index.tsx b/app/components/Views/AssetDetails/index.tsx index 13decd37de2..60a623f66de 100644 --- a/app/components/Views/AssetDetails/index.tsx +++ b/app/components/Views/AssetDetails/index.tsx @@ -30,6 +30,7 @@ import { import WarningMessage from '../SendFlow/WarningMessage'; import { useTheme } from '../../../util/theme'; import AnalyticsV2 from '../../../util/analyticsV2'; +import Routes from '../../../constants/navigation/Routes'; const createStyles = (colors: any) => StyleSheet.create({ @@ -165,33 +166,39 @@ const AssetDetails = (props: Props) => { const triggerHideToken = () => { const { TokensController, NetworkController } = Engine.context as any; - navigation.navigate('AssetHideConfirmation', { - onConfirm: () => { - navigation.navigate('WalletView'); - InteractionManager.runAfterInteractions(async () => { - try { - await TokensController.ignoreTokens([address]); - NotificationManager.showSimpleNotification({ - status: `simple_notification`, - duration: 5000, - title: strings('wallet.token_toast.token_hidden_title'), - description: strings('wallet.token_toast.token_hidden_desc', { - tokenSymbol: symbol, - }), - }); - AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.TOKENS_HIDDEN, { - location: 'token_details', - token_standard: 'ERC20', - asset_type: 'token', - tokens: [`${symbol} - ${address}`], - chain_id: getDecimalChainId( - NetworkController?.state?.provider?.chainId, - ), - }); - } catch (err) { - Logger.log(err, 'AssetDetails: Failed to hide token!'); - } - }); + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: 'AssetHideConfirmation', + params: { + onConfirm: () => { + navigation.navigate('WalletView'); + InteractionManager.runAfterInteractions(async () => { + try { + await TokensController.ignoreTokens([address]); + NotificationManager.showSimpleNotification({ + status: `simple_notification`, + duration: 5000, + title: strings('wallet.token_toast.token_hidden_title'), + description: strings('wallet.token_toast.token_hidden_desc', { + tokenSymbol: symbol, + }), + }); + AnalyticsV2.trackEvent( + AnalyticsV2.ANALYTICS_EVENTS.TOKENS_HIDDEN, + { + location: 'token_details', + token_standard: 'ERC20', + asset_type: 'token', + tokens: [`${symbol} - ${address}`], + chain_id: getDecimalChainId( + NetworkController?.state?.provider?.chainId, + ), + }, + ); + } catch (err) { + Logger.log(err, 'AssetDetails: Failed to hide token!'); + } + }); + }, }, }); }; diff --git a/app/components/Views/Browser/index.js b/app/components/Views/Browser/index.js index 969f5dc95fd..eefbaba4d14 100644 --- a/app/components/Views/Browser/index.js +++ b/app/components/Views/Browser/index.js @@ -1,5 +1,5 @@ -import React, { useContext, useEffect, useRef } from 'react'; -import { connect } from 'react-redux'; +import React, { useEffect, useRef, useContext } from 'react'; +import { connect, useSelector } from 'react-redux'; import { View, Dimensions } from 'react-native'; import PropTypes from 'prop-types'; import { @@ -17,8 +17,16 @@ import Device from '../../../util/device'; import BrowserTab from '../BrowserTab'; import AppConstants from '../../../core/AppConstants'; import { baseStyles } from '../../../styles/common'; -import { DrawerContext } from '../../Nav/Main/MainNavigator'; import { useTheme } from '../../../util/theme'; +import { getPermittedAccounts } from '../../../core/Permissions'; +import getAccountNameWithENS from '../../../util/accounts'; +import { useAccounts } from '../../../components/hooks/useAccounts'; +import { + ToastContext, + ToastVariant, +} from '../../../component-library/components/Toast'; +import { strings } from '../../../../locales/i18n'; +import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; const margin = 16; const THUMB_WIDTH = Dimensions.get('window').width / 2 - margin * 2; @@ -40,16 +48,20 @@ const Browser = (props) => { activeTab: activeTabId, tabs, } = props; - const { drawerRef } = useContext(DrawerContext); const previousTabs = useRef(null); const { colors } = useTheme(); + const { toastRef } = useContext(ToastContext); + const browserUrl = props.route?.params?.url; + const prevSite = useRef(browserUrl); + const { accounts, ensByAccountAddress } = useAccounts(); + const accountAvatarType = useSelector((state) => + state.settings.useBlockieIcon + ? AvatarAccountType.Blockies + : AvatarAccountType.JazzIcon, + ); useEffect( - () => { - navigation.setOptions( - getBrowserViewNavbarOptions(route, colors), - ); - }, + () => navigation.setOptions(getBrowserViewNavbarOptions(route, colors)), /* eslint-disable-next-line */ [navigation, route, colors], ); @@ -78,6 +90,45 @@ const Browser = (props) => { updateTabInfo(tab.url, tab.id); }; + const hasAccounts = useRef(Boolean(accounts.length)); + + useEffect(() => { + const checkIfActiveAccountChanged = async () => { + const hostname = new URL(browserUrl).hostname; + const permittedAccounts = await getPermittedAccounts(hostname); + const activeAccountAddress = permittedAccounts?.[0]; + if (activeAccountAddress) { + const accountName = getAccountNameWithENS({ + accountAddress: activeAccountAddress, + accounts, + ensByAccountAddress, + }); + // Show active account toast + toastRef?.current?.showToast({ + variant: ToastVariant.Account, + labelOptions: [ + { + label: `${accountName} `, + isBold: true, + }, + { label: strings('toast.now_active') }, + ], + accountAddress: activeAccountAddress, + accountAvatarType, + }); + } + }; + + // Handle when the Browser initially mounts and when url changes. + if (accounts.length && browserUrl) { + if (prevSite.current !== browserUrl || !hasAccounts.current) { + checkIfActiveAccountChanged(); + } + hasAccounts.current = true; + prevSite.current = browserUrl; + } + }, [browserUrl, accounts, ensByAccountAddress, accountAvatarType, toastRef]); + // componentDidMount useEffect( () => { diff --git a/app/components/Views/BrowserTab/__snapshots__/index.test.tsx.snap b/app/components/Views/BrowserTab/__snapshots__/index.test.tsx.snap index 2c2d3404c10..18589be6ab4 100644 --- a/app/components/Views/BrowserTab/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/BrowserTab/__snapshots__/index.test.tsx.snap @@ -1,107 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Browser should render correctly 1`] = ` - - - - - - - - - - - - + } +> + + `; diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index bbcbafc4836..4e83c976bf8 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -1,10 +1,4 @@ -import React, { - useState, - useRef, - useEffect, - useCallback, - useContext, -} from 'react'; +import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Text, StyleSheet, @@ -72,12 +66,6 @@ import { } from '../../../core/Permissions'; import Routes from '../../../constants/navigation/Routes'; import { isEqual } from 'lodash'; -import { - ToastContext, - ToastVariant, -} from '../../../component-library/components/Toast'; -import { useAccounts } from '../../hooks/useAccounts'; -import getAccountNameWithENS from '../../../util/accounts'; const { HOMEPAGE_URL, USER_AGENT, NOTIFICATION_NAMES } = AppConstants; const HOMEPAGE_HOST = new URL(HOMEPAGE_URL)?.hostname; @@ -243,8 +231,6 @@ export const BrowserTab = (props) => { const backgroundBridges = useRef([]); const fromHomepage = useRef(false); const wizardScrollAdjusted = useRef(false); - const { toastRef } = useContext(ToastContext); - const { accounts, ensByAccountAddress } = useAccounts(); const permittedAccountsList = useSelector((state) => { const permissionsControllerState = state.engine.backgroundState.PermissionController; @@ -659,30 +645,6 @@ export const BrowserTab = (props) => { * Handles state changes for when the url changes */ const changeUrl = async (siteInfo) => { - if (siteInfo.url !== url.current) { - const hostname = new URL(siteInfo.url).hostname; - const permittedAccounts = await getPermittedAccounts(hostname); - const activeAccountAddress = permittedAccounts?.[0]; - if (activeAccountAddress) { - const accountName = getAccountNameWithENS({ - accountAddress: activeAccountAddress, - accounts, - ensByAccountAddress, - }); - // Show active account toast - toastRef?.current?.showToast({ - variant: ToastVariant.Account, - labelOptions: [ - { - label: accountName, - isBold: true, - }, - { label: strings('toast.now_active') }, - ], - accountAddress: activeAccountAddress, - }); - } - } url.current = siteInfo.url; title.current = siteInfo.title; if (siteInfo.icon) icon.current = siteInfo.icon; @@ -971,17 +933,14 @@ export const BrowserTab = (props) => { }; const sendActiveAccount = useCallback(async () => { - const hostname = new URL(url.current).hostname; - const accounts = await getPermittedAccounts(hostname); - notifyAllConnections({ method: NOTIFICATION_NAMES.accountsChanged, - params: accounts, + params: permittedAccountsList, }); if (isTabActive) { props.navigation.setParams({ - connectedAccounts: accounts, + connectedAccounts: permittedAccountsList, }); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/app/components/Views/BrowserTab/index.test.tsx b/app/components/Views/BrowserTab/index.test.tsx index 70e88faaadd..bb2928c72b7 100644 --- a/app/components/Views/BrowserTab/index.test.tsx +++ b/app/components/Views/BrowserTab/index.test.tsx @@ -1,12 +1,40 @@ -jest.useFakeTimers(); - import React from 'react'; import { shallow } from 'enzyme'; import { BrowserTab } from './'; +import { Provider } from 'react-redux'; +import configureMockStore from 'redux-mock-store'; + +jest.useFakeTimers(); + +const initialState = { + browser: { activeTab: '' }, + engine: { + backgroundState: { + PermissionController: { + subjects: {}, + }, + }, + }, + transaction: { + selectedAsset: '', + }, +}; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn().mockImplementation(() => initialState), +})); + +const mockStore = configureMockStore(); +const store = mockStore(initialState); describe('Browser', () => { it('should render correctly', () => { - const wrapper = shallow(); + const wrapper = shallow( + + + , + ); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/app/components/Views/Settings/NetworksSettings/index.js b/app/components/Views/Settings/NetworksSettings/index.js index 8da3957dcde..3529d7ff0a6 100644 --- a/app/components/Views/Settings/NetworksSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/index.js @@ -23,6 +23,8 @@ import { MAINNET, RPC } from '../../../../constants/network'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; import { ThemeContext, mockTheme } from '../../../../util/theme'; import ImageIcons from '../../../UI/ImageIcon'; +import AvatarNetwork from '../../../../component-library/components/Avatars/AvatarNetwork'; +import { AvatarBaseSize } from '../../../../component-library/components/Avatars/AvatarBase'; const createStyles = (colors) => StyleSheet.create({ @@ -207,7 +209,11 @@ class NetworksSettings extends PureComponent { (image ? ( ) : ( - + ))} {!isCustomRPC && ( diff --git a/app/components/hooks/useAccounts/useAccounts.ts b/app/components/hooks/useAccounts/useAccounts.ts index 7be14bdbbc3..4d1734a9e59 100644 --- a/app/components/hooks/useAccounts/useAccounts.ts +++ b/app/components/hooks/useAccounts/useAccounts.ts @@ -141,11 +141,12 @@ const useAccounts = ({ const balanceWeiHex = accountInfoByAddress?.[checksummedAddress]?.balance || 0x0; const balanceETH = renderFromWei(balanceWeiHex); // Gives ETH - const balanceFiat = weiToFiat( - hexToBN(balanceWeiHex) as any, - conversionRate, - currentCurrency, - ); + const balanceFiat = + weiToFiat( + hexToBN(balanceWeiHex) as any, + conversionRate, + currentCurrency, + ) || ''; const balanceTicker = getTicker(ticker); const balanceLabel = `${balanceFiat}\n${balanceETH} ${balanceTicker}`; const balanceError = checkBalanceError?.(balanceWeiHex); diff --git a/app/core/Engine.js b/app/core/Engine.js index 452d8b59241..6626c91902b 100644 --- a/app/core/Engine.js +++ b/app/core/Engine.js @@ -660,8 +660,12 @@ class Engine { CollectiblesController, TokenBalancesController, TokenRatesController, + PermissionController, } = this.context; + // Remove all permissions. + PermissionController?.clearState?.(); + //Clear assets info TokensController.update({ allTokens: {}, diff --git a/app/core/Permissions/index.ts b/app/core/Permissions/index.ts index 3cdee32acdb..e19ea904a27 100644 --- a/app/core/Permissions/index.ts +++ b/app/core/Permissions/index.ts @@ -171,6 +171,13 @@ export const removeAccountFromPermissions = async (address: string) => { } }; +/** + * Get permitted accounts for the given the host. + * + * @param hostname - Subject to check if permissions exists. Ex: A Dapp is a subject. + * @returns An array containing permitted accounts for the specified host. + * Currently, this will only return the active account since we only return the first item from the caveat decorator in permissions. + */ export const getPermittedAccounts = async (hostname: string) => { try { const accountsWithLastUsed = diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index fa2ec4d6a93..73462243ad0 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -56,7 +56,6 @@ interface RPCMethodsMiddleParameters { export const checkActiveAccountAndChainId = async ({ address, chainId, - // activeAccounts, isWalletConnect, hostname, }: any) => { From 1a3e42d44fceb9e9b365b16cc65a3c559240e692 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Mon, 3 Oct 2022 15:47:29 -0700 Subject: [PATCH 14/91] Fix non-components tests --- app/core/Permissions/specifications.js | 3 +- app/core/Permissions/specifications.test.js | 32 ++++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index a049906a425..d53475d46d7 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -148,7 +148,8 @@ function validateCaveatAccounts(accounts, getIdentities) { } const identities = getIdentities(); - accounts.forEach(({ address }) => { + accounts.forEach((account) => { + const address = account?.address; if (!address || typeof address !== 'string') { throw new Error( `${PermissionKeys.eth_accounts} error: Expected an array of objects that contains an Ethereum addresses. Received: "${address}".`, diff --git a/app/core/Permissions/specifications.test.js b/app/core/Permissions/specifications.test.js index 807db77a519..35ad45c415d 100644 --- a/app/core/Permissions/specifications.test.js +++ b/app/core/Permissions/specifications.test.js @@ -5,9 +5,6 @@ import { unrestrictedMethods, } from './specifications'; -// Note: This causes Date.now() to return the number 1. -jest.useFakeTimers('modern').setSystemTime(1); - describe('PermissionController specifications', () => { describe('caveat specifications', () => { it('getCaveatSpecifications returns the expected specifications object', () => { @@ -22,6 +19,10 @@ describe('PermissionController specifications', () => { describe('decorator', () => { it('returns the first array member included in the caveat value', async () => { const getIdentities = jest.fn(); + const caveatValues = [ + { address: '0x1', lastUsed: '1' }, + { address: '0x2', lastUsed: '2' }, + ]; const { decorator } = getCaveatSpecifications({ getIdentities })[ CaveatTypes.restrictReturnedAccounts ]; @@ -29,14 +30,16 @@ describe('PermissionController specifications', () => { const method = async () => ['0x1', '0x2', '0x3']; const caveat = { type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1', '0x2'], + value: caveatValues, }; const decorated = decorator(method, caveat); - expect(await decorated()).toStrictEqual(['0x1']); + + expect(await decorated()).toStrictEqual([caveatValues[0]]); }); it('returns an empty array if no array members are included in the caveat value', async () => { const getIdentities = jest.fn(); + const caveatValues = [{ address: '0x5', lastUsed: '1' }]; const { decorator } = getCaveatSpecifications({ getIdentities })[ CaveatTypes.restrictReturnedAccounts ]; @@ -44,7 +47,7 @@ describe('PermissionController specifications', () => { const method = async () => ['0x1', '0x2', '0x3']; const caveat = { type: CaveatTypes.restrictReturnedAccounts, - value: ['0x5'], + value: caveatValues, }; const decorated = decorator(method, caveat); expect(await decorated()).toStrictEqual([]); @@ -52,6 +55,10 @@ describe('PermissionController specifications', () => { it('returns an empty array if the method result is an empty array', async () => { const getIdentities = jest.fn(); + const caveatValues = [ + { address: '0x1', lastUsed: '1' }, + { address: '0x2', lastUsed: '2' }, + ]; const { decorator } = getCaveatSpecifications({ getIdentities })[ CaveatTypes.restrictReturnedAccounts ]; @@ -59,7 +66,7 @@ describe('PermissionController specifications', () => { const method = async () => []; const caveat = { type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1', '0x2'], + value: caveatValues, }; const decorated = decorator(method, caveat); expect(await decorated()).toStrictEqual([]); @@ -88,7 +95,7 @@ describe('PermissionController specifications', () => { [[{}], [[]], [null], ['']].forEach((invalidValue) => { expect(() => validator({ value: invalidValue })).toThrow( - /Expected an array of Ethereum addresses. Received:/u, + /Expected an array of objects that contains an Ethereum addresses. Received:/u, ); }); }); @@ -98,12 +105,17 @@ describe('PermissionController specifications', () => { '0x1': true, '0x3': true, })); + const caveatValues = [ + { address: '0x1', lastUsed: '1' }, + { address: '0x2', lastUsed: '2' }, + { address: '0x3', lastUsed: '3' }, + ]; const { validator } = getCaveatSpecifications({ getIdentities })[ CaveatTypes.restrictReturnedAccounts ]; - expect(() => validator({ value: ['0x1', '0x2', '0x3'] })).toThrow( + expect(() => validator({ value: caveatValues })).toThrow( /Received unrecognized address:/u, ); }); @@ -140,7 +152,7 @@ describe('PermissionController specifications', () => { value: ['0x1'], }, ], - date: 1, + date: expect.any(Number), id: expect.any(String), invoker: 'foo.bar', parentCapability: 'eth_accounts', From 4d88950c6836463328ac8a4c46f26687b0d62b71 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 4 Oct 2022 01:52:38 -0700 Subject: [PATCH 15/91] Fix WalletConnect and transacting logic --- app/components/UI/AccountInfoCard/index.js | 9 ++++++-- app/core/RPCMethods/RPCMethodMiddleware.ts | 24 +++++++++++++--------- app/core/WalletConnect.js | 8 +++++--- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/components/UI/AccountInfoCard/index.js b/app/components/UI/AccountInfoCard/index.js index 2e00e62e274..c5a0974414a 100644 --- a/app/components/UI/AccountInfoCard/index.js +++ b/app/components/UI/AccountInfoCard/index.js @@ -6,7 +6,11 @@ import { renderFromWei, weiToFiat, hexToBN } from '../../../util/number'; import Identicon from '../Identicon'; import { strings } from '../../../../locales/i18n'; import { connect } from 'react-redux'; -import { renderAccountName, renderShortAddress } from '../../../util/address'; +import { + renderAccountName, + renderShortAddress, + safeToChecksumAddress, +} from '../../../util/address'; import { getTicker } from '../../../util/transactions'; import Engine from '../../../core/Engine'; import { QR_HARDWARE_WALLET_DEVICE } from '../../../constants/keyringTypes'; @@ -141,8 +145,9 @@ class AccountInfoCard extends PureComponent { operation, ticker, showFiatBalance = true, - fromAddress, + fromAddress: rawFromAddress, } = this.props; + const fromAddress = safeToChecksumAddress(rawFromAddress); const { isHardwareKeyring } = this.state; const colors = this.context.colors || mockTheme.colors; const styles = createStyles(colors); diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index 73462243ad0..54ca51ca3bd 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -12,7 +12,7 @@ import Networks, { import { polyfillGasPrice } from './utils'; import ImportedEngine from '../Engine'; import { strings } from '../../../locales/i18n'; -import { resemblesAddress } from '../../util/address'; +import { resemblesAddress, safeToChecksumAddress } from '../../util/address'; import { store } from '../../store'; import { removeBookmark } from '../../actions/bookmarks'; import setOnboardingWizardStep from '../../actions/wizard'; @@ -59,24 +59,22 @@ export const checkActiveAccountAndChainId = async ({ isWalletConnect, hostname, }: any) => { - let isInvalidAccount = true; + let isInvalidAccount = false; if (address) { + const formattedAddress = safeToChecksumAddress(address); if (isWalletConnect) { const selectedAddress = Engine.context.PreferencesController.state.selectedAddress; - if (address.toLowerCase() !== selectedAddress.toLowerCase()) { - isInvalidAccount = false; + if (formattedAddress !== safeToChecksumAddress(selectedAddress)) { + isInvalidAccount = true; } } else { // For Browser use permissions const accounts = await getPermittedAccounts(hostname); - const normalizedAccounts = accounts.map((_address: string) => - _address.toLowerCase(), - ); - const normalizedAddress = address.toLowerCase(); + const normalizedAccounts = accounts.map(safeToChecksumAddress); - if (!normalizedAccounts.includes(normalizedAddress)) { - isInvalidAccount = false; + if (!normalizedAccounts.includes(formattedAddress)) { + isInvalidAccount = true; } } if (isInvalidAccount) { @@ -270,6 +268,7 @@ export const getRpcMethodMiddleware = ({ eth_sendTransaction: async () => { checkTabActive(); await checkActiveAccountAndChainId({ + hostname, address: req.params[0].from, chainId: req.params[0].chainId, isWalletConnect, @@ -295,6 +294,7 @@ export const getRpcMethodMiddleware = ({ if (req.params[1].length === 66 || req.params[1].length === 67) { await checkActiveAccountAndChainId({ + hostname, address: req.params[0].from, isWalletConnect, }); @@ -337,6 +337,7 @@ export const getRpcMethodMiddleware = ({ checkTabActive(); await checkActiveAccountAndChainId({ + hostname, address: params.from, isWalletConnect, }); @@ -362,6 +363,7 @@ export const getRpcMethodMiddleware = ({ checkTabActive(); await checkActiveAccountAndChainId({ + hostname, address: req.params[1], isWalletConnect, }); @@ -395,6 +397,7 @@ export const getRpcMethodMiddleware = ({ checkTabActive(); await checkActiveAccountAndChainId({ + hostname, address: req.params[0], chainId, isWalletConnect, @@ -429,6 +432,7 @@ export const getRpcMethodMiddleware = ({ checkTabActive(); await checkActiveAccountAndChainId({ + hostname, address: req.params[0], chainId, isWalletConnect, diff --git a/app/core/WalletConnect.js b/app/core/WalletConnect.js index b1b55e3af7e..f40aa22cbbe 100644 --- a/app/core/WalletConnect.js +++ b/app/core/WalletConnect.js @@ -148,8 +148,8 @@ class WalletConnect { if (payload.method) { const payloadUrl = this.walletConnector.session.peerMeta.url; - - if (new URL(payloadUrl).hostname === this.backgroundBridge.url) { + const payloadHostname = new URL(payloadUrl).hostname; + if (payloadHostname === this.backgroundBridge.hostname) { if (METHODS_TO_REDIRECT[payload.method]) { this.requestsToRedirect[payload.id] = true; } @@ -168,7 +168,9 @@ class WalletConnect { checkActiveAccountAndChainId({ address: payload.params[0].from, chainId: payload.params[0].chainId, + isWalletConnect: true, activeAccounts: [selectedAddress], + hostname: payloadHostname, }); const hash = await ( @@ -299,7 +301,7 @@ class WalletConnect { this.backgroundBridge = new BackgroundBridge({ webview: null, - url: this.hostname, + url: this.url.current, isWalletConnect: true, wcWalletConnector: this.walletConnector, wcRequestActions: { From 0d8c597c12f6e2adfd400eb87a9bd638bcfb84fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <1649425+jpcloureiro@users.noreply.github.com> Date: Tue, 4 Oct 2022 22:35:37 +0100 Subject: [PATCH 16/91] [PS] code cleanup (#5084) * (Navigation): update route names * (BrowserUrlBar): use getURLProtocol to check SSL * (BrowserUrlBar): test margins * (Browser): fix Android url bar onPress --- app/components/Nav/Main/MainNavigator.js | 26 +++++++++---------- .../UI/ApproveTransactionReview/index.js | 2 +- .../UI/BrowserUrlBar/BrowserUrlBar.styles.ts | 8 ++---- .../UI/BrowserUrlBar/BrowserUrlBar.tsx | 7 ++++- app/components/UI/DrawerView/index.js | 2 +- app/components/UI/Navbar/index.js | 4 +-- .../UI/OnboardingWizard/Step5/index.js | 4 +-- .../TransactionReviewInformation/index.js | 2 +- .../Views/BrowserUrlModal/BrowserUrlModal.tsx | 2 +- .../Views/SendFlow/Confirm/index.js | 2 +- .../NetworkSettings/emptyList.tsx | 3 ++- app/constants/navigation/Routes.ts | 12 ++++++--- app/core/DeeplinkManager.js | 4 +-- 13 files changed, 43 insertions(+), 35 deletions(-) diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index fe14a3b326e..73d84d6bf1d 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -161,9 +161,15 @@ const WalletTabStackFlow = () => ( ); +const WalletTabModalFlow = () => ( + + + +); + const TransactionsHome = () => ( - + ( ); -const WalletTabModalFlow = () => ( - - - -); - const BrowserFlow = () => ( - + @@ -212,7 +212,7 @@ const HomeTabs = () => { ( { )} > diff --git a/app/components/UI/ApproveTransactionReview/index.js b/app/components/UI/ApproveTransactionReview/index.js index 1c76a81b7cd..d9665432bbc 100644 --- a/app/components/UI/ApproveTransactionReview/index.js +++ b/app/components/UI/ApproveTransactionReview/index.js @@ -927,7 +927,7 @@ class ApproveTransactionReview extends PureComponent { goToFaucet = () => { InteractionManager.runAfterInteractions(() => { this.onCancelPress(); - this.props.navigation.navigate(Routes.BROWSER_VIEW, { + this.props.navigation.navigate(Routes.BROWSER.VIEW, { newTabUrl: AppConstants.URLS.MM_FAUCET, timestamp: Date.now(), }); diff --git a/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts b/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts index c85791b5485..0658da7b6b3 100644 --- a/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts +++ b/app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts @@ -6,15 +6,11 @@ const styleSheet = (params: { theme: Theme }) => StyleSheet.create({ main: { flexDirection: 'row', - marginLeft: 23, - marginRight: 23, alignItems: 'center', }, text: { - flex: 1, - marginLeft: 6, - color: params.theme.colors.icon.alternative, - width: 260, + marginLeft: 8, + color: params.theme.colors.text.alternative, }, }); diff --git a/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx b/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx index ac7cec58be5..fe2808d3130 100644 --- a/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx +++ b/app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx @@ -1,6 +1,9 @@ import React from 'react'; import { TouchableOpacity, View } from 'react-native'; + import { useStyles } from '../../../component-library/hooks'; +import { getURLProtocol } from '../../../util/general'; +import { PROTOCOLS } from '../../../constants/deeplinks'; import { isGatewayUrl } from '../../../lib/ens-ipfs/resolver'; import AppConstants from '../../../core/AppConstants'; import Icon, { @@ -8,6 +11,7 @@ import Icon, { IconSize, } from '../../../component-library/components/Icon'; import Text from '../../../component-library/components/Texts/Text'; + import { BrowserUrlBarProps } from './BrowserUrlBar.types'; import stylesheet from './BrowserUrlBar.styles'; @@ -28,7 +32,8 @@ const BrowserUrlBar = ({ url, route, onPress }: BrowserUrlBarProps) => { return urlObj.host.toLowerCase().replace(/^www\./, ''); }; - const isHttps = url && url.toLowerCase().substr(0, 6) === 'https:'; + const contentProtocol = getURLProtocol(url); + const isHttps = contentProtocol === PROTOCOLS.HTTPS; const secureConnectionIcon = isHttps ? IconName.LockFilled diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js index be5526600bd..a33bd47ad19 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -703,7 +703,7 @@ class DrawerView extends PureComponent { }; goToBrowser = () => { - this.props.navigation.navigate(Routes.BROWSER_TAB_HOME); + this.props.navigation.navigate(Routes.BROWSER.HOME); this.hideDrawer(); // Q: duplicated analytic event? this.trackOpenBrowserEvent(); diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index ecefcb3897b..df3b8c96342 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -583,10 +583,10 @@ export function getBrowserViewNavbarOptions(route, themeColors) { return { gestureEnabled: false, - headerLeft: () => ( + headerTitleAlign: 'left', + headerTitle: () => ( ), - headerTitle: null, headerRight: () => ( { setOnboardingWizardStep && setOnboardingWizardStep(6); drawerRef?.current?.dismissDrawer?.(); navigation && - navigation.navigate(Routes.BROWSER_TAB_HOME, { - screen: Routes.BROWSER_VIEW, + navigation.navigate(Routes.BROWSER.HOME, { + screen: Routes.BROWSER.VIEW, }); AnalyticsV2.trackEvent( AnalyticsV2.ANALYTICS_EVENTS.ONBOARDING_TOUR_STEP_COMPLETED, diff --git a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js index 2fa39b31bb7..fb849541988 100644 --- a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js +++ b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js @@ -511,7 +511,7 @@ class TransactionReviewInformation extends PureComponent { goToFaucet = () => { InteractionManager.runAfterInteractions(() => { this.onCancelPress(); - this.props.navigation.navigate(Routes.BROWSER_VIEW, { + this.props.navigation.navigate(Routes.BROWSER.VIEW, { newTabUrl: AppConstants.URLS.MM_FAUCET, timestamp: Date.now(), }); diff --git a/app/components/Views/BrowserUrlModal/BrowserUrlModal.tsx b/app/components/Views/BrowserUrlModal/BrowserUrlModal.tsx index 1c7e8eb3f93..3abbd18b90b 100644 --- a/app/components/Views/BrowserUrlModal/BrowserUrlModal.tsx +++ b/app/components/Views/BrowserUrlModal/BrowserUrlModal.tsx @@ -26,7 +26,7 @@ export interface BrowserUrlParams { } export const createBrowserUrlModalNavDetails = - createNavigationDetails(Routes.BROWSER_URL_MODAL); + createNavigationDetails(Routes.BROWSER.URL_MODAL); const BrowserUrlModal = () => { const { onUrlInputSubmit, url } = useParams(); diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js index 2f6961f787d..113b7ed7165 100644 --- a/app/components/Views/SendFlow/Confirm/index.js +++ b/app/components/Views/SendFlow/Confirm/index.js @@ -1300,7 +1300,7 @@ class Confirm extends PureComponent { goToFaucet = () => { InteractionManager.runAfterInteractions(() => { - this.props.navigation.navigate(Routes.BROWSER_VIEW, { + this.props.navigation.navigate(Routes.BROWSER.VIEW, { newTabUrl: AppConstants.URLS.MM_FAUCET, timestamp: Date.now(), }); diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/emptyList.tsx b/app/components/Views/Settings/NetworksSettings/NetworkSettings/emptyList.tsx index aae6859a5a9..fe399b35213 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/emptyList.tsx +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/emptyList.tsx @@ -5,6 +5,7 @@ import { strings } from '../../../../../../locales/i18n'; import Alert, { AlertType } from '../../../../Base/Alert'; import { useTheme } from '../../../../../util/theme'; import { CHAINLIST_URL } from '../../../../../constants/urls'; +import Routes from '../../../../../constants/navigation/Routes'; const createStyles = (colors: any) => StyleSheet.create({ @@ -28,7 +29,7 @@ const EmptyPopularList = ({ goToCustomNetwork }: Props) => { const goToBrowserTab = () => { navigation.navigate('BrowserTabHome', { - screen: 'BrowserView', + screen: Routes.BROWSER.VIEW, params: { newTabUrl: CHAINLIST_URL, timestamp: Date.now(), diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index 2097d039419..6f40cbabd24 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -1,7 +1,4 @@ const Routes = { - BROWSER_TAB_HOME: 'BrowserTabHome', - BROWSER_URL_MODAL: 'BrowserUrlModal', - BROWSER_VIEW: 'BrowserView', FIAT_ON_RAMP_AGGREGATOR: { ID: 'FiatOnRampAggregator', GET_STARTED: 'GetStarted', @@ -15,6 +12,7 @@ const Routes = { ORDER_DETAILS: 'OrderDetails', }, QR_SCANNER: 'QRScanner', + TRANSACTIONS_VIEW: 'TransactionsView', MODAL: { DELETE_WALLET: 'DeleteWalletModal', ROOT_MODAL_FLOW: 'RootModalFlow', @@ -40,6 +38,14 @@ const Routes = { ACCOUNT_CONNECT: 'AccountConnect', ACCOUNT_PERMISSIONS: 'AccountPermissions', }, + BROWSER: { + HOME: 'BrowserTabHome', + URL_MODAL: 'BrowserUrlModal', + VIEW: 'BrowserView', + }, + WALLET: { + HOME: 'WalletTabHome', + }, }; export default Routes; diff --git a/app/core/DeeplinkManager.js b/app/core/DeeplinkManager.js index 4f0f57d7add..bfd0a25853a 100644 --- a/app/core/DeeplinkManager.js +++ b/app/core/DeeplinkManager.js @@ -159,8 +159,8 @@ class DeeplinkManager { if (callback) { callback(url); } else { - this.navigation.navigate(Routes.BROWSER_TAB_HOME, { - screen: Routes.BROWSER_VIEW, + this.navigation.navigate(Routes.BROWSER.HOME, { + screen: Routes.BROWSER.VIEW, params: { newTabUrl: url, timestamp: Date.now(), From 410502ef720221e87ba1677b6aa162fe810ab2ce Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 5 Oct 2022 15:17:05 -0700 Subject: [PATCH 17/91] Fix snapshot --- .../Amount/__snapshots__/index.test.tsx.snap | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/components/Views/SendFlow/Amount/__snapshots__/index.test.tsx.snap b/app/components/Views/SendFlow/Amount/__snapshots__/index.test.tsx.snap index a64c56ba609..f04e96973ef 100644 --- a/app/components/Views/SendFlow/Amount/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/SendFlow/Amount/__snapshots__/index.test.tsx.snap @@ -807,7 +807,7 @@ exports[`Amount should convert ERC-20 token value to USD 1`] = ` Object { "address": "", "isETH": true, - "logo": "../images/eth-logo.png", + "logo": "../images/eth-logo-new.png", "name": "Ether", "symbol": "ETH", }, @@ -1803,7 +1803,7 @@ exports[`Amount should convert ETH to USD 1`] = ` Object { "address": "", "isETH": true, - "logo": "../images/eth-logo.png", + "logo": "../images/eth-logo-new.png", "name": "Ether", "symbol": "ETH", }, @@ -2816,7 +2816,7 @@ exports[`Amount should convert USD to ERC-20 token value 1`] = ` Object { "address": "", "isETH": true, - "logo": "../images/eth-logo.png", + "logo": "../images/eth-logo-new.png", "name": "Ether", "symbol": "ETH", }, @@ -3829,7 +3829,7 @@ exports[`Amount should convert USD to ETH 1`] = ` Object { "address": "", "isETH": true, - "logo": "../images/eth-logo.png", + "logo": "../images/eth-logo-new.png", "name": "Ether", "symbol": "ETH", }, @@ -4824,7 +4824,7 @@ exports[`Amount should display correct balance 1`] = ` Object { "address": "", "isETH": true, - "logo": "../images/eth-logo.png", + "logo": "../images/eth-logo-new.png", "name": "Ether", "symbol": "ETH", }, @@ -5815,7 +5815,7 @@ exports[`Amount should proceed if balance is sufficient while on Native primary Object { "address": "", "isETH": true, - "logo": "../images/eth-logo.png", + "logo": "../images/eth-logo-new.png", "name": "Ether", "symbol": "ETH", }, @@ -6731,7 +6731,7 @@ exports[`Amount should render correctly 1`] = ` Object { "address": "", "isETH": true, - "logo": "../images/eth-logo.png", + "logo": "../images/eth-logo-new.png", "name": "Ether", "symbol": "ETH", }, @@ -7791,7 +7791,7 @@ exports[`Amount should show an error message if balance is insufficient 1`] = ` Object { "address": "", "isETH": true, - "logo": "../images/eth-logo.png", + "logo": "../images/eth-logo-new.png", "name": "Ether", "symbol": "ETH", }, From 1d33480e51634ab64d2d6e1bb6c182c1ae8e71ce Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 5 Oct 2022 13:16:43 -0700 Subject: [PATCH 18/91] Update onboarding wizard --- app/components/Nav/Main/index.js | 9 +- .../UI/OnboardingWizard/Coachmark/index.js | 8 ++ .../UI/OnboardingWizard/Step4/index.js | 17 +--- .../UI/OnboardingWizard/Step5/index.js | 87 +++++++------------ .../UI/OnboardingWizard/Step6/index.js | 15 ++-- app/components/UI/OnboardingWizard/index.js | 80 ++--------------- locales/languages/en.json | 8 +- 7 files changed, 70 insertions(+), 154 deletions(-) diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 0502d1c6f17..28594fba747 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -313,10 +313,12 @@ const Main = (props) => { }; const renderDeprecatedNetworkAlert = (network, backUpSeedphraseVisible) => { + const { wizardStep } = props; const { type } = network.provider; if ( (type === ROPSTEN || type === RINKEBY || type === KOVAN) && - showDeprecatedAlert + showDeprecatedAlert && + !wizardStep ) { return ( ({ @@ -428,6 +434,7 @@ const mapStateToProps = (state) => ({ providerType: state.engine.backgroundState.NetworkController.provider.type, network: state.engine.backgroundState.NetworkController, backUpSeedphraseVisible: state.user.backUpSeedphraseVisible, + wizardStep: state.wizard.step, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/app/components/UI/OnboardingWizard/Coachmark/index.js b/app/components/UI/OnboardingWizard/Coachmark/index.js index 1d38b95974b..987ab8eb34f 100644 --- a/app/components/UI/OnboardingWizard/Coachmark/index.js +++ b/app/components/UI/OnboardingWizard/Coachmark/index.js @@ -99,6 +99,12 @@ const createStyles = (colors) => alignItems: 'flex-start', marginLeft: 30, }, + bottomRight: { + marginBottom: 10, + top: -2, + alignItems: 'flex-end', + marginRight: 30, + }, circle: { width: 6, height: 6, @@ -166,6 +172,7 @@ export default class Coachmark extends PureComponent { false, 'bottomCenter', 'bottomLeft', + 'bottomRight', ]), }; @@ -244,6 +251,7 @@ export default class Coachmark extends PureComponent { const positions = { bottomCenter: styles.bottomCenter, bottomLeft: styles.bottomLeft, + bottomRight: styles.bottomRight, [undefined]: styles.bottomCenter, }; return positions[bottomIndicatorPosition]; diff --git a/app/components/UI/OnboardingWizard/Step4/index.js b/app/components/UI/OnboardingWizard/Step4/index.js index 49a8770d621..9f1b211db17 100644 --- a/app/components/UI/OnboardingWizard/Step4/index.js +++ b/app/components/UI/OnboardingWizard/Step4/index.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { View, Text, StyleSheet, TouchableWithoutFeedback } from 'react-native'; @@ -6,13 +6,9 @@ import Coachmark from '../Coachmark'; import setOnboardingWizardStep from '../../../../actions/wizard'; import { strings } from '../../../../../locales/i18n'; import onboardingStyles from './../styles'; -import { - fontStyles, - colors as importedColors, -} from '../../../../styles/common'; +import { colors as importedColors } from '../../../../styles/common'; import AnalyticsV2 from '../../../../util/analyticsV2'; import { ONBOARDING_WIZARD_STEP_DESCRIPTION } from '../../../../util/analytics'; -import { DrawerContext } from '../../../../components/Nav/Main/MainNavigator'; import { useTheme } from '../../../../util/theme'; const styles = StyleSheet.create({ @@ -38,7 +34,6 @@ const styles = StyleSheet.create({ const Step4 = (props) => { const { coachmarkRef, setOnboardingWizardStep } = props; const [viewTop, setViewTop] = useState(0); - const { drawerRef } = useContext(DrawerContext); const { colors } = useTheme(); const dynamicOnboardingStyles = onboardingStyles(colors); @@ -65,7 +60,6 @@ const Step4 = (props) => { * Dispatches 'setOnboardingWizardStep' with next step */ const onNext = () => { - drawerRef?.current?.showDrawer?.(); setOnboardingWizardStep && setOnboardingWizardStep(5); AnalyticsV2.trackEvent( AnalyticsV2.ANALYTICS_EVENTS.ONBOARDING_TOUR_STEP_COMPLETED, @@ -96,13 +90,10 @@ const Step4 = (props) => { const content = () => ( - - {strings('onboarding_wizard.step4.content1')}{' '} - - {strings('onboarding_wizard.step4.content2')} + {strings('onboarding_wizard.step4.content1')} - {strings('onboarding_wizard.step4.content3')} + {strings('onboarding_wizard.step4.content2')} ); diff --git a/app/components/UI/OnboardingWizard/Step5/index.js b/app/components/UI/OnboardingWizard/Step5/index.js index a80dcf0d682..117bcd316d1 100644 --- a/app/components/UI/OnboardingWizard/Step5/index.js +++ b/app/components/UI/OnboardingWizard/Step5/index.js @@ -1,21 +1,23 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { View, Text, StyleSheet, Dimensions } from 'react-native'; +import { + View, + Text, + StyleSheet, + Dimensions, + TouchableWithoutFeedback, +} from 'react-native'; import { colors as importedColors } from '../../../../styles/common'; import Coachmark from '../Coachmark'; import setOnboardingWizardStep from '../../../../actions/wizard'; import { strings } from '../../../../../locales/i18n'; import onboardingStyles from './../styles'; -import Device from '../../../../util/device'; import AnalyticsV2 from '../../../../util/analyticsV2'; import { ONBOARDING_WIZARD_STEP_DESCRIPTION } from '../../../../util/analytics'; -import { DrawerContext } from '../../../../components/Nav/Main/MainNavigator'; import { useTheme } from '../../../../util/theme'; import Routes from '../../../../constants/navigation/Routes'; -const INDICATOR_HEIGHT = 10; -const DRAWER_WIDTH = 315; const WIDTH = Dimensions.get('window').width; const styles = StyleSheet.create({ main: { @@ -23,54 +25,37 @@ const styles = StyleSheet.create({ backgroundColor: importedColors.transparent, }, some: { - marginLeft: 24, - marginRight: WIDTH - DRAWER_WIDTH + 24, + marginHorizontal: 40, }, coachmarkContainer: { flex: 1, position: 'absolute', left: 0, right: 0, + bottom: 100, + }, + dummyBrowserButton: { + height: 82, + position: 'absolute', + bottom: 0, + right: 0, + width: WIDTH / 2, + }, + fill: { + flex: 1, }, }); const Step5 = (props) => { - const { navigation, setOnboardingWizardStep, coachmarkRef } = props; - const [coachmarkTop, setCoachmarkTop] = useState(0); - const [coachmarkBottom, setCoachmarkBottom] = useState(0); - const { drawerRef } = useContext(DrawerContext); + const { navigation, setOnboardingWizardStep } = props; const { colors } = useTheme(); const dynamicOnboardingStyles = onboardingStyles(colors); - /** - * If component ref defined, calculate its position and position coachmark accordingly - */ - const getPosition = (ref) => { - ref && - ref.current && - ref.current.measure((a, b, width, height, px, py) => { - setCoachmarkTop(height + py - INDICATOR_HEIGHT); - setCoachmarkBottom(py - 165); - }); - }; - - useEffect( - () => { - setTimeout(() => { - getPosition(coachmarkRef); - }, 300); - }, - /* eslint-disable-next-line */ - [getPosition], - ); - /** * Dispatches 'setOnboardingWizardStep' with next step - * Closing drawer and navigating to 'BrowserView' */ const onNext = () => { setOnboardingWizardStep && setOnboardingWizardStep(6); - drawerRef?.current?.dismissDrawer?.(); navigation && navigation.navigate(Routes.BROWSER.HOME, { screen: Routes.BROWSER.VIEW, @@ -86,11 +71,9 @@ const Step5 = (props) => { /** * Dispatches 'setOnboardingWizardStep' with next step - * Closing drawer and navigating to 'WalletView' */ const onBack = () => { navigation && navigation.navigate('WalletView'); - drawerRef?.current?.dismissDrawer?.(); setTimeout(() => { setOnboardingWizardStep && setOnboardingWizardStep(4); }, 1); @@ -114,29 +97,29 @@ const Step5 = (props) => { ); - if (coachmarkTop === 0) return null; - return ( - + + + + + + ); }; @@ -154,10 +137,6 @@ Step5.propTypes = { * Dispatch set onboarding wizard step */ setOnboardingWizardStep: PropTypes.func, - /** - * Coachmark ref to get position - */ - coachmarkRef: PropTypes.object, }; export default connect(null, mapDispatchToProps)(Step5); diff --git a/app/components/UI/OnboardingWizard/Step6/index.js b/app/components/UI/OnboardingWizard/Step6/index.js index f2a80877659..5850a0d0058 100644 --- a/app/components/UI/OnboardingWizard/Step6/index.js +++ b/app/components/UI/OnboardingWizard/Step6/index.js @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { View, Text, StyleSheet } from 'react-native'; @@ -9,8 +9,8 @@ import onboardingStyles from './../styles'; import Device from '../../../../util/device'; import AnalyticsV2 from '../../../../util/analyticsV2'; import { ONBOARDING_WIZARD_STEP_DESCRIPTION } from '../../../../util/analytics'; -import { DrawerContext } from '../../../../components/Nav/Main/MainNavigator'; import { useTheme } from '../../../../util/theme'; +import Routes from '../../../../constants/navigation/Routes'; const styles = StyleSheet.create({ main: { @@ -25,10 +25,9 @@ const styles = StyleSheet.create({ }); const Step6 = (props) => { - const { setOnboardingWizardStep, onClose } = props; + const { setOnboardingWizardStep, onClose, navigation } = props; const [ready, setReady] = useState(false); const [coachmarkTop, setCoachmarkTop] = useState(0); - const { drawerRef } = useContext(DrawerContext); const { colors } = useTheme(); const dynamicOnboardingStyles = onboardingStyles(colors); @@ -49,10 +48,10 @@ const Step6 = (props) => { }, []); /** - * Dispatches 'setOnboardingWizardStep' with back step, opening drawer + * Dispatches 'setOnboardingWizardStep' with back step */ const onBack = () => { - drawerRef?.current?.showDrawer?.(); + navigation?.navigate?.(Routes.WALLET.HOME); setOnboardingWizardStep && setOnboardingWizardStep(5); AnalyticsV2.trackEvent( AnalyticsV2.ANALYTICS_EVENTS.ONBOARDING_TOUR_STEP_REVISITED, @@ -106,6 +105,10 @@ const mapDispatchToProps = (dispatch) => ({ }); Step6.propTypes = { + /** + * Object that represents the navigator + */ + navigation: PropTypes.object, /** * Dispatch set onboarding wizard step */ diff --git a/app/components/UI/OnboardingWizard/index.js b/app/components/UI/OnboardingWizard/index.js index 2d3b3b94388..d3552c913c5 100644 --- a/app/components/UI/OnboardingWizard/index.js +++ b/app/components/UI/OnboardingWizard/index.js @@ -1,14 +1,7 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import { - TouchableOpacity, - View, - StyleSheet, - Text, - Dimensions, - InteractionManager, -} from 'react-native'; -import { colors as importedColors, fontStyles } from '../../../styles/common'; +import { View, StyleSheet, Dimensions, InteractionManager } from 'react-native'; +import { colors as importedColors } from '../../../styles/common'; import { connect } from 'react-redux'; import Step1 from './Step1'; import Step2 from './Step2'; @@ -17,19 +10,16 @@ import Step4 from './Step4'; import Step5 from './Step5'; import Step6 from './Step6'; import setOnboardingWizardStep from '../../../actions/wizard'; -import { strings } from '../../../../locales/i18n'; import DefaultPreference from 'react-native-default-preference'; -import ElevatedView from 'react-native-elevated-view'; import Modal from 'react-native-modal'; import Device from '../../../util/device'; import { ONBOARDING_WIZARD_STEP_DESCRIPTION } from '../../../util/analytics'; import { ONBOARDING_WIZARD, EXPLORED } from '../../../constants/storage'; import AnalyticsV2 from '../../../util/analyticsV2'; import { DrawerContext } from '../../../components/Nav/Main/MainNavigator'; -import { useTheme } from '../../../util/theme'; const MIN_HEIGHT = Dimensions.get('window').height; -const createStyles = (colors) => +const createStyles = () => StyleSheet.create({ root: { top: 0, @@ -45,39 +35,6 @@ const createStyles = (colors) => flex: 1, backgroundColor: importedColors.transparent, }, - smallSkipWrapper: { - alignItems: 'center', - alignSelf: 'center', - bottom: Device.isIos() ? 30 : 35, - }, - largeSkipWrapper: { - alignItems: 'center', - alignSelf: 'center', - bottom: Device.isIos() && Device.isIphoneX() ? 98 : 66, - }, - skipButtonContainer: { - height: 30, - width: 120, - borderRadius: 15, - backgroundColor: colors.background.default, - }, - skipButton: { - backgroundColor: colors.background.default, - alignItems: 'center', - justifyContent: 'center', - }, - androidElevated: { - width: 120, - borderRadius: 30, - }, - iosTouchable: { - width: 120, - }, - skipText: { - ...fontStyles.normal, - fontSize: 12, - color: colors.primary.default, - }, }); const OnboardingWizard = (props) => { @@ -88,8 +45,7 @@ const OnboardingWizard = (props) => { coachmarkRef, } = props; const { drawerRef } = useContext(DrawerContext); - const { colors } = useTheme(); - const styles = createStyles(colors); + const styles = createStyles(); /** * Close onboarding wizard setting step to 0 and closing drawer @@ -124,13 +80,7 @@ const OnboardingWizard = (props) => { navigation={navigation} /> ), - 5: ( - - ), + 5: , 6: ( { style={[styles.root, Device.isAndroid() ? { minHeight: MIN_HEIGHT } : {}]} > {onboardingWizardNavigator(step)} - {step !== 1 && ( - - - - {strings('onboarding_wizard.skip_tutorial')} - - - - )} ); }; diff --git a/locales/languages/en.json b/locales/languages/en.json index 41f2fe4c530..5aef3cc8c60 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -71,7 +71,6 @@ "get_started": "Get started" }, "onboarding_wizard": { - "skip_tutorial": "Skip Tutorial", "coachmark": { "action_back": "No, Thanks", "action_next": "Take the tour", @@ -95,10 +94,9 @@ "content3": "now to edit account name." }, "step4": { - "title": "Main Navigation", - "content1": "Tap here", - "content2": "to access your Wallet, Browser, and Transaction activity.", - "content3": "You can take more actions with your accounts & access MetaMask settings." + "title": "Main Menu", + "content1": "You can access Transaction history, Settings, and Support from this menu.", + "content2": "You can take more actions with your accounts & access MetaMask settings." }, "step5": { "title": "Explore the Browser", From a578fb67cf5820d4de9346ce959374444d745d6f Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 5 Oct 2022 13:32:10 -0700 Subject: [PATCH 19/91] don't show account toast when host name is the same --- app/components/Views/Browser/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/components/Views/Browser/index.js b/app/components/Views/Browser/index.js index eefbaba4d14..eef052e64a8 100644 --- a/app/components/Views/Browser/index.js +++ b/app/components/Views/Browser/index.js @@ -52,7 +52,7 @@ const Browser = (props) => { const { colors } = useTheme(); const { toastRef } = useContext(ToastContext); const browserUrl = props.route?.params?.url; - const prevSite = useRef(browserUrl); + const prevSiteHostname = useRef(browserUrl); const { accounts, ensByAccountAddress } = useAccounts(); const accountAvatarType = useSelector((state) => state.settings.useBlockieIcon @@ -121,11 +121,12 @@ const Browser = (props) => { // Handle when the Browser initially mounts and when url changes. if (accounts.length && browserUrl) { - if (prevSite.current !== browserUrl || !hasAccounts.current) { + const hostname = new URL(browserUrl).hostname; + if (prevSiteHostname.current !== hostname || !hasAccounts.current) { checkIfActiveAccountChanged(); } hasAccounts.current = true; - prevSite.current = browserUrl; + prevSiteHostname.current = hostname; } }, [browserUrl, accounts, ensByAccountAddress, accountAvatarType, toastRef]); From 0bcf975dee64d0642a448d0534b97734c3faf4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Thu, 13 Oct 2022 16:30:05 +0100 Subject: [PATCH 20/91] [PS]: update component library imports --- .../SheetActions/SheetActions.tsx | 6 ++--- app/components/Nav/Main/index.js | 4 ++-- .../UI/AccountRightButton/index.tsx | 6 ++--- .../AccountSelectorList.tsx | 4 ++-- app/components/UI/NetworkList/index.js | 6 ++--- .../Views/AccountConnect/AccountConnect.tsx | 8 +++---- .../AccountConnectMultiSelector.tsx | 20 ++++++++-------- .../AccountConnectSingle.tsx | 22 ++++++++--------- .../AccountPermissions/AccountPermissions.tsx | 8 +++---- .../AccountPermissionsConnected.tsx | 4 ++-- .../AccountPermissionsRevoke.tsx | 24 +++++++++---------- app/components/Views/Browser/index.js | 6 ++--- .../Views/Settings/NetworksSettings/index.js | 4 ++-- 13 files changed, 61 insertions(+), 61 deletions(-) diff --git a/app/component-library/components-temp/SheetActions/SheetActions.tsx b/app/component-library/components-temp/SheetActions/SheetActions.tsx index defc760c0b4..a00fd084a8c 100644 --- a/app/component-library/components-temp/SheetActions/SheetActions.tsx +++ b/app/component-library/components-temp/SheetActions/SheetActions.tsx @@ -4,8 +4,8 @@ import { View } from 'react-native'; // External dependencies. import { useStyles } from '../../hooks'; -import ButtonTertiary from '../../components/Buttons/ButtonTertiary'; -import { ButtonBaseSize } from '../../components/Buttons/ButtonBase'; +import ButtonTertiary from '../../components/Buttons/Button/variants/ButtonTertiary'; +import { ButtonSize } from '../../components/Buttons/Button'; import Loader from '../Loader'; // Internal dependencies. @@ -28,7 +28,7 @@ const SheetActions = ({ actions }: SheetActionsProps) => { testID={testID} onPress={onPress} label={label} - size={ButtonBaseSize.Lg} + size={ButtonSize.Lg} disabled={disabled || isLoading} /* eslint-disable-next-line */ style={{ opacity: disabled ? 0.5 : 1 }} diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 0502d1c6f17..343cce03c3e 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -63,7 +63,7 @@ import { } from '../../../util/networks'; import { ToastContext, - ToastVariant, + ToastVariants, } from '../../../component-library/components/Toast'; const Stack = createStackNavigator(); @@ -227,7 +227,7 @@ const Main = (props) => { const networkImage = getNetworkImageSource(networkProvider.chainId); const networkName = getNetworkNameFromProvider(networkProvider); toastRef?.current?.showToast({ - variant: ToastVariant.Network, + variant: ToastVariants.Network, labelOptions: [ { label: `${networkName} `, diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx index f315724b68d..b0c4938227f 100644 --- a/app/components/UI/AccountRightButton/index.tsx +++ b/app/components/UI/AccountRightButton/index.tsx @@ -4,15 +4,15 @@ import { TouchableOpacity, StyleSheet } from 'react-native'; import Device from '../../../util/device'; import AvatarAccount, { AvatarAccountType, -} from '../../../component-library/components/Avatars/AvatarAccount'; +} from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; import { AccountRightButtonProps } from './AccountRightButton.types'; -import AvatarNetwork from '../../../component-library/components/Avatars/AvatarNetwork'; +import AvatarNetwork from '../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork'; import { getNetworkImageSource, getNetworkNameFromProvider, } from '../../../util/networks'; import { toggleNetworkModal } from '../../../actions/modals'; -import { BadgeVariants } from '../../../component-library/components/Badges/Badge'; +import { BadgeVariants } from '../../../component-library/components/Badges/Badge/Badge.types'; import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrapper'; const styles = StyleSheet.create({ diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index f152734a5c6..51127fb576d 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -13,10 +13,10 @@ import { useStyles } from '../../../component-library/hooks'; import Text from '../../../component-library/components/Texts/Text'; import AvatarGroup from '../../../component-library/components/Avatars/AvatarGroup'; import { formatAddress } from '../../../util/address'; -import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; +import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; import { isDefaultAccountName } from '../../../util/ENSUtils'; import { strings } from '../../../../locales/i18n'; -import { AvatarVariants } from '../../../component-library/components/Avatars/Avatar.types'; +import { AvatarVariants } from '../../../component-library/components/Avatars/Avatar/Avatar.types'; import { Account, Assets } from '../../hooks/useAccounts'; import UntypedEngine from '../../../core/Engine'; import { removeAccountFromPermissions } from '../../../core/Permissions'; diff --git a/app/components/UI/NetworkList/index.js b/app/components/UI/NetworkList/index.js index 4bc1e890ac7..372dc611c23 100644 --- a/app/components/UI/NetworkList/index.js +++ b/app/components/UI/NetworkList/index.js @@ -30,8 +30,8 @@ import { NETWORK_SCROLL_ID, } from '../../../constants/test-ids'; import ImageIcon from '../ImageIcon'; -import AvatarNetwork from '../../../component-library/components/Avatars/AvatarNetwork'; -import { AvatarBaseSize } from '../../../component-library/components/Avatars/AvatarBase'; +import AvatarNetwork from '../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork'; +import { AvatarSize } from '../../../component-library/components/Avatars/Avatar'; const createStyles = (colors) => StyleSheet.create({ @@ -281,7 +281,7 @@ export class NetworkList extends PureComponent { ) : ( ))} diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 636560f09c0..a526edeb573 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -22,16 +22,16 @@ import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; import { SelectedAccount } from '../../../components/UI/AccountSelectorList/AccountSelectorList.types'; import { ToastContext, - ToastVariant, - ToastOptions, + ToastVariants, } from '../../../component-library/components/Toast'; +import { ToastOptions } from '../../../component-library/components/Toast/Toast.types'; import { useAccounts, Account } from '../../hooks/useAccounts'; import getAccountNameWithENS from '../../../util/accounts'; import { IconName } from '../../../component-library/components/Icon'; import { getActiveTabUrl } from '../../../util/transactions'; import { getUrlObj } from '../../../util/browser'; import { strings } from '../../../../locales/i18n'; -import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; +import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; import { safeToChecksumAddress } from '../../../util/address'; // Internal dependencies. @@ -139,7 +139,7 @@ const AccountConnect = (props: AccountConnectProps) => { ]; } toastRef?.current?.showToast({ - variant: ToastVariant.Account, + variant: ToastVariants.Account, labelOptions, accountAddress: activeAddress, accountAvatarType, diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx index a25051a7e86..faf9a6fc106 100644 --- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx @@ -11,14 +11,14 @@ import TagUrl from '../../../../component-library/components/Tags/TagUrl'; import Text from '../../../../component-library/components/Texts/Text'; import { useStyles } from '../../../../component-library/hooks'; import ButtonPrimary, { - ButtonPrimaryVariant, -} from '../../../../component-library/components/Buttons/ButtonPrimary'; + ButtonPrimaryVariants, +} from '../../../../component-library/components/Buttons/Button/variants/ButtonPrimary'; import ButtonSecondary, { - ButtonSecondaryVariant, -} from '../../../../component-library/components/Buttons/ButtonSecondary'; -import { ButtonBaseSize } from '../../../../component-library/components/Buttons/ButtonBase'; + ButtonSecondaryVariants, +} from '../../../../component-library/components/Buttons/Button/variants/ButtonSecondary'; +import { ButtonSize } from '../../../../component-library/components/Buttons/Button'; import AccountSelectorList from '../../../UI/AccountSelectorList'; -import ButtonLink from '../../../../component-library/components/Buttons/ButtonLink'; +import ButtonLink from '../../../../component-library/components/Buttons/Button/variants/ButtonLink'; import AnalyticsV2 from '../../../../util/analyticsV2'; import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics'; @@ -120,22 +120,22 @@ const AccountConnectMultiSelector = ({ return ( onDismissSheetWithCallback()} - size={ButtonBaseSize.Lg} + size={ButtonSize.Lg} style={styles.button} /> ( diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index 719f9427bba..a8c54facc2b 100644 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -23,9 +23,9 @@ import AccountConnectMultiSelector from '../AccountConnect/AccountConnectMultiSe import Logger from '../../../util/Logger'; import { ToastContext, - ToastOptions, - ToastVariant, + ToastVariants, } from '../../../component-library/components/Toast'; +import { ToastOptions } from '../../../component-library/components/Toast/Toast.types'; import AnalyticsV2 from '../../../util/analyticsV2'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; import { useAccounts, Account } from '../../hooks/useAccounts'; @@ -34,7 +34,7 @@ import { IconName } from '../../../component-library/components/Icon'; import { getUrlObj } from '../../../util/browser'; import { getActiveTabUrl } from '../../../util/transactions'; import { strings } from '../../../../locales/i18n'; -import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; +import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; // Internal dependencies. import { @@ -169,7 +169,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { ]; } toastRef?.current?.showToast({ - variant: ToastVariant.Account, + variant: ToastVariants.Account, labelOptions, accountAddress: newActiveAddress, accountAvatarType, diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx index 5fe8003e0d4..53542828d24 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -19,7 +19,7 @@ import { AccountPermissionsScreens } from '../AccountPermissions.types'; import { switchActiveAccounts } from '../../../../core/Permissions'; import { ToastContext, - ToastVariant, + ToastVariants, } from '../../../../component-library/components/Toast'; import getAccountNameWithENS from '../../../../util/accounts'; import AnalyticsV2 from '../../../../util/analyticsV2'; @@ -76,7 +76,7 @@ const AccountPermissionsConnected = ({ ensByAccountAddress, }); toastRef?.current?.showToast({ - variant: ToastVariant.Account, + variant: ToastVariants.Account, labelOptions: [ { label: `${activeAccountName} `, diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx index 84f8d2d9d6c..c2fdd280b1e 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx @@ -10,19 +10,19 @@ import TagUrl from '../../../../component-library/components/Tags/TagUrl'; import Text from '../../../../component-library/components/Texts/Text'; import { useStyles } from '../../../../component-library/hooks'; import ButtonSecondary, { - ButtonSecondaryVariant, -} from '../../../../component-library/components/Buttons/ButtonSecondary'; -import { ButtonBaseSize } from '../../../../component-library/components/Buttons/ButtonBase'; + ButtonSecondaryVariants, +} from '../../../../component-library/components/Buttons/Button/variants/ButtonSecondary'; +import { ButtonSize } from '../../../../component-library/components/Buttons/Button'; import AccountSelectorList from '../../../../components/UI/AccountSelectorList'; -import { ButtonTertiaryVariant } from '../../../../component-library/components/Buttons/ButtonTertiary'; +import { ButtonTertiaryVariants } from '../../../../component-library/components/Buttons/Button/variants/ButtonTertiary'; import { removePermittedAccount } from '../../../../core/Permissions'; import UntypedEngine from '../../../../core/Engine'; import Logger from '../../../../util/Logger'; import { ToastContext, - ToastVariant, - ToastOptions, + ToastVariants, } from '../../../../component-library/components/Toast'; +import { ToastOptions } from '../../../../component-library/components/Toast/Toast.types'; import { AccountPermissionsScreens } from '../AccountPermissions.types'; import getAccountNameWithENS from '../../../../util/accounts'; @@ -55,7 +55,7 @@ const AccountPermissionsRevoke = ({ ); onDismissSheet(); toastRef?.current?.showToast({ - variant: ToastVariant.Plain, + variant: ToastVariants.Plain, labelOptions: [{ label: strings('toast.revoked_all') }], }); } catch (e) { @@ -75,7 +75,7 @@ const AccountPermissionsRevoke = ({ label: strings('accounts.disconnect_all_accounts'), onPress: revokeAllAccounts, disabled: isLoading, - variant: ButtonTertiaryVariant.Danger, + variant: ButtonTertiaryVariants.Danger, }, ]} /> @@ -101,7 +101,7 @@ const AccountPermissionsRevoke = ({ ( { if (permittedAddresses.length === 1) { // Dismiss and show toast @@ -130,7 +130,7 @@ const AccountPermissionsRevoke = ({ { label: strings('toast.now_active') }, ); toastRef?.current?.showToast({ - variant: ToastVariant.Account, + variant: ToastVariants.Account, labelOptions, accountAddress: nextActiveAddress, accountAvatarType, @@ -139,14 +139,14 @@ const AccountPermissionsRevoke = ({ // Just disconnect removePermittedAccount(hostname, address); toastRef?.current?.showToast({ - variant: ToastVariant.Plain, + variant: ToastVariants.Plain, labelOptions, }); } } }} label={strings('accounts.disconnect')} - size={ButtonBaseSize.Sm} + size={ButtonSize.Sm} style={styles.disconnectButton} /> )} diff --git a/app/components/Views/Browser/index.js b/app/components/Views/Browser/index.js index eefbaba4d14..a0218cba47d 100644 --- a/app/components/Views/Browser/index.js +++ b/app/components/Views/Browser/index.js @@ -23,10 +23,10 @@ import getAccountNameWithENS from '../../../util/accounts'; import { useAccounts } from '../../../components/hooks/useAccounts'; import { ToastContext, - ToastVariant, + ToastVariants, } from '../../../component-library/components/Toast'; import { strings } from '../../../../locales/i18n'; -import { AvatarAccountType } from '../../../component-library/components/Avatars/AvatarAccount'; +import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; const margin = 16; const THUMB_WIDTH = Dimensions.get('window').width / 2 - margin * 2; @@ -105,7 +105,7 @@ const Browser = (props) => { }); // Show active account toast toastRef?.current?.showToast({ - variant: ToastVariant.Account, + variant: ToastVariants.Account, labelOptions: [ { label: `${accountName} `, diff --git a/app/components/Views/Settings/NetworksSettings/index.js b/app/components/Views/Settings/NetworksSettings/index.js index 3529d7ff0a6..b54253fa553 100644 --- a/app/components/Views/Settings/NetworksSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/index.js @@ -23,8 +23,8 @@ import { MAINNET, RPC } from '../../../../constants/network'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; import { ThemeContext, mockTheme } from '../../../../util/theme'; import ImageIcons from '../../../UI/ImageIcon'; -import AvatarNetwork from '../../../../component-library/components/Avatars/AvatarNetwork'; -import { AvatarBaseSize } from '../../../../component-library/components/Avatars/AvatarBase'; +import AvatarNetwork from '../../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork'; +import { AvatarBaseSize } from '../../../../component-library/components/Avatars/Avatar/foundation/AvatarBase'; const createStyles = (colors) => StyleSheet.create({ From 1a00887818d47a5e99407bb4e73e7432b25c2cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Fri, 14 Oct 2022 20:15:24 +0100 Subject: [PATCH 21/91] (CustomNetwork): update prop to allow wallet return --- .../NetworkSettings/CustomNetworkView/CustomNetwork.tsx | 2 ++ .../NetworkSettings/CustomNetworkView/CustomNetwork.types.ts | 4 ++++ .../Views/Settings/NetworksSettings/NetworkSettings/index.js | 3 +++ 3 files changed, 9 insertions(+) diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx b/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx index e1af0d7b043..9cd59cf0465 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx @@ -20,6 +20,7 @@ const CustomNetwork = ({ toggleWarningModal, showNetworkModal, switchTab, + shouldNetworkSwitchPopToWallet, }: CustomNetworkProps) => { const savedNetworkList = useSelector( (state: any) => @@ -53,6 +54,7 @@ const CustomNetwork = ({ onClose={closeNetworkModal} network={selectedNetwork} navigation={navigation} + shouldNetworkSwitchPopToWallet={shouldNetworkSwitchPopToWallet} /> )} {filteredPopularList.map((item, index) => ( diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.types.ts b/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.types.ts index be3e3b6f10f..a3caedd16dd 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.types.ts +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.types.ts @@ -38,4 +38,8 @@ export interface CustomNetworkProps extends Network { * Switch tab between popular and custom networks */ switchTab: (goToPage: number) => void; + /** + * should navigation return to wallet after network change + */ + shouldNetworkSwitchPopToWallet: boolean; } diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js index 688946c7597..50fefe109cc 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js @@ -954,6 +954,9 @@ class NetworkSettings extends PureComponent { toggleWarningModal={this.toggleWarningModal} showNetworkModal={this.showNetworkModal} switchTab={this.tabView} + shouldNetworkSwitchPopToWallet={ + shouldNetworkSwitchPopToWallet + } /> Date: Fri, 14 Oct 2022 22:20:32 +0100 Subject: [PATCH 22/91] update build number --- android/app/build.gradle | 2 +- bitrise.yml | 2 +- ios/MetaMask.xcodeproj/project.pbxproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b5e425e613a..adc54cc1237 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -159,7 +159,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 973 + versionCode 991 versionName "5.8.0" multiDexEnabled true testBuildType System.getProperty('testBuildType', 'debug') diff --git a/bitrise.yml b/bitrise.yml index 6c5865e5aa5..e1fd2dcdff6 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -436,7 +436,7 @@ app: VERSION_NAME: 5.8.0 - opts: is_expand: false - VERSION_NUMBER: 973 + VERSION_NUMBER: 991 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 26a3550dfe2..dc3ea27d8e3 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1183,7 +1183,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 973; + CURRENT_PROJECT_VERSION = 991; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; FRAMEWORK_SEARCH_PATHS = ( From 543022354e8cb83161a7cb51dd07180146e5fc39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Fri, 14 Oct 2022 23:40:22 +0100 Subject: [PATCH 23/91] implement visual & ux updates --- .../SheetActions/SheetActions.tsx | 7 ++++- .../Navigation/TabBar/TabBar.styles.ts | 1 + .../Navigation/TabBarItem/TabBarItem.tsx | 2 +- app/components/UI/Navbar/index.js | 3 +++ app/components/UI/NetworkList/index.js | 1 + .../UI/Swaps/components/Onboarding.js | 7 +++-- .../AccountConnectMultiSelector.tsx | 27 ++++++++++++++++++- .../AccountPermissionsRevoke.tsx | 4 +-- locales/languages/en.json | 15 ++++++----- 9 files changed, 53 insertions(+), 14 deletions(-) diff --git a/app/component-library/components-temp/SheetActions/SheetActions.tsx b/app/component-library/components-temp/SheetActions/SheetActions.tsx index a00fd084a8c..2d77bcd9f84 100644 --- a/app/component-library/components-temp/SheetActions/SheetActions.tsx +++ b/app/component-library/components-temp/SheetActions/SheetActions.tsx @@ -20,9 +20,14 @@ const SheetActions = ({ actions }: SheetActionsProps) => { actions.map( ({ label, onPress, testID, isLoading, disabled, variant }, index) => { const key = `${label}-${index}`; + // Avoid drawing separator above the first element + const isFirstElement = index === 0; + return ( - {actions.length > 1 && } + {actions.length > 1 && !isFirstElement && ( + + )} { flexDirection: 'row', alignItems: 'center', height: 82, + paddingHorizontal: 16, marginBottom: bottomInset, borderTopWidth: 0.5, borderColor: colors.border.muted, diff --git a/app/component-library/components/Navigation/TabBarItem/TabBarItem.tsx b/app/component-library/components/Navigation/TabBarItem/TabBarItem.tsx index 4cd551b4057..d3e5ac23f78 100644 --- a/app/component-library/components/Navigation/TabBarItem/TabBarItem.tsx +++ b/app/component-library/components/Navigation/TabBarItem/TabBarItem.tsx @@ -30,7 +30,7 @@ const TabBarItem = ({ return ( - + {label} diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index df3b8c96342..dfd060a3983 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -566,6 +566,8 @@ export function getBrowserViewNavbarOptions(route, themeColors) { backgroundColor: themeColors.background.default, shadowColor: importedColors.transparent, elevation: 0, + borderBottomWidth: 0.5, + borderColor: themeColors.border.muted, }, headerIcon: { color: themeColors.primary.default, @@ -871,6 +873,7 @@ export function getWalletNavbarOptions( }, headerTitle: { justifyContent: 'center', + marginTop: 5, flex: 1, }, }); diff --git a/app/components/UI/NetworkList/index.js b/app/components/UI/NetworkList/index.js index 372dc611c23..cdf011237fd 100644 --- a/app/components/UI/NetworkList/index.js +++ b/app/components/UI/NetworkList/index.js @@ -87,6 +87,7 @@ const createStyles = (colors) => footer: { marginVertical: 10, flexDirection: 'row', + paddingBottom: 8, }, footerButton: { flex: 1, diff --git a/app/components/UI/Swaps/components/Onboarding.js b/app/components/UI/Swaps/components/Onboarding.js index c3024d34cba..fd8e1c64e3b 100644 --- a/app/components/UI/Swaps/components/Onboarding.js +++ b/app/components/UI/Swaps/components/Onboarding.js @@ -9,6 +9,7 @@ import { Platform, UIManager, } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useNavigation } from '@react-navigation/native'; import { strings } from '../../../../../locales/i18n'; import Device from '../../../../util/device'; @@ -22,7 +23,7 @@ const swapsAggregatorsLight = require('../../../../images/swaps_aggs-light.png') const swapsAggregatorsDark = require('../../../../images/swaps_aggs-dark.png'); /* eslint-enable import/no-commonjs */ -const createStyles = (colors) => +const createStyles = (colors, bottomInset) => StyleSheet.create({ screen: { flex: 1, @@ -57,6 +58,7 @@ const createStyles = (colors) => }, actionButtonWrapper: { width: '100%', + paddingBottom: bottomInset, }, actionButton: { marginVertical: 10, @@ -73,7 +75,8 @@ if ( function Onboarding({ setHasOnboarded }) { const navigation = useNavigation(); const { colors } = useTheme(); - const styles = createStyles(colors); + const { bottom: bottomInset } = useSafeAreaInsets(); + const styles = createStyles(colors, bottomInset); const swapsAggregators = useAssetFromTheme( swapsAggregatorsLight, swapsAggregatorsDark, diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx index faf9a6fc106..132b5cf6853 100644 --- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx @@ -114,6 +114,25 @@ const AccountConnectMultiSelector = ({ [accounts, isLoading, onSelectAddress, styles], ); + const renderUnselectAllButton = useCallback( + () => + Boolean(accounts.length) && ( + { + if (isLoading) return; + onSelectAddress([]); + }} + style={{ + ...styles.selectAllButton, + ...(isLoading && styles.disabled), + }} + > + {strings('accounts.unselect_all')} + + ), + [accounts, isLoading, onSelectAddress, styles], + ); + const renderCtaButtons = useCallback(() => { const isConnectDisabled = Boolean(!selectedAddresses.length) || isLoading; @@ -152,6 +171,10 @@ const AccountConnectMultiSelector = ({ styles, ]); + const areAllAccountsSelected = accounts + .map(({ address }) => address) + .every((address) => selectedAddresses.includes(address)); + return ( <> @@ -160,7 +183,9 @@ const AccountConnectMultiSelector = ({ {strings('accounts.connect_description')} - {renderSelectAllButton()} + {areAllAccountsSelected + ? renderUnselectAllButton() + : renderSelectAllButton()} { diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx index c2fdd280b1e..626a6ccd84f 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx @@ -72,7 +72,7 @@ const AccountPermissionsRevoke = ({ diff --git a/locales/languages/en.json b/locales/languages/en.json index c21faccaef3..28e47891c88 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -275,13 +275,13 @@ }, "wallet": { "title": "Wallet", - "tokens": "TOKENS", + "tokens": "Tokens", "collectible": "Collectible", "collectibles": "NFTs", "transactions": "TRANSACTIONS", "no_collectibles": "Don’t see your NFT?", "no_available_tokens": "Don't see your token?", - "add_tokens": "Import Tokens", + "add_tokens": "Import tokens", "tokens_detected_in_account": "{{tokenCount}} new {{tokensLabel}} found in this account", "token_toast": { "tokens_imported_title": "Imported Tokens", @@ -444,9 +444,9 @@ "no": "No", "yes_remove_it": "Yes, remove it", "accounts_title": "Accounts", - "connect_account_title": "Connect Account", - "connect_accounts_title": "Connect Accounts", - "connected_accounts_title": "Connected Accounts", + "connect_account_title": "Connect account", + "connect_accounts_title": "Connect accounts", + "connected_accounts_title": "Connected accounts", "connect_description": "Share your account address, balance, activity, and allow site to initiate transactions.", "connect_multiple_accounts": "Connect multiple accounts", "connect_more_accounts": "Connect more accounts", @@ -454,9 +454,10 @@ "connect": "Connect", "connect_with_count": "Connect{{countLabel}}", "select_all": "Select all", + "unselect_all": "Unselect all", "permissions": "Permissions", - "disconnect": "Disconnect", - "disconnect_all_accounts": "Disconnect all accounts" + "revoke": "Revoke", + "revoke_all": "Revoke all" }, "toast": { "connected_and_active": "connected and active.", From 1136fe936a76635bcac8c8bb1a8aab66d046c6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Sat, 15 Oct 2022 18:12:51 +0100 Subject: [PATCH 24/91] (TestBuild) bump version --- android/app/build.gradle | 2 +- bitrise.yml | 2 +- ios/MetaMask.xcodeproj/project.pbxproj | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index adc54cc1237..114adf806c4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -160,7 +160,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 991 - versionName "5.8.0" + versionName "5.9.1" multiDexEnabled true testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy "minReactNative", "minReactNative46" diff --git a/bitrise.yml b/bitrise.yml index e1fd2dcdff6..775f1e4629b 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -433,7 +433,7 @@ app: PROJECT_LOCATION_IOS: ios - opts: is_expand: false - VERSION_NAME: 5.8.0 + VERSION_NAME: 5.9.1 - opts: is_expand: false VERSION_NUMBER: 991 diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index dc3ea27d8e3..780a53fef0f 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1116,7 +1116,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 973; + CURRENT_PROJECT_VERSION = 991; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1152,7 +1152,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.0; + MARKETING_VERSION = 5.9.1; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "$(inherited)", @@ -1218,7 +1218,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.0; + MARKETING_VERSION = 5.9.1; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "$(inherited)", @@ -1326,7 +1326,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 973; + CURRENT_PROJECT_VERSION = 991; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1362,7 +1362,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.0; + MARKETING_VERSION = 5.9.1; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "$(inherited)", @@ -1393,7 +1393,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 973; + CURRENT_PROJECT_VERSION = 991; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; FRAMEWORK_SEARCH_PATHS = ( @@ -1428,7 +1428,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.0; + MARKETING_VERSION = 5.9.1; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "$(inherited)", From ea6c741a04bba6cf48ad97dd4ed66f8bdc0a83f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Mon, 17 Oct 2022 14:31:57 +0200 Subject: [PATCH 25/91] Toast: respect account avatar global settings --- app/component-library/components/Toast/Toast.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/component-library/components/Toast/Toast.tsx b/app/component-library/components/Toast/Toast.tsx index 8fbf67de8c3..13e45ab9a19 100644 --- a/app/component-library/components/Toast/Toast.tsx +++ b/app/component-library/components/Toast/Toast.tsx @@ -26,7 +26,6 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; // External dependencies. import Avatar, { AvatarSize, AvatarVariants } from '../Avatars/Avatar'; -import { AvatarAccountType } from '../Avatars/Avatar/variants/AvatarAccount'; import Text, { TextVariants } from '../Texts/Text'; import Button, { ButtonVariants } from '../Buttons/Button'; @@ -133,11 +132,14 @@ const Toast = forwardRef((_, ref: React.ForwardedRef) => { return null; case ToastVariants.Account: { const { accountAddress } = toastOptions; + const { accountAvatarType } = toastOptions; return ( From 5203dc87b420e2e170c6a2bad97ff5180398e09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Mon, 31 Oct 2022 16:21:02 +0000 Subject: [PATCH 26/91] (TabBarItem): update component jest snapshot --- .../TabBarItem/__snapshots__/TabBarItem.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/component-library/components/Navigation/TabBarItem/__snapshots__/TabBarItem.test.tsx.snap b/app/component-library/components/Navigation/TabBarItem/__snapshots__/TabBarItem.test.tsx.snap index 3988cbfee27..f0df631047e 100644 --- a/app/component-library/components/Navigation/TabBarItem/__snapshots__/TabBarItem.test.tsx.snap +++ b/app/component-library/components/Navigation/TabBarItem/__snapshots__/TabBarItem.test.tsx.snap @@ -21,7 +21,7 @@ exports[`TabBarItem should render correctly 1`] = ` "color": "#037DD6", } } - variant="sBodyMD" + variant="sBodySM" > Tab From 4eeaac772294c783d829ed9026564e0c890df4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Mon, 31 Oct 2022 20:19:49 +0000 Subject: [PATCH 27/91] (util/address): remove duplicate import --- app/util/address/index.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/util/address/index.js b/app/util/address/index.js index daa34d3d489..b541d4ab5a2 100644 --- a/app/util/address/index.js +++ b/app/util/address/index.js @@ -11,7 +11,12 @@ import { KeyringTypes } from '@metamask/controllers'; import Engine from '../../core/Engine'; import { strings } from '../../../locales/i18n'; import { tlc } from '../general'; -import { doENSLookup, doENSReverseLookup } from '../../util/ENSUtils'; +import { + doENSLookup, + doENSReverseLookup, + ENSCache, + isDefaultAccountName, +} from '../../util/ENSUtils'; import NetworkList from '../../util/networks'; import { collectConfusables } from '../../util/confusables'; import { @@ -19,7 +24,6 @@ import { SYMBOL_ERROR, } from '../../../app/constants/error'; import { PROTOCOLS } from '../../constants/deeplinks'; -import { ENSCache, isDefaultAccountName } from '../ENSUtils'; /** * Returns full checksummed address @@ -370,13 +374,13 @@ export async function validateAddressOrENS(params) { * Check if it's smart contract address */ /* - const smart = false; // + const smart = false; // - if (smart) { - addressError = strings('transaction.smartContractAddressWarning'); - isOnlyWarning = true; - } - */ + if (smart) { + addressError = strings('transaction.smartContractAddressWarning'); + isOnlyWarning = true; + } + */ } else if (isENS(toAccount)) { toEnsName = toAccount; confusableCollection = collectConfusables(toEnsName); From 37b226a46fe1fe572d24e64305ed2ce18948a16a Mon Sep 17 00:00:00 2001 From: Andre Pimenta Date: Fri, 18 Nov 2022 17:41:03 +0000 Subject: [PATCH 28/91] SDK compatibility --- app/components/Nav/Main/RootRPCMethodsUI.js | 45 +++++++++++++ app/core/RPCMethods/RPCMethodMiddleware.ts | 70 ++++++++++++++++++--- app/core/SDKConnect.ts | 1 + 3 files changed, 107 insertions(+), 9 deletions(-) diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index a4ea2fc9e03..c0c751ffdff 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -83,6 +83,8 @@ const RootRPCMethodsUI = (props) => { const [customNetworkToAdd, setCustomNetworkToAdd] = useState(null); const [customNetworkToSwitch, setCustomNetworkToSwitch] = useState(null); + const [hostToApprove, setHostToApprove] = useState(null); + const [watchAsset, setWatchAsset] = useState(false); const [suggestedAssetMeta, setSuggestedAssetMeta] = useState(undefined); @@ -605,6 +607,47 @@ const RootRPCMethodsUI = (props) => { ); + /** + * When user clicks on approve to connect with a dapp + */ + const onAccountsConfirm = () => { + acceptPendingApproval(hostToApprove.id, hostToApprove.requestData); + setShowPendingApproval(false); + }; + + /** + * When user clicks on reject to connect with a dapp + */ + const onAccountsReject = () => { + rejectPendingApproval(hostToApprove.id, hostToApprove.requestData); + setShowPendingApproval(false); + }; + + /** + * Render the modal that asks the user to approve/reject connections to a dapp + */ + const renderAccountsApprovalModal = () => ( + + + + ); + /** * On rejection addinga an asset */ @@ -675,6 +718,7 @@ const RootRPCMethodsUI = (props) => { } break; case ApprovalTypes.CONNECT_ACCOUNTS: + setHostToApprove({ data: requestData, id: request.id }); showPendingApprovalModal({ type: ApprovalTypes.CONNECT_ACCOUNTS, origin: request.origin, @@ -756,6 +800,7 @@ const RootRPCMethodsUI = (props) => { {renderSwitchCustomNetworkModal()} {renderWatchAssetModal()} {renderQRSigningModal()} + {renderAccountsApprovalModal()} ); }; diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index 00059eca8c8..8ea223fdc8a 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -48,6 +48,11 @@ interface RPCMethodsMiddleParameters { tabId: string; // For WalletConnect isWalletConnect: boolean; + // For MM SDK + isMMSDK: boolean; + getApprovedHosts: any; + setApprovedHosts: (approvedHosts: any) => void; + approveHost: (fullHostname: string) => void; injectHomePageScripts: (bookmarks?: []) => void; analytics: { [key: string]: string | boolean }; } @@ -55,13 +60,13 @@ interface RPCMethodsMiddleParameters { export const checkActiveAccountAndChainId = async ({ address, chainId, - isWalletConnect, + checkSelectedAddress, hostname, }: any) => { let isInvalidAccount = false; if (address) { const formattedAddress = safeToChecksumAddress(address); - if (isWalletConnect) { + if (checkSelectedAddress) { const selectedAddress = Engine.context.PreferencesController.state.selectedAddress; if (formattedAddress !== safeToChecksumAddress(selectedAddress)) { @@ -141,12 +146,31 @@ export const getRpcMethodMiddleware = ({ tabId, // For WalletConnect isWalletConnect, + // For MM SDK + isMMSDK, + getApprovedHosts, + setApprovedHosts, + approveHost, injectHomePageScripts, // For analytics analytics, }: RPCMethodsMiddleParameters) => // all user facing RPC calls not implemented by the provider createAsyncMiddleware(async (req: any, res: any, next: any) => { + const getAccounts = (): string[] => { + const { + privacy: { privacyMode }, + } = store.getState(); + + const selectedAddress = + Engine.context.PreferencesController.state.selectedAddress?.toLowerCase(); + + const isEnabled = + isWalletConnect || !privacyMode || getApprovedHosts()[hostname]; + + return isEnabled && selectedAddress ? [selectedAddress] : []; + }; + const checkTabActive = () => { if (!tabId) return true; const { browser } = store.getState(); @@ -247,6 +271,30 @@ export const getRpcMethodMiddleware = ({ const permittedAccounts = await getPermittedAccounts(hostname); if (!params?.force && permittedAccounts.length) { res.result = permittedAccounts; + } else if (isMMSDK) { + try { + let { selectedAddress } = + Engine.context.PreferencesController.state; + + selectedAddress = selectedAddress?.toLowerCase(); + + await requestUserApproval({ + type: ApprovalTypes.CONNECT_ACCOUNTS, + requestData: { hostname }, + }); + const fullHostname = hostname; + approveHost?.(fullHostname); + setApprovedHosts?.({ + ...getApprovedHosts?.(), + [fullHostname]: true, + }); + + res.result = selectedAddress ? [selectedAddress] : []; + } catch (e) { + throw ethErrors.provider.userRejectedRequest( + 'User denied account authorization.', + ); + } } else { try { checkTabActive(); @@ -272,7 +320,11 @@ export const getRpcMethodMiddleware = ({ } }, eth_accounts: async () => { - res.result = await getPermittedAccounts(hostname); + if (isMMSDK || isWalletConnect) { + res.result = await getAccounts(); + } else { + res.result = await getPermittedAccounts(hostname); + } }, eth_coinbase: async () => { res.result = await getPermittedAccounts(hostname); @@ -283,7 +335,7 @@ export const getRpcMethodMiddleware = ({ hostname, address: req.params[0].from, chainId: req.params[0].chainId, - isWalletConnect, + checkSelectedAddress: isMMSDK || isWalletConnect, }); next(); }, @@ -312,7 +364,7 @@ export const getRpcMethodMiddleware = ({ await checkActiveAccountAndChainId({ hostname, address: req.params[0].from, - isWalletConnect, + checkSelectedAddress: isMMSDK || isWalletConnect, }); const rawSig = await MessageManager.addUnapprovedMessageAsync({ data: req.params[1], @@ -359,7 +411,7 @@ export const getRpcMethodMiddleware = ({ await checkActiveAccountAndChainId({ hostname, address: params.from, - isWalletConnect, + checkSelectedAddress: isMMSDK || isWalletConnect, }); const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({ @@ -389,7 +441,7 @@ export const getRpcMethodMiddleware = ({ await checkActiveAccountAndChainId({ hostname, address: req.params[1], - isWalletConnect, + checkSelectedAddress: isMMSDK || isWalletConnect, }); const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( @@ -428,7 +480,7 @@ export const getRpcMethodMiddleware = ({ hostname, address: req.params[0], chainId, - isWalletConnect, + checkSelectedAddress: isMMSDK || isWalletConnect, }); const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( @@ -467,7 +519,7 @@ export const getRpcMethodMiddleware = ({ hostname, address: req.params[0], chainId, - isWalletConnect, + checkSelectedAddress: isMMSDK || isWalletConnect, }); const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( diff --git a/app/core/SDKConnect.ts b/app/core/SDKConnect.ts index 9068c385563..891d74af6c4 100644 --- a/app/core/SDKConnect.ts +++ b/app/core/SDKConnect.ts @@ -181,6 +181,7 @@ class Connection { wizardScrollAdjusted: { current: false }, tabId: '', isWalletConnect: false, + isMMSDK: true, analytics: { isRemoteConn: true, platform: parseSource(originatorInfo?.platform), From af6cce6f5ec95230fc7f6edf25c8f8a5058548ae Mon Sep 17 00:00:00 2001 From: Andre Pimenta Date: Fri, 18 Nov 2022 17:41:47 +0000 Subject: [PATCH 29/91] Update coinbase --- app/core/RPCMethods/RPCMethodMiddleware.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index 8ea223fdc8a..fdabead2095 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -327,7 +327,11 @@ export const getRpcMethodMiddleware = ({ } }, eth_coinbase: async () => { - res.result = await getPermittedAccounts(hostname); + if (isMMSDK || isWalletConnect) { + res.result = await getAccounts(); + } else { + res.result = await getPermittedAccounts(hostname); + } }, eth_sendTransaction: async () => { checkTabActive(); From 3f5b09829fc2037ea5e4723a90f9efd5c31040b6 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Fri, 18 Nov 2022 12:18:30 -0800 Subject: [PATCH 30/91] Clean up SDK support a bit --- app/components/Nav/Main/RootRPCMethodsUI.js | 42 ++++++----- app/core/Engine.js | 5 +- app/core/RPCMethods/RPCMethodMiddleware.ts | 83 +++++++++------------ 3 files changed, 60 insertions(+), 70 deletions(-) diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index c0c751ffdff..c41fd55b08c 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -509,20 +509,6 @@ const RootRPCMethodsUI = (props) => { ); - const rejectPendingApproval = (id, error) => { - const { ApprovalController } = Engine.context; - try { - ApprovalController.reject(id, error); - } catch (error) { - Logger.error(error, 'Reject while rejecting pending connection request'); - } - }; - - const acceptPendingApproval = (id, requestData) => { - const { ApprovalController } = Engine.context; - ApprovalController.accept(id, requestData); - }; - const onAddCustomNetworkReject = () => { setShowPendingApproval(false); rejectPendingApproval( @@ -537,7 +523,7 @@ const RootRPCMethodsUI = (props) => { }; /** - * Render the modal that asks the user to approve/reject connections to a dapp + * Render the modal that asks the user to add chain to wallet. */ const renderAddCustomNetworkModal = () => ( { }; /** - * Render the modal that asks the user to approve/reject connections to a dapp + * Render the modal that asks the user to switch chain on wallet. */ const renderSwitchCustomNetworkModal = () => ( { ); + // Reject pending approval using MetaMask SDK. + const rejectPendingApproval = (id, error) => { + const { ApprovalController } = Engine.context; + try { + ApprovalController.reject(id, error); + } catch (error) { + Logger.error(error, 'Reject while rejecting pending connection request'); + } + }; + + // Accept pending approval using MetaMask SDK. + const acceptPendingApproval = (id, requestData) => { + const { ApprovalController } = Engine.context; + ApprovalController.accept(id, requestData); + }; + /** - * When user clicks on approve to connect with a dapp + * When user clicks on approve to connect with a dapp using the MetaMask SDK. */ const onAccountsConfirm = () => { acceptPendingApproval(hostToApprove.id, hostToApprove.requestData); @@ -616,7 +618,7 @@ const RootRPCMethodsUI = (props) => { }; /** - * When user clicks on reject to connect with a dapp + * When user clicks on reject to connect with a dapp using the MetaMask SDK. */ const onAccountsReject = () => { rejectPendingApproval(hostToApprove.id, hostToApprove.requestData); @@ -624,7 +626,7 @@ const RootRPCMethodsUI = (props) => { }; /** - * Render the modal that asks the user to approve/reject connections to a dapp + * Render the modal that asks the user to approve/reject connections to a dapp using the MetaMask SDK. */ const renderAccountsApprovalModal = () => ( { setCurrentPageMeta(requestData.pageMeta); } switch (request.type) { - case 'wallet_requestPermissions': + case ApprovalTypes.REQUEST_PERMISSIONS: if (requestData?.permissions?.eth_accounts) { props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.ACCOUNT_CONNECT, diff --git a/app/core/Engine.js b/app/core/Engine.js index 6626c91902b..740cec8ca76 100644 --- a/app/core/Engine.js +++ b/app/core/Engine.js @@ -92,6 +92,7 @@ class Engine { ); const networkController = new NetworkController({ infuraProjectId: process.env.MM_INFURA_PROJECT_ID || NON_EMPTY, + // TODO: Check if providerConfig here is still necessary. providerConfig: { static: { eth_sendTransaction: async ( @@ -118,8 +119,8 @@ class Engine { end: (arg0: null, arg1: any[]) => void, payload: { hostname: string | number }, ) => { - const { approvedHosts, privacyMode } = store.getState(); - const isEnabled = !privacyMode || approvedHosts[payload.hostname]; + const { approvedHosts } = store.getState(); + const isEnabled = approvedHosts[payload.hostname]; const { KeyringController } = this.context; const isUnlocked = KeyringController.isUnlocked(); const selectedAddress = diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index fdabead2095..30601ca1129 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -28,6 +28,7 @@ export enum ApprovalTypes { SIGN_MESSAGE = 'SIGN_MESSAGE', ADD_ETHEREUM_CHAIN = 'ADD_ETHEREUM_CHAIN', SWITCH_ETHEREUM_CHAIN = 'SWITCH_ETHEREUM_CHAIN', + REQUEST_PERMISSIONS = 'wallet_requestPermissions', } interface RPCMethodsMiddleParameters { @@ -57,6 +58,7 @@ interface RPCMethodsMiddleParameters { analytics: { [key: string]: string | boolean }; } +// Also used by WalletConnect.js. export const checkActiveAccountAndChainId = async ({ address, chainId, @@ -149,7 +151,6 @@ export const getRpcMethodMiddleware = ({ // For MM SDK isMMSDK, getApprovedHosts, - setApprovedHosts, approveHost, injectHomePageScripts, // For analytics @@ -157,20 +158,23 @@ export const getRpcMethodMiddleware = ({ }: RPCMethodsMiddleParameters) => // all user facing RPC calls not implemented by the provider createAsyncMiddleware(async (req: any, res: any, next: any) => { + // Utility function for getting accounts for either WalletConnect or MetaMask SDK. const getAccounts = (): string[] => { - const { - privacy: { privacyMode }, - } = store.getState(); - const selectedAddress = Engine.context.PreferencesController.state.selectedAddress?.toLowerCase(); - - const isEnabled = - isWalletConnect || !privacyMode || getApprovedHosts()[hostname]; - + const isEnabled = isWalletConnect || getApprovedHosts()[hostname]; return isEnabled && selectedAddress ? [selectedAddress] : []; }; + // Used by eth_accounts and eth_coinbase RPCs. + const getEthAccounts = async () => { + if (isMMSDK || isWalletConnect) { + res.result = getAccounts(); + } else { + res.result = await getPermittedAccounts(hostname); + } + }; + const checkTabActive = () => { if (!tabId) return true; const { browser } = store.getState(); @@ -260,41 +264,36 @@ export const getRpcMethodMiddleware = ({ } }, eth_requestAccounts: async () => { + console.log('REQUESTING ACCOUNTS!'); const { params } = req; - if (isWalletConnect) { let { selectedAddress } = Engine.context.PreferencesController.state; selectedAddress = selectedAddress?.toLowerCase(); res.result = [selectedAddress]; + } else if (isMMSDK) { + console.log('METAMASK SDK!'); + try { + let { selectedAddress } = + Engine.context.PreferencesController.state; + selectedAddress = selectedAddress?.toLowerCase(); + // Prompts user approval UI in RootRPCMethodsUI.js. + await requestUserApproval({ + type: ApprovalTypes.CONNECT_ACCOUNTS, + requestData: { hostname }, + }); + // Stores approvals in SDKConnect.ts. + approveHost?.(hostname); + res.result = selectedAddress ? [selectedAddress] : []; + } catch (e) { + throw ethErrors.provider.userRejectedRequest( + 'User denied account authorization.', + ); + } } else { // Check against permitted accounts. const permittedAccounts = await getPermittedAccounts(hostname); if (!params?.force && permittedAccounts.length) { res.result = permittedAccounts; - } else if (isMMSDK) { - try { - let { selectedAddress } = - Engine.context.PreferencesController.state; - - selectedAddress = selectedAddress?.toLowerCase(); - - await requestUserApproval({ - type: ApprovalTypes.CONNECT_ACCOUNTS, - requestData: { hostname }, - }); - const fullHostname = hostname; - approveHost?.(fullHostname); - setApprovedHosts?.({ - ...getApprovedHosts?.(), - [fullHostname]: true, - }); - - res.result = selectedAddress ? [selectedAddress] : []; - } catch (e) { - throw ethErrors.provider.userRejectedRequest( - 'User denied account authorization.', - ); - } } else { try { checkTabActive(); @@ -319,20 +318,8 @@ export const getRpcMethodMiddleware = ({ } } }, - eth_accounts: async () => { - if (isMMSDK || isWalletConnect) { - res.result = await getAccounts(); - } else { - res.result = await getPermittedAccounts(hostname); - } - }, - eth_coinbase: async () => { - if (isMMSDK || isWalletConnect) { - res.result = await getAccounts(); - } else { - res.result = await getPermittedAccounts(hostname); - } - }, + eth_accounts: getEthAccounts, + eth_coinbase: getEthAccounts, eth_sendTransaction: async () => { checkTabActive(); await checkActiveAccountAndChainId({ From 357fdabd0f964221452187cde530335cd0a8196e Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Fri, 18 Nov 2022 12:27:51 -0800 Subject: [PATCH 31/91] Add padding to TagURL --- app/component-library/components/Tags/TagUrl/TagUrl.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/component-library/components/Tags/TagUrl/TagUrl.tsx b/app/component-library/components/Tags/TagUrl/TagUrl.tsx index e98295cbb01..5589771d435 100644 --- a/app/component-library/components/Tags/TagUrl/TagUrl.tsx +++ b/app/component-library/components/Tags/TagUrl/TagUrl.tsx @@ -22,6 +22,7 @@ const TagUrl = ({ imageSource, label, cta, style, ...props }: TagUrlProps) => { variant={AvatarVariants.Favicon} imageSource={imageSource} size={AvatarSize.Md} + style={styles.favicon} /> {label} From 008c18243d1cbd9599f7b2f50c81fab5ab16d111 Mon Sep 17 00:00:00 2001 From: sethkfman Date: Wed, 16 Nov 2022 18:55:10 -0700 Subject: [PATCH 32/91] updated android SDK versions --- android/app/src/main/AndroidManifest.xml | 4 +++- android/build.gradle | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5b0158ec797..ad890741574 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -22,12 +22,14 @@ tools:ignore="GoogleAppIndexingWarning" android:networkSecurityConfig="@xml/react_native_config" android:largeHeap="true" - > + android:exported="false" + > diff --git a/android/build.gradle b/android/build.gradle index cb3d3410d0f..42e75b303c5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,8 +5,8 @@ buildscript { ext { buildToolsVersion = "30.0.3" minSdkVersion = project.hasProperty('minSdkVersion') ? project.getProperty('minSdkVersion') : 23 - compileSdkVersion = 30 - targetSdkVersion = 30 + compileSdkVersion = 31 + targetSdkVersion = 31 kotlin_version = "1.5.31" kotlinVersion = "$kotlin_version" supportLibVersion = "28.0.0" From 17f7afdacfe5c84660124e7c395ffa0b5908d7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Thu, 17 Nov 2022 16:54:06 +0000 Subject: [PATCH 33/91] update to react-native 0.66.5 more info https://github.com/facebook/react-native/issues/35210 --- ios/Podfile.lock | 438 +++++++++++++++++++++++------------------------ package.json | 2 +- yarn.lock | 8 +- 3 files changed, 224 insertions(+), 224 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2eb1046ad15..29172c0180a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -5,14 +5,14 @@ PODS: - React - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - - FBLazyVector (0.66.3) - - FBReactNativeSpec (0.66.3): + - FBLazyVector (0.66.5) + - FBReactNativeSpec (0.66.5): - RCT-Folly (= 2021.06.28.00-v2) - - RCTRequired (= 0.66.3) - - RCTTypeSafety (= 0.66.3) - - React-Core (= 0.66.3) - - React-jsi (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) + - RCTRequired (= 0.66.5) + - RCTTypeSafety (= 0.66.5) + - React-Core (= 0.66.5) + - React-jsi (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) - Flipper (0.93.0): - Flipper-Folly (~> 2.6) - Flipper-RSocket (~> 1.4) @@ -92,204 +92,204 @@ PODS: - DoubleConversion - fmt (~> 6.2.1) - glog - - RCTRequired (0.66.3) + - RCTRequired (0.66.5) - RCTSearchApi (1.0.1): - React - React-RCTImage - - RCTTypeSafety (0.66.3): - - FBLazyVector (= 0.66.3) + - RCTTypeSafety (0.66.5): + - FBLazyVector (= 0.66.5) - RCT-Folly (= 2021.06.28.00-v2) - - RCTRequired (= 0.66.3) - - React-Core (= 0.66.3) - - React (0.66.3): - - React-Core (= 0.66.3) - - React-Core/DevSupport (= 0.66.3) - - React-Core/RCTWebSocket (= 0.66.3) - - React-RCTActionSheet (= 0.66.3) - - React-RCTAnimation (= 0.66.3) - - React-RCTBlob (= 0.66.3) - - React-RCTImage (= 0.66.3) - - React-RCTLinking (= 0.66.3) - - React-RCTNetwork (= 0.66.3) - - React-RCTSettings (= 0.66.3) - - React-RCTText (= 0.66.3) - - React-RCTVibration (= 0.66.3) - - React-callinvoker (0.66.3) - - React-Core (0.66.3): + - RCTRequired (= 0.66.5) + - React-Core (= 0.66.5) + - React (0.66.5): + - React-Core (= 0.66.5) + - React-Core/DevSupport (= 0.66.5) + - React-Core/RCTWebSocket (= 0.66.5) + - React-RCTActionSheet (= 0.66.5) + - React-RCTAnimation (= 0.66.5) + - React-RCTBlob (= 0.66.5) + - React-RCTImage (= 0.66.5) + - React-RCTLinking (= 0.66.5) + - React-RCTNetwork (= 0.66.5) + - React-RCTSettings (= 0.66.5) + - React-RCTText (= 0.66.5) + - React-RCTVibration (= 0.66.5) + - React-callinvoker (0.66.5) + - React-Core (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-Core/Default (= 0.66.3) - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-Core/Default (= 0.66.5) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/CoreModulesHeaders (0.66.3): + - React-Core/CoreModulesHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/Default (0.66.3): + - React-Core/Default (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/DevSupport (0.66.3): + - React-Core/DevSupport (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-Core/Default (= 0.66.3) - - React-Core/RCTWebSocket (= 0.66.3) - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-jsinspector (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-Core/Default (= 0.66.5) + - React-Core/RCTWebSocket (= 0.66.5) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-jsinspector (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTActionSheetHeaders (0.66.3): + - React-Core/RCTActionSheetHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTAnimationHeaders (0.66.3): + - React-Core/RCTAnimationHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTBlobHeaders (0.66.3): + - React-Core/RCTBlobHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTImageHeaders (0.66.3): + - React-Core/RCTImageHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTLinkingHeaders (0.66.3): + - React-Core/RCTLinkingHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTNetworkHeaders (0.66.3): + - React-Core/RCTNetworkHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTPushNotificationHeaders (0.66.3): + - React-Core/RCTPushNotificationHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTSettingsHeaders (0.66.3): + - React-Core/RCTSettingsHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTTextHeaders (0.66.3): + - React-Core/RCTTextHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTVibrationHeaders (0.66.3): + - React-Core/RCTVibrationHeaders (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - React-Core/Default - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-Core/RCTWebSocket (0.66.3): + - React-Core/RCTWebSocket (0.66.5): - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-Core/Default (= 0.66.3) - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsiexecutor (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-Core/Default (= 0.66.5) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsiexecutor (= 0.66.5) + - React-perflogger (= 0.66.5) - Yoga - - React-CoreModules (0.66.3): - - FBReactNativeSpec (= 0.66.3) + - React-CoreModules (0.66.5): + - FBReactNativeSpec (= 0.66.5) - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.66.3) - - React-Core/CoreModulesHeaders (= 0.66.3) - - React-jsi (= 0.66.3) - - React-RCTImage (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-cxxreact (0.66.3): + - RCTTypeSafety (= 0.66.5) + - React-Core/CoreModulesHeaders (= 0.66.5) + - React-jsi (= 0.66.5) + - React-RCTImage (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-cxxreact (0.66.5): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-callinvoker (= 0.66.3) - - React-jsi (= 0.66.3) - - React-jsinspector (= 0.66.3) - - React-logger (= 0.66.3) - - React-perflogger (= 0.66.3) - - React-runtimeexecutor (= 0.66.3) - - React-jsi (0.66.3): + - React-callinvoker (= 0.66.5) + - React-jsi (= 0.66.5) + - React-jsinspector (= 0.66.5) + - React-logger (= 0.66.5) + - React-perflogger (= 0.66.5) + - React-runtimeexecutor (= 0.66.5) + - React-jsi (0.66.5): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-jsi/Default (= 0.66.3) - - React-jsi/Default (0.66.3): + - React-jsi/Default (= 0.66.5) + - React-jsi/Default (0.66.5): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-jsiexecutor (0.66.3): + - React-jsiexecutor (0.66.5): - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-perflogger (= 0.66.3) - - React-jsinspector (0.66.3) - - React-logger (0.66.3): + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-perflogger (= 0.66.5) + - React-jsinspector (0.66.5) + - React-logger (0.66.5): - glog - react-native-aes (1.3.9): - React @@ -337,77 +337,77 @@ PODS: - React-Core - react-native-webview (11.13.0): - React-Core - - React-perflogger (0.66.3) - - React-RCTActionSheet (0.66.3): - - React-Core/RCTActionSheetHeaders (= 0.66.3) - - React-RCTAnimation (0.66.3): - - FBReactNativeSpec (= 0.66.3) + - React-perflogger (0.66.5) + - React-RCTActionSheet (0.66.5): + - React-Core/RCTActionSheetHeaders (= 0.66.5) + - React-RCTAnimation (0.66.5): + - FBReactNativeSpec (= 0.66.5) - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.66.3) - - React-Core/RCTAnimationHeaders (= 0.66.3) - - React-jsi (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-RCTBlob (0.66.3): - - FBReactNativeSpec (= 0.66.3) + - RCTTypeSafety (= 0.66.5) + - React-Core/RCTAnimationHeaders (= 0.66.5) + - React-jsi (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-RCTBlob (0.66.5): + - FBReactNativeSpec (= 0.66.5) - RCT-Folly (= 2021.06.28.00-v2) - - React-Core/RCTBlobHeaders (= 0.66.3) - - React-Core/RCTWebSocket (= 0.66.3) - - React-jsi (= 0.66.3) - - React-RCTNetwork (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-RCTImage (0.66.3): - - FBReactNativeSpec (= 0.66.3) + - React-Core/RCTBlobHeaders (= 0.66.5) + - React-Core/RCTWebSocket (= 0.66.5) + - React-jsi (= 0.66.5) + - React-RCTNetwork (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-RCTImage (0.66.5): + - FBReactNativeSpec (= 0.66.5) - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.66.3) - - React-Core/RCTImageHeaders (= 0.66.3) - - React-jsi (= 0.66.3) - - React-RCTNetwork (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-RCTLinking (0.66.3): - - FBReactNativeSpec (= 0.66.3) - - React-Core/RCTLinkingHeaders (= 0.66.3) - - React-jsi (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-RCTNetwork (0.66.3): - - FBReactNativeSpec (= 0.66.3) + - RCTTypeSafety (= 0.66.5) + - React-Core/RCTImageHeaders (= 0.66.5) + - React-jsi (= 0.66.5) + - React-RCTNetwork (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-RCTLinking (0.66.5): + - FBReactNativeSpec (= 0.66.5) + - React-Core/RCTLinkingHeaders (= 0.66.5) + - React-jsi (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-RCTNetwork (0.66.5): + - FBReactNativeSpec (= 0.66.5) - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.66.3) - - React-Core/RCTNetworkHeaders (= 0.66.3) - - React-jsi (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-RCTPushNotification (0.66.3): - - FBReactNativeSpec (= 0.66.3) - - RCTTypeSafety (= 0.66.3) - - React-Core/RCTPushNotificationHeaders (= 0.66.3) - - React-jsi (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-RCTSettings (0.66.3): - - FBReactNativeSpec (= 0.66.3) + - RCTTypeSafety (= 0.66.5) + - React-Core/RCTNetworkHeaders (= 0.66.5) + - React-jsi (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-RCTPushNotification (0.66.5): + - FBReactNativeSpec (= 0.66.5) + - RCTTypeSafety (= 0.66.5) + - React-Core/RCTPushNotificationHeaders (= 0.66.5) + - React-jsi (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-RCTSettings (0.66.5): + - FBReactNativeSpec (= 0.66.5) - RCT-Folly (= 2021.06.28.00-v2) - - RCTTypeSafety (= 0.66.3) - - React-Core/RCTSettingsHeaders (= 0.66.3) - - React-jsi (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-RCTText (0.66.3): - - React-Core/RCTTextHeaders (= 0.66.3) - - React-RCTVibration (0.66.3): - - FBReactNativeSpec (= 0.66.3) + - RCTTypeSafety (= 0.66.5) + - React-Core/RCTSettingsHeaders (= 0.66.5) + - React-jsi (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-RCTText (0.66.5): + - React-Core/RCTTextHeaders (= 0.66.5) + - React-RCTVibration (0.66.5): + - FBReactNativeSpec (= 0.66.5) - RCT-Folly (= 2021.06.28.00-v2) - - React-Core/RCTVibrationHeaders (= 0.66.3) - - React-jsi (= 0.66.3) - - ReactCommon/turbomodule/core (= 0.66.3) - - React-runtimeexecutor (0.66.3): - - React-jsi (= 0.66.3) - - ReactCommon/turbomodule/core (0.66.3): + - React-Core/RCTVibrationHeaders (= 0.66.5) + - React-jsi (= 0.66.5) + - ReactCommon/turbomodule/core (= 0.66.5) + - React-runtimeexecutor (0.66.5): + - React-jsi (= 0.66.5) + - ReactCommon/turbomodule/core (0.66.5): - DoubleConversion - glog - RCT-Folly (= 2021.06.28.00-v2) - - React-callinvoker (= 0.66.3) - - React-Core (= 0.66.3) - - React-cxxreact (= 0.66.3) - - React-jsi (= 0.66.3) - - React-logger (= 0.66.3) - - React-perflogger (= 0.66.3) + - React-callinvoker (= 0.66.5) + - React-Core (= 0.66.5) + - React-cxxreact (= 0.66.5) + - React-jsi (= 0.66.5) + - React-logger (= 0.66.5) + - React-perflogger (= 0.66.5) - ReactNativePayments (1.5.0): - React - rn-fetch-blob (0.12.0): @@ -778,8 +778,8 @@ SPEC CHECKSUMS: BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 - FBLazyVector: de148e8310b8b878db304ceea2fec13f2c02e3a0 - FBReactNativeSpec: 6192956c9e346013d5f1809ba049af720b11c6a4 + FBLazyVector: a926a9aaa3596b181972abf0f47eff3dee796222 + FBReactNativeSpec: f1141d5407f4a27c397bca5db03cc03919357b0d Flipper: b1fddf9a17c32097b2b4c806ad158b2f36bb2692 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 @@ -796,18 +796,18 @@ SPEC CHECKSUMS: lottie-react-native: 7ca15c46249b61e3f9ffcf114cb4123e907a2156 OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 - RCTRequired: 59d2b744d8c2bf2d9bc7032a9f654809adcf7d50 + RCTRequired: 405e24508a0feed1771d48caebb85c581db53122 RCTSearchApi: d2d38a5a7bffbfb144e2c770fbb30f51b1053067 - RCTTypeSafety: d0aaf7ccae5c70a4aaa3a5c3e9e0db97efae760e - React: fbe655dd1d12c052299b61abdc576720098d80fc - React-callinvoker: a535746608d9bc8b1dea7095ed4d8d3d7aae9a05 - React-Core: 008d2638c4f80b189c8e170ff2d241027ec517fd - React-CoreModules: 91c9a03f4e1b74494c087d9c9a29e89a3145c228 - React-cxxreact: 9c462fb6d59f865855e2dee2097c7d87b3d2de49 - React-jsi: 4de8b8d70ba4ed841eb9b772bdb719f176387e21 - React-jsiexecutor: 433a691aee158533a6a6ee9c86cb4a1684fa2853 - React-jsinspector: d9c8eb0b53f0da206fed56612b289fec84991157 - React-logger: e522e76fa3e9ec3e7d7115b49485cc065cf4ae06 + RCTTypeSafety: 0654998ea6afd3dccecbf6bb379d7c10d1361a72 + React: cce3ac45191e66a78c79234582cbfe322e4dfd00 + React-callinvoker: 613b19264ce63cab0a2bbb6546caa24f2ad0a679 + React-Core: fe529d7c1d74b3eb9505fdfc2f4b10f2f2984a85 + React-CoreModules: 71f5d032922a043ab6f9023acda41371a774bf5c + React-cxxreact: 808c0d39b270860af712848082009d623180999c + React-jsi: 01b246df3667ad921a33adeb0ce199772afe9e2b + React-jsiexecutor: 50a73168582868421112609d2fb155e607e34ec8 + React-jsinspector: 953260b8580780a6e81f2a6d319a8d42fd5028d8 + React-logger: fa4ff1e9c7e115648f7c5dafb7c20822ab4f7a7e react-native-aes: ff31f0dd4c791eb423a631ee04570fcf3c618924 react-native-background-timer: 1b6e6b4e10f1b74c367a1fdc3c72b67c619b222b react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c @@ -826,19 +826,19 @@ SPEC CHECKSUMS: react-native-viewpager: f730c1d175a2c1ae789464855d4a2c14247d3109 react-native-webrtc: b8f2769386d51a6a8c89778478618fe311226bc3 react-native-webview: 133a6a5149f963259646e710b4545c67ef35d7c9 - React-perflogger: 73732888d37d4f5065198727b167846743232882 - React-RCTActionSheet: 96c6d774fa89b1f7c59fc460adc3245ba2d7fd79 - React-RCTAnimation: 8940cfd3a8640bd6f6372150dbdb83a79bcbae6c - React-RCTBlob: e80de5fdf952a4f226a00fc54f3db526809f92f7 - React-RCTImage: f990d6b272c7e89ff864caf0bccfb620ab3ca5d0 - React-RCTLinking: 2280ed0d5ffb78954b484b90228d597b5f941c5f - React-RCTNetwork: 1359fa853c216616e711b810dcb8682a6a8e7564 - React-RCTPushNotification: 2442f0fcbba9c30d69519a3beda0723fab7e56a6 - React-RCTSettings: 84958860aaa3639f0249e751ea7702c62eb67188 - React-RCTText: 196cf06b8cb6229d8c6dd9fc9057bdf97db5d3fb - React-RCTVibration: 50cfe7049167cfc7e83ac5542c6fff0c76791a9b - React-runtimeexecutor: bbbdb3d8fcf327c6e2249ee71b6ef1764b7dc266 - ReactCommon: 9bac022ab71596f2b0fde1268272543184c63971 + React-perflogger: 169fb34f60c5fd793b370002ee9c916eba9bc4ae + React-RCTActionSheet: 2355539e02ad5cd4b1328682ab046487e1e1e920 + React-RCTAnimation: 150644a38c24d80f1f761859c10c727412303f57 + React-RCTBlob: 66042a0ab4206f112ed453130f2cb8802dd7cd82 + React-RCTImage: 3b954d8398ec4bed26cec10e10d311cb3533818b + React-RCTLinking: 331d9b8a0702c751c7843ddc65b64297c264adc2 + React-RCTNetwork: 96e10dad824ce112087445199ea734b79791ad14 + React-RCTPushNotification: 5488d48929b2b43586e03e9b2e380010d3c4f2e1 + React-RCTSettings: 41feb3f5fb3319846ad0ba9e8d339e54b5774b67 + React-RCTText: 254741e11c41516759e93ab0b8c38b90f8998f61 + React-RCTVibration: 96dbefca7504f3e52ff47cd0ad0826d20e3a789f + React-runtimeexecutor: 09041c28ce04143a113eac2d357a6b06bd64b607 + ReactCommon: 8a7a138ae43c04bb8dd760935589f326ca810484 ReactNativePayments: a4e3ac915256a4e759c8a04338b558494a63a0f5 rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba RNCAsyncStorage: 0c357f3156fcb16c8589ede67cc036330b6698ca @@ -854,7 +854,7 @@ SPEC CHECKSUMS: RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94 RNOS: 6f2f9a70895bbbfbdad7196abd952e7b01d45027 - RNReanimated: 0a4de6844119f4f1c3a46e6e7958ee7425ab4d0f + RNReanimated: d25b5cff636be95594974add4baaed8b6fcf2dac RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19 RNSensors: c363d486c879e181905dea84a2535e49af1c2d25 RNSentry: e86fb2e2fec0365644f4b582938bf66be515acce @@ -865,7 +865,7 @@ SPEC CHECKSUMS: Sentry: 9d055e2de30a77685e86b219acf02e59b82091fc sovran-react-native: fd3dc8f1a4b14acdc4ad25fc6b4ac4f52a2a2a15 TcpSockets: a8eb6b5867fe643e6cfed5db2e4de62f4d1e8fd0 - Yoga: 32a18c0e845e185f4a2a66ec76e1fd1f958f22fa + Yoga: b316a990bb6d115afa1b436b5626ac7c61717d17 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a PODFILE CHECKSUM: 7f7f2a91148820d72060690efb985d8d04e01f4b diff --git a/package.json b/package.json index eac85bb56ef..a7fb100f84d 100644 --- a/package.json +++ b/package.json @@ -216,7 +216,7 @@ "query-string": "^6.12.1", "randomfill": "^1.0.4", "react": "17.0.2", - "react-native": "0.66.3", + "react-native": "0.66.5", "react-native-actionsheet": "beefe/react-native-actionsheet#107/head", "react-native-aes-crypto": "1.3.9", "react-native-aes-crypto-forked": "git+https://github.com/MetaMask/react-native-aes-crypto-forked.git#397d5db5250e8e7408294807965b5b9fd4ca6a25", diff --git a/yarn.lock b/yarn.lock index 8ac3cf194ff..86c7e4cc2c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19699,10 +19699,10 @@ react-native-webview@11.13.0: escape-string-regexp "2.0.0" invariant "2.2.4" -react-native@0.66.3: - version "0.66.3" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.66.3.tgz#25c7c4c7d81867326b3eb7a36f0fe6a61fa4104e" - integrity sha512-B/dQpuvta9YvF5MihDWefoGlTvxzUHK5X5RjdrXHAu/ihTehJXxEA+m6z/tufp1ZUMDjU+tMZK6gnehzCuYfzw== +react-native@0.66.5: + version "0.66.5" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.66.5.tgz#9056a2f7ad04d5e75b3a00dab847b3366d69f26a" + integrity sha512-dC5xmE1anT+m8eGU0N/gv2XUWZygii6TTqbwZPsN+uMhVvjxt4FsTqpZOFFvA5sxLPR/NDEz8uybTvItNBMClw== dependencies: "@jest/create-cache-key-function" "^27.0.1" "@react-native-community/cli" "^6.0.0" From 722c568f205989184ca8d8e7369b1907fd88de01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Fri, 18 Nov 2022 21:31:59 +0000 Subject: [PATCH 34/91] fix linter --- app/components/Nav/Main/RootRPCMethodsUI.js | 32 ++++++++++---------- app/components/UI/TransactionReview/index.js | 5 --- app/core/RPCMethods/RPCMethodMiddleware.ts | 2 -- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index c41fd55b08c..3045ea4f391 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -509,6 +509,22 @@ const RootRPCMethodsUI = (props) => { ); + // Reject pending approval using MetaMask SDK. + const rejectPendingApproval = (id, error) => { + const { ApprovalController } = Engine.context; + try { + ApprovalController.reject(id, error); + } catch (error) { + Logger.error(error, 'Reject while rejecting pending connection request'); + } + }; + + // Accept pending approval using MetaMask SDK. + const acceptPendingApproval = (id, requestData) => { + const { ApprovalController } = Engine.context; + ApprovalController.accept(id, requestData); + }; + const onAddCustomNetworkReject = () => { setShowPendingApproval(false); rejectPendingApproval( @@ -593,22 +609,6 @@ const RootRPCMethodsUI = (props) => { ); - // Reject pending approval using MetaMask SDK. - const rejectPendingApproval = (id, error) => { - const { ApprovalController } = Engine.context; - try { - ApprovalController.reject(id, error); - } catch (error) { - Logger.error(error, 'Reject while rejecting pending connection request'); - } - }; - - // Accept pending approval using MetaMask SDK. - const acceptPendingApproval = (id, requestData) => { - const { ApprovalController } = Engine.context; - ApprovalController.accept(id, requestData); - }; - /** * When user clicks on approve to connect with a dapp using the MetaMask SDK. */ diff --git a/app/components/UI/TransactionReview/index.js b/app/components/UI/TransactionReview/index.js index 4e8f1147194..6ad33aa48a5 100644 --- a/app/components/UI/TransactionReview/index.js +++ b/app/components/UI/TransactionReview/index.js @@ -402,11 +402,6 @@ class TransactionReview extends PureComponent { gasSelected, chainId, transaction: { from }, - updateTransactionState, - gasObject, - eip1559GasTransaction, - dappSuggestedGasPrice, - dappSuggestedEIP1559Gas, } = this.props; const { actionKey, diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index 30601ca1129..e2f5404779b 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -264,14 +264,12 @@ export const getRpcMethodMiddleware = ({ } }, eth_requestAccounts: async () => { - console.log('REQUESTING ACCOUNTS!'); const { params } = req; if (isWalletConnect) { let { selectedAddress } = Engine.context.PreferencesController.state; selectedAddress = selectedAddress?.toLowerCase(); res.result = [selectedAddress]; } else if (isMMSDK) { - console.log('METAMASK SDK!'); try { let { selectedAddress } = Engine.context.PreferencesController.state; From 649b974206ba07c5b2766b5467322c6370ef2aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Tue, 13 Dec 2022 16:25:37 +0000 Subject: [PATCH 35/91] fix typo --- app/util/networks/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/util/networks/index.js b/app/util/networks/index.js index 4b862c75070..93f5379dd18 100644 --- a/app/util/networks/index.js +++ b/app/util/networks/index.js @@ -1,4 +1,4 @@ -jmport URL from 'url-parse'; +import URL from 'url-parse'; import { utils } from 'ethers'; import EthContract from 'ethjs-contract'; import { getContractFactory } from '@eth-optimism/contracts/dist/contract-defs'; From e222a4427d0a7ad406b37e1e81a9cd30fa5e8d7b Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 14 Dec 2022 08:22:34 -0800 Subject: [PATCH 36/91] Fix revoke button colors (#5255) * Fix revoke button colors * Remove unused import --- .../TagUrl/__snapshots__/TagUrl.test.tsx.snap | 5 ++++ .../AccountConnectMultiSelector.tsx | 23 ++++++++++--------- .../AccountConnectSingle.tsx | 23 ++++++++++--------- .../AccountPermissionsRevoke.tsx | 14 ++++++----- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap b/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap index 8492a3da481..e70af7ccb3b 100644 --- a/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap +++ b/app/component-library/components/Tags/TagUrl/__snapshots__/TagUrl.test.tsx.snap @@ -24,6 +24,11 @@ exports[`TagUrl should render correctly 1`] = ` } } size="32" + style={ + Object { + "marginRight": 8, + } + } variant="Favicon" /> - onDismissSheetWithCallback()} size={ButtonSize.Lg} style={styles.button} /> - ( - - ( - { if (permittedAddresses.length === 1) { // Dismiss and show toast From 406610fbbff88b920ec80901888b2816cbe9149a Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 14 Dec 2022 08:22:53 -0800 Subject: [PATCH 37/91] Hide tab bar on Android (#5261) --- app/components/Nav/Main/MainNavigator.js | 39 ++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index beb66cb0487..2aff4661ff9 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -1,5 +1,5 @@ -import React, { useRef } from 'react'; -import { Image, StyleSheet } from 'react-native'; +import React, { useRef, useState, useEffect } from 'react'; +import { Image, StyleSheet, Keyboard, Platform } from 'react-native'; import { createStackNavigator } from '@react-navigation/stack'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import Browser from '../../Views/Browser'; @@ -198,6 +198,7 @@ export const DrawerContext = React.createContext({ drawerRef: null }); const HomeTabs = () => { const drawerRef = useRef(null); + const [isKeyboardHidden, setIsKeyboardHidden] = useState(true); const options = { home: { @@ -208,18 +209,38 @@ const HomeTabs = () => { }, }; + useEffect(() => { + // Hide keyboard on Android when keyboard is visible. + // Better solution would be to update android:windowSoftInputMode in the AndroidManifest and refactor pages to support it. + if (Platform.OS === 'android') { + const showSubscription = Keyboard.addListener('keyboardDidShow', () => { + setIsKeyboardHidden(false); + }); + const hideSubscription = Keyboard.addListener('keyboardDidHide', () => { + setIsKeyboardHidden(true); + }); + + return () => { + showSubscription.remove(); + hideSubscription.remove(); + }; + } + }, []); + return ( ( - - )} + tabBar={({ state, descriptors, navigation }) => + isKeyboardHidden ? ( + + ) : null + } > Date: Wed, 14 Dec 2022 18:53:24 +0000 Subject: [PATCH 38/91] fix lint errors --- app/components/UI/OnboardingWizard/index.js | 8 ++------ .../Settings/NetworksSettings/NetworkSettings/index.js | 4 ++-- app/components/Views/Settings/NetworksSettings/index.js | 2 -- app/components/hooks/AppConfig/AppConfig.ts | 4 +++- app/core/Engine.js | 1 - 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/app/components/UI/OnboardingWizard/index.js b/app/components/UI/OnboardingWizard/index.js index 02909c0bb91..880cd3508e8 100644 --- a/app/components/UI/OnboardingWizard/index.js +++ b/app/components/UI/OnboardingWizard/index.js @@ -1,11 +1,7 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import { - View, - StyleSheet, - InteractionManager, -} from 'react-native'; -import { colors as importedColors, } from '../../../styles/common'; +import { View, StyleSheet, InteractionManager } from 'react-native'; +import { colors as importedColors } from '../../../styles/common'; import { connect } from 'react-redux'; import Step1 from './Step1'; import Step2 from './Step2'; diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js index 1baf94583d8..dd324807e8e 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js @@ -561,8 +561,8 @@ class NetworkSettings extends PureComponent { isCustomMainnet ? navigation.navigate('OptinMetrics') : shouldNetworkSwitchPopToWallet - ? navigation.navigate('WalletView') - : navigation.goBack(); + ? navigation.navigate('WalletView') + : navigation.goBack(); } }; diff --git a/app/components/Views/Settings/NetworksSettings/index.js b/app/components/Views/Settings/NetworksSettings/index.js index 970c8b48d14..a73a4539802 100644 --- a/app/components/Views/Settings/NetworksSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/index.js @@ -23,8 +23,6 @@ import { MAINNET, RPC } from '../../../../constants/network'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; import { ThemeContext, mockTheme } from '../../../../util/theme'; import ImageIcons from '../../../UI/ImageIcon'; -import AvatarNetwork from '../../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork'; -import { AvatarBaseSize } from '../../../../component-library/components/Avatars/Avatar/foundation/AvatarBase'; import { ADD_NETWORK_BUTTON } from '../../../../../wdio/features/testIDs/Screens/NetworksScreen.testids'; const createStyles = (colors) => diff --git a/app/components/hooks/AppConfig/AppConfig.ts b/app/components/hooks/AppConfig/AppConfig.ts index 7c5d48b1734..d7ca7db1620 100644 --- a/app/components/hooks/AppConfig/AppConfig.ts +++ b/app/components/hooks/AppConfig/AppConfig.ts @@ -12,6 +12,8 @@ interface Security { More information on this interface can be found here: https://github.com/MetaMask/metamask-mobile/tree/gh-pages - this interface should match the this api: https://github.com/MetaMask/metamask-mobile/tree/gh-pages#app-config-api */ -export default interface AppConfig { +interface AppConfig { security: Security; } + +export default AppConfig; diff --git a/app/core/Engine.js b/app/core/Engine.js index 315333e2885..83df6c15858 100644 --- a/app/core/Engine.js +++ b/app/core/Engine.js @@ -99,7 +99,6 @@ class Engine { name: 'NetworkController', allowedEvents: [], allowedActions: [], - // TODO: Check if providerConfig here is still necessary. }), }; From d2b27538f137c296cbf64ed4b9770be0a7e90430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <1649425+jpcloureiro@users.noreply.github.com> Date: Thu, 15 Dec 2022 23:44:20 +0000 Subject: [PATCH 39/91] update Permission System translations (#5401) translations fetched from commit bda3e4a --- locales/languages/de.json | 23 ++++++++++++++++++++++- locales/languages/el.json | 29 +++++++++++++++++++++++++---- locales/languages/es.json | 27 ++++++++++++++++++++++++--- locales/languages/fr.json | 25 +++++++++++++++++++++++-- locales/languages/hi.json | 27 ++++++++++++++++++++++++--- locales/languages/id.json | 27 ++++++++++++++++++++++++--- locales/languages/ja.json | 27 ++++++++++++++++++++++++--- locales/languages/ko.json | 23 ++++++++++++++++++++++- locales/languages/pt.json | 25 +++++++++++++++++++++++-- locales/languages/ru.json | 25 +++++++++++++++++++++++-- locales/languages/tl.json | 31 ++++++++++++++++++++++++++----- locales/languages/tr.json | 31 ++++++++++++++++++++++++++----- locales/languages/vi.json | 25 +++++++++++++++++++++++-- locales/languages/zh.json | 23 ++++++++++++++++++++++- 14 files changed, 331 insertions(+), 37 deletions(-) diff --git a/locales/languages/de.json b/locales/languages/de.json index 830d845633d..97f5e5f5195 100644 --- a/locales/languages/de.json +++ b/locales/languages/de.json @@ -407,7 +407,28 @@ "remove_account_title": "Kontoentfernung", "remove_account_message": "Möchten Sie dieses Konto wirklich entfernen?", "no": "Nein", - "yes_remove_it": "Ja, entfernen" + "yes_remove_it": "Ja, entfernen", + "accounts_title": "Konten", + "connect_account_title": "Konto verbinden", + "connect_accounts_title": "Konten verbinden", + "connected_accounts_title": "Verbundene Konten", + "connect_description": "Konto-Adresse, Guthaben, Aktivitäten teilen und dieser Seite erlauben, Transaktionen einzuleiten.", + "connect_multiple_accounts": "Mehrere Konten verbinden", + "connect_more_accounts": "Mehr Konten verbinden", + "cancel": "Abbrechen", + "connect": "Verbinden", + "connect_with_count": "{{countLabel}} verbinden", + "select_all": "\nAlle auswählen", + "permissions": "Berechtigungen", + "disconnect": "Verbindung trennen", + "disconnect_all_accounts": "Alle Konten trennen" + }, + "toast": { + "connected_and_active": "verbunden und aktiv.", + "now_active": "jetzt aktiv.", + "revoked": "widerrufen.", + "revoked_all": "Alle Konten widerrufen.", + "accounts_connected": "Konten verbunden." }, "connect_qr_hardware": { "title": "Eine QR-basierte Hardware-Wallet verknüpfen", diff --git a/locales/languages/el.json b/locales/languages/el.json index 4c694d6a4ce..1b0c352038e 100644 --- a/locales/languages/el.json +++ b/locales/languages/el.json @@ -401,13 +401,34 @@ }, "accounts": { "create_new_account": "Δημιουργία Νέου Λογαριασμού", - "import_account": "Εισαγωγή Λογαριασμού", - "connect_hardware": "Σύνδεση Πορτοφολιού Υλικού", - "imported": "ΕΙΣΗΧΘΗ", + "import_account": "Εισαγωγή ενός Λογαριασμού", + "connect_hardware": "Σύνδεση Υλικού Πορτοφολιού", + "imported": "Έγινε εισαγωγή", "remove_account_title": "Αφαίρεση Λογαριασμού", "remove_account_message": "Θέλετε σίγουρα να αφαιρέσετε αυτόν τον λογαριασμό;", "no": "Όχι", - "yes_remove_it": "Ναι, αφαιρέστε τον" + "yes_remove_it": "Ναι, αφαιρέστε τον", + "accounts_title": "Λογαριασμοί", + "connect_account_title": "Σύνδεση Λογαριασμού", + "connect_accounts_title": "Σύνδεση Λογαριασμών", + "connected_accounts_title": "Συνδεδεμένοι Λογαριασμοί", + "connect_description": "Μοιραστείτε τη διεύθυνση του λογαριασμού σας, το υπόλοιπο, τη δραστηριότητά σας και επιτρέψτε στον ιστότοπο να ξεκινήσει συναλλαγές.", + "connect_multiple_accounts": "Σύνδεση πολλαπλών λογαριασμών", + "connect_more_accounts": "Σύνδεση περισσότερων λογαριασμών", + "cancel": "Ακύρωση", + "connect": "Σύνδεση", + "connect_with_count": "Σύνδεση{{countLabel}}", + "select_all": "Επιλογή όλων", + "permissions": "Άδειες", + "disconnect": "Αποσύνδεση", + "disconnect_all_accounts": "Αποσύνδεση όλων των λογαριασμών" + }, + "toast": { + "connected_and_active": "συνδεδεμένοι και ενεργοί.", + "now_active": "τώρα ενεργοί.", + "revoked": "ανακλήθηκαν.", + "revoked_all": "Όλοι οι λογαριασμοί ανακλήθηκαν.", + "accounts_connected": "λογαριασμοί συνδεδεμένοι." }, "connect_qr_hardware": { "title": "Συνδέστε ένα πορτοφόλι εξοπλισμού με βάση κωδικούς QR", diff --git a/locales/languages/es.json b/locales/languages/es.json index 9f55ca4ee86..7923413479c 100644 --- a/locales/languages/es.json +++ b/locales/languages/es.json @@ -402,12 +402,33 @@ "accounts": { "create_new_account": "Crear cuenta nueva", "import_account": "Importar una cuenta", - "connect_hardware": "Conectar la cartera de hardware", - "imported": "IMPORTADO", + "connect_hardware": "Conectar la billetera de hardware", + "imported": "Importado", "remove_account_title": "Eliminación de la cuenta", "remove_account_message": "¿Está seguro de que quiere quitar esta cuenta?", "no": "No", - "yes_remove_it": "Sí, deseo quitarla" + "yes_remove_it": "Sí, deseo quitarla", + "accounts_title": "Cuentas", + "connect_account_title": "Conectar cuenta", + "connect_accounts_title": "Conectar cuentas", + "connected_accounts_title": "Cuentas conectadas", + "connect_description": "Comparta la dirección de su cuenta, saldo, actividad y permita que el sitio inicie transacciones.", + "connect_multiple_accounts": "Conectar múltiples cuentas", + "connect_more_accounts": "Conectar más cuentas", + "cancel": "Cancelar", + "connect": "Conectar", + "connect_with_count": "Conectar {{countLabel}}", + "select_all": "Seleccionar todo", + "permissions": "Permisos", + "disconnect": "Desconectar", + "disconnect_all_accounts": "Desconectar todas las cuentas" + }, + "toast": { + "connected_and_active": "conectado y activo.", + "now_active": "activo ahora.", + "revoked": "revocada.", + "revoked_all": "Todas las cuentas revocadas.", + "accounts_connected": "cuentas conectadas." }, "connect_qr_hardware": { "title": "Conectar una cartera de hardware basada en QR", diff --git a/locales/languages/fr.json b/locales/languages/fr.json index 8807cd6d4d3..6de0279678f 100644 --- a/locales/languages/fr.json +++ b/locales/languages/fr.json @@ -403,11 +403,32 @@ "create_new_account": "Créer un nouveau compte", "import_account": "Importer un compte", "connect_hardware": "Connecter un portefeuille matériel", - "imported": "IMPORTÉ", + "imported": "Importés", "remove_account_title": "Suppression du compte", "remove_account_message": "Souhaitez-vous vraiment supprimer ce compte ?", "no": "Non", - "yes_remove_it": "Oui, supprimez-le" + "yes_remove_it": "Oui, supprimez-le", + "accounts_title": "Comptes", + "connect_account_title": "Connecter le compte", + "connect_accounts_title": "Connecter les comptes", + "connected_accounts_title": "Comptes connectés", + "connect_description": "Partagez l’adresse, le solde, l’activité de votre compte et autorisez le site à effectuer des transactions.", + "connect_multiple_accounts": "Connecter plusieurs comptes", + "connect_more_accounts": "Connecter plus de comptes", + "cancel": "Annuler", + "connect": "Connecter", + "connect_with_count": "Connecter{{countLabel}}", + "select_all": "Tout sélectionner", + "permissions": "Autorisations", + "disconnect": "Déconnecter", + "disconnect_all_accounts": "Déconnecter tous les comptes" + }, + "toast": { + "connected_and_active": "connecté et actif.", + "now_active": "maintenant actif.", + "revoked": "révoqué.", + "revoked_all": "Tous les comptes ont été révoqués.", + "accounts_connected": "comptes connectés." }, "connect_qr_hardware": { "title": "Connecter un hardware wallet (portefeuille matériel) basé sur code QR", diff --git a/locales/languages/hi.json b/locales/languages/hi.json index da827b3e16c..21fed2bfcc5 100644 --- a/locales/languages/hi.json +++ b/locales/languages/hi.json @@ -400,14 +400,35 @@ "receive": "प्राप्त करें" }, "accounts": { - "create_new_account": "नया अकाउंट बनाएं", - "import_account": "एक अकाउंट इम्पोर्ट करें", + "create_new_account": "नया खाता बनाएं", + "import_account": "एक खाता इम्पोर्ट करें", "connect_hardware": "हार्डवेयर वॉलेट कनेक्ट करें", "imported": "इम्पोर्ट किया गया", "remove_account_title": "अकाउंट हटाया जाना", "remove_account_message": "क्या आप वास्तव में इस अकाउंट को हटाना चाहते हैं?", "no": "नहीं", - "yes_remove_it": "हां, इसे हटा दें" + "yes_remove_it": "हां, इसे हटा दें", + "accounts_title": "खाता", + "connect_account_title": "खाता कनेक्ट करें", + "connect_accounts_title": "खाते को कनेक्ट करें", + "connected_accounts_title": "कनेक्ट किए हुए खाते", + "connect_description": "अपना खाता पता, शेष राशि, गतिविधि साझा करें और साइट को लेन-देन शुरू करने की अनुमति दें।", + "connect_multiple_accounts": "एक से अधिक खाते को कनेक्ट करें", + "connect_more_accounts": "अधिक खाते को कनेक्ट करें", + "cancel": "रद्द करें", + "connect": "कनेक्ट करें", + "connect_with_count": "{{countLabel}} को कनेक्ट करें", + "select_all": "सभी का चयन करें", + "permissions": "अनुमतियां", + "disconnect": "डिस्कनेक्ट करें", + "disconnect_all_accounts": "सभी खाते डिस्कनेक्ट करें" + }, + "toast": { + "connected_and_active": "कनेक्ट किया हुआ और सक्रिय।", + "now_active": "अब सक्रिय है", + "revoked": "निरस्त किया हुआ।", + "revoked_all": "सभी खाते निरस्त कर दिए गए।", + "accounts_connected": "खाते कनेक्ट किए गए।" }, "connect_qr_hardware": { "title": "क्यूआर-आधारित हार्डवेयर वॉलेट के साथ कनेक्ट करें", diff --git a/locales/languages/id.json b/locales/languages/id.json index 2226e35e086..a0232c95ab4 100644 --- a/locales/languages/id.json +++ b/locales/languages/id.json @@ -402,12 +402,33 @@ "accounts": { "create_new_account": "Buat Akun Baru", "import_account": "Impor Akun", - "connect_hardware": "Hubungkan Dompet Perangkat Keras", - "imported": "DIIMPOR", + "connect_hardware": "Hubungkan dompet perangkat keras", + "imported": "Diimpor", "remove_account_title": "Penghapusan akun", "remove_account_message": "Yakin ingin menghapus akun ini?", "no": "Tidak", - "yes_remove_it": "Ya, hapus" + "yes_remove_it": "Ya, hapus", + "accounts_title": "Akun", + "connect_account_title": "Hubungkan Akun", + "connect_accounts_title": "Hubungkan Akun", + "connected_accounts_title": "Akun yang Terhubung", + "connect_description": "Bagikan alamat, saldo, aktivitas akun Anda, dan izinkan situs untuk memulai transaksi.", + "connect_multiple_accounts": "Hubungkan beberapa akun", + "connect_more_accounts": "Hubungkan lebih banyak akun", + "cancel": "Batalkan", + "connect": "Hubungkan", + "connect_with_count": "Hubungkan{{countLabel}}", + "select_all": "Pilih semua", + "permissions": "Izin", + "disconnect": "Putuskan koneksi", + "disconnect_all_accounts": "Putuskan koneksi semua akun" + }, + "toast": { + "connected_and_active": "terhubung dan aktif.", + "now_active": "sekarang aktif.", + "revoked": "dicabut.", + "revoked_all": "Semua akun dicabut.", + "accounts_connected": "akun terhubung." }, "connect_qr_hardware": { "title": "Hubungkan dompet perangkat keras berbasis QR", diff --git a/locales/languages/ja.json b/locales/languages/ja.json index 88c5c1cc791..8263c1901d7 100644 --- a/locales/languages/ja.json +++ b/locales/languages/ja.json @@ -400,14 +400,35 @@ "receive": "受け取る" }, "accounts": { - "create_new_account": "新規アカウントを作成", - "import_account": "アカウントをインポート", + "create_new_account": "新規アカウントの作成", + "import_account": "アカウントのインポート", "connect_hardware": "ハードウェアウォレットの接続", "imported": "インポートされました", "remove_account_title": "アカウントの削除", "remove_account_message": "このアカウントを削除してよろしいですか?", "no": "いいえ", - "yes_remove_it": "はい、削除します" + "yes_remove_it": "はい、削除します", + "accounts_title": "アカウント", + "connect_account_title": "アカウントの接続", + "connect_accounts_title": "アカウントの接続", + "connected_accounts_title": "接続されたアカウント", + "connect_description": "アカウントのアドレス、残高、アクティビティを共有すると、サイトがトランザクションを実行できるようになります。", + "connect_multiple_accounts": "複数アカウントを接続", + "connect_more_accounts": "他のアカウントを接続", + "cancel": "キャンセル", + "connect": "接続", + "connect_with_count": "{{countLabel}} 個を接続", + "select_all": "すべて選択", + "permissions": "許可", + "disconnect": "接続解除", + "disconnect_all_accounts": "すべてのアカウントを接続解除" + }, + "toast": { + "connected_and_active": "接続済みかつ有効です。", + "now_active": "有効になりました。", + "revoked": "取り消されました。", + "revoked_all": "すべてのアカウントが取り消されました。", + "accounts_connected": "アカウントが接続されました。" }, "connect_qr_hardware": { "title": "QR ベースのハードウェアウォレットを接続", diff --git a/locales/languages/ko.json b/locales/languages/ko.json index e3b45f01217..d4cbd40131a 100644 --- a/locales/languages/ko.json +++ b/locales/languages/ko.json @@ -407,7 +407,28 @@ "remove_account_title": "계정 제거", "remove_account_message": "해당 계정을 정말로 제거하시겠습니까?", "no": "아니오", - "yes_remove_it": "네. 제거하겠습니다" + "yes_remove_it": "네. 제거하겠습니다", + "accounts_title": "계정", + "connect_account_title": "계정 연결", + "connect_accounts_title": "계정 연결", + "connected_accounts_title": "연결된 계정", + "connect_description": "계정 주소와 잔액 및 활동을 공유하고 사이트가 결제를 진행하도록 허용하세요.", + "connect_multiple_accounts": "여러 계정 연결", + "connect_more_accounts": "더 많은 계정 연결", + "cancel": "취소", + "connect": "연결", + "connect_with_count": "{{countLabel}} 연결", + "select_all": "모두 선택", + "permissions": "권한", + "disconnect": "연결 해제", + "disconnect_all_accounts": "모든 계정 연결 해제" + }, + "toast": { + "connected_and_active": "연결되어 활성화됨.", + "now_active": "지금 활성화됨.", + "revoked": "취소됨.", + "revoked_all": "모든 계정이 취소되었습니다.", + "accounts_connected": "계정이 연결되었습니다." }, "connect_qr_hardware": { "title": "QR 코드 기반 하드웨어 지갑 연결", diff --git a/locales/languages/pt.json b/locales/languages/pt.json index 41fcb69025e..ce6747f2c54 100644 --- a/locales/languages/pt.json +++ b/locales/languages/pt.json @@ -403,11 +403,32 @@ "create_new_account": "Criar nova conta", "import_account": "Importar uma conta", "connect_hardware": "Conectar carteira de hardware", - "imported": "IMPORTADA", + "imported": "Importadas", "remove_account_title": "Remoção da conta", "remove_account_message": "Tem certeza de que deseja remover esta conta?", "no": "Não", - "yes_remove_it": "Sim, remover" + "yes_remove_it": "Sim, remover", + "accounts_title": "Contas", + "connect_account_title": "Conectar conta", + "connect_accounts_title": "Conectar contas", + "connected_accounts_title": "Contas conectadas", + "connect_description": "Compartilhe o endereço, saldo e atividades da sua conta, e permita que o site inicie transações.", + "connect_multiple_accounts": "Conectar múltiplas contas", + "connect_more_accounts": "Conectar mais contas", + "cancel": "Cancelar", + "connect": "Conectar", + "connect_with_count": "Conectar{{countLabel}}", + "select_all": "Selecionar tudo", + "permissions": "Permissões", + "disconnect": "Desconectar", + "disconnect_all_accounts": "Desconectar todas as contas" + }, + "toast": { + "connected_and_active": "conectada e ativa.", + "now_active": "agora ativa.", + "revoked": "revogada.", + "revoked_all": "Todas as contas revogadas.", + "accounts_connected": "contas conectadas." }, "connect_qr_hardware": { "title": "Conecte uma carteira de hardware baseada em QR", diff --git a/locales/languages/ru.json b/locales/languages/ru.json index 0704ff61aad..83a25cf6daf 100644 --- a/locales/languages/ru.json +++ b/locales/languages/ru.json @@ -403,11 +403,32 @@ "create_new_account": "Создать новый счет", "import_account": "Импортировать счет", "connect_hardware": "Подключить аппаратный кошелек", - "imported": "ИМПОРТИРОВАН", + "imported": "Импортирован", "remove_account_title": "Удаление счета", "remove_account_message": "Вы действительно хотите удалить этот счет?", "no": "Нет", - "yes_remove_it": "Да, удалить" + "yes_remove_it": "Да, удалить", + "accounts_title": "Счета", + "connect_account_title": "Подключить счет", + "connect_accounts_title": "Подключить счета", + "connected_accounts_title": "Подключенные счета", + "connect_description": "Делитесь адресом, балансом и активностью своего счета и разрешите сайту инициировать транзакции.", + "connect_multiple_accounts": "Подключить нескольких счетов", + "connect_more_accounts": "Подключите еще счета", + "cancel": "Отмена", + "connect": "Подключиться", + "connect_with_count": "Подключить {{countLabel}}", + "select_all": "Выбрать все", + "permissions": "Разрешения", + "disconnect": "Отключить", + "disconnect_all_accounts": "Отключить все счета" + }, + "toast": { + "connected_and_active": "подключен и активен.", + "now_active": "сейчас активен.", + "revoked": "отозван.", + "revoked_all": "Все счета отозваны.", + "accounts_connected": "счета подключены." }, "connect_qr_hardware": { "title": "Подключите аппаратный кошелек на основе QR-кодов", diff --git a/locales/languages/tl.json b/locales/languages/tl.json index a930aa392ec..e354e5e9318 100644 --- a/locales/languages/tl.json +++ b/locales/languages/tl.json @@ -400,14 +400,35 @@ "receive": "TUMANGGAP" }, "accounts": { - "create_new_account": "Gumawa ng Bagong Account", - "import_account": "Mag-import ng Account", - "connect_hardware": "Ikonekta ang Hardware Wallet", - "imported": "NA-IMPORT", + "create_new_account": "Gumawa ng bagong account", + "import_account": "Mag-import ng account", + "connect_hardware": "Ikonekta ang hardware wallet", + "imported": "Na-import", "remove_account_title": "Pagtanggal ng account", "remove_account_message": "Gusto mo ba talagang tanggalin ang account na ito?", "no": "Hindi", - "yes_remove_it": "Oo, tanggalin ito" + "yes_remove_it": "Oo, tanggalin ito", + "accounts_title": "Mga Account", + "connect_account_title": "Ikonekta ang Account", + "connect_accounts_title": "Ikonekta ang mga Account", + "connected_accounts_title": "Mga Konektadong Account", + "connect_description": "Ibahagi ang address ng iyong account, balanse, aktibidad, at payagan ang site na magsimula ng mga transaksyon.", + "connect_multiple_accounts": "Ikonekta ang maraming account", + "connect_more_accounts": "Ikonekta ang higit pang mga account", + "cancel": "Kanselahin", + "connect": "Kumonekta", + "connect_with_count": "Kumonekta{{countLabel}}", + "select_all": "Piliin lahat", + "permissions": "Mga Pahintulot", + "disconnect": "Idiskonekta", + "disconnect_all_accounts": "Idiskonekta ang lahat ng account" + }, + "toast": { + "connected_and_active": "konektado at aktibo.", + "now_active": "aktibo ngayon.", + "revoked": "binawi.", + "revoked_all": "Ang lahat ng account ay binawi.", + "accounts_connected": "mga account na konektado." }, "connect_qr_hardware": { "title": "Ikonekta ang QR-based na hardware wallet", diff --git a/locales/languages/tr.json b/locales/languages/tr.json index cefa5d5e4c0..d8ec5cfadba 100644 --- a/locales/languages/tr.json +++ b/locales/languages/tr.json @@ -400,14 +400,35 @@ "receive": "AL" }, "accounts": { - "create_new_account": "Yeni Hesap Oluştur", - "import_account": "Bir Hesabı İçe Aktar", - "connect_hardware": "Donanım Cüzdanı Bağla", - "imported": "İÇE AKTARILDI", + "create_new_account": "Yeni hesap oluştur", + "import_account": "Bir hesabı içe aktar", + "connect_hardware": "Donanım cüzdanı bağla", + "imported": "İçe aktarıldı", "remove_account_title": "Hesap kaldırma", "remove_account_message": "Bu hesabı gerçekten kaldırmak istiyor musunuz?", "no": "Hayır", - "yes_remove_it": "Evet, kaldır" + "yes_remove_it": "Evet, kaldır", + "accounts_title": "Hesaplar", + "connect_account_title": "Hesabı Bağla", + "connect_accounts_title": "Hesapları Bağla", + "connected_accounts_title": "Bağlı Hesaplar", + "connect_description": "Hesap adresinizi, bakiyenizi, faaliyetinizi paylaşın ve sitenin işlem başlatmasına izin verin.", + "connect_multiple_accounts": "Birden fazla hesabı bağla", + "connect_more_accounts": "Daha fazla hesap bağla", + "cancel": "İptal et", + "connect": "Bağla", + "connect_with_count": "{{countLabel}} Bağla", + "select_all": "Tümünü seç", + "permissions": "İzinler", + "disconnect": "Bağlantıyı kes", + "disconnect_all_accounts": "Tüm hesapların bağlantısını kes" + }, + "toast": { + "connected_and_active": "bağlandı ve aktif.", + "now_active": "şimdi aktif.", + "revoked": "iptal edildi.", + "revoked_all": "Tüm hesaplar iptal edildi.", + "accounts_connected": "hesap bağlandı." }, "connect_qr_hardware": { "title": "QR tabanlı bir donanım cüzdanı bağlayın", diff --git a/locales/languages/vi.json b/locales/languages/vi.json index b6b69d6460d..6e171243902 100644 --- a/locales/languages/vi.json +++ b/locales/languages/vi.json @@ -403,11 +403,32 @@ "create_new_account": "Tạo tài khoản mới", "import_account": "Nhập tài khoản", "connect_hardware": "Kết nối với ví cứng", - "imported": "ĐÃ NHẬP", + "imported": "Đã nhập", "remove_account_title": "Gỡ bỏ tài khoản", "remove_account_message": "Bạn có thật sự muốn gỡ bỏ tài khoản này?", "no": "Không", - "yes_remove_it": "Có, hãy gỡ bỏ nó" + "yes_remove_it": "Có, hãy gỡ bỏ nó", + "accounts_title": "Tài khoản", + "connect_account_title": "Kết nối tài khoản", + "connect_accounts_title": "Kết nối tài khoản", + "connected_accounts_title": "Tài khoản đã kết nối", + "connect_description": "Chia sẻ địa chỉ tài khoản, số dư, hoạt động của bạn và cho phép trang web bắt đầu thực hiện các giao dịch.", + "connect_multiple_accounts": "Kết nối nhiều tài khoản", + "connect_more_accounts": "Kết nối thêm tài khoản", + "cancel": "Hủy", + "connect": "Kết nối", + "connect_with_count": "Kết nối{{countLabel}}", + "select_all": "Chọn tất cả", + "permissions": "Quyền", + "disconnect": "Ngắt kết nối", + "disconnect_all_accounts": "Ngắt kết nối tất cả các tài khoản" + }, + "toast": { + "connected_and_active": "đã được kết nối và đang hoạt động.", + "now_active": "đang hoạt động.", + "revoked": "đã bị thu hồi.", + "revoked_all": "Tất cả tài khoản đã bị thu hồi.", + "accounts_connected": "tài khoản đã được kết nối." }, "connect_qr_hardware": { "title": "Kết nối với một ví cứng dựa trên mã QR", diff --git a/locales/languages/zh.json b/locales/languages/zh.json index e81a1ffe8ed..f1f3463e9cf 100644 --- a/locales/languages/zh.json +++ b/locales/languages/zh.json @@ -407,7 +407,28 @@ "remove_account_title": "删除账户", "remove_account_message": "是否确实想要删除此账户?", "no": "否", - "yes_remove_it": "是,删除它" + "yes_remove_it": "是,删除它", + "accounts_title": "账户", + "connect_account_title": "连接账户", + "connect_accounts_title": "连接账户", + "connected_accounts_title": "已连接的账户", + "connect_description": "请分享您的账户地址、余额、活动,并允许站点启动交易。", + "connect_multiple_accounts": "连接多个账户", + "connect_more_accounts": "连接更多账户", + "cancel": "取消", + "connect": "连接", + "connect_with_count": "连接{{countLabel}}", + "select_all": "全部选择", + "permissions": "权限", + "disconnect": "断开连接", + "disconnect_all_accounts": "断开所有账户的连接" + }, + "toast": { + "connected_and_active": "已连接且处于活动状态。", + "now_active": "现处于活动状态。", + "revoked": "已撤销。", + "revoked_all": "已撤销所有账户。", + "accounts_connected": "账户已连接。" }, "connect_qr_hardware": { "title": "连接基于二维码的硬件钱包", From 0e49621a4db4bdf30973b16e45b8d0378536c47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <1649425+jpcloureiro@users.noreply.github.com> Date: Tue, 20 Dec 2022 17:18:05 +0000 Subject: [PATCH 40/91] [PS] Allow Permission Request Rejection (#5405) * get permission request id on AccountConnect * refactor account connect action handlers * move USER_INTENT enum to constants folder * clenup - remove eslint ignores - remove old TODOs - add useEffect comment to address why we need the user intent state machine --- app/components/Nav/Main/RootRPCMethodsUI.js | 6 + .../Views/AccountConnect/AccountConnect.tsx | 268 ++++++++++++------ .../AccountConnect/AccountConnect.types.ts | 1 + .../AccountConnectMultiSelector.tsx | 54 +--- .../AccountConnectMultiSelector.types.ts | 5 +- .../AccountConnectSingle.tsx | 14 +- .../AccountConnectSingle.types.ts | 4 +- .../AccountConnectSingleSelector.tsx | 42 +-- .../AccountConnectSingleSelector.types.ts | 4 +- .../AccountPermissions/AccountPermissions.tsx | 89 ++++-- app/constants/permissions.ts | 11 + app/core/RPCMethods/RPCMethodMiddleware.ts | 19 +- 12 files changed, 300 insertions(+), 217 deletions(-) create mode 100644 app/constants/permissions.ts diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 3045ea4f391..32e90a87416 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -708,6 +708,11 @@ const RootRPCMethodsUI = (props) => { if (requestData.pageMeta) { setCurrentPageMeta(requestData.pageMeta); } + + const { + metadata: { id }, + } = requestData; + switch (request.type) { case ApprovalTypes.REQUEST_PERMISSIONS: if (requestData?.permissions?.eth_accounts) { @@ -715,6 +720,7 @@ const RootRPCMethodsUI = (props) => { screen: Routes.SHEET.ACCOUNT_CONNECT, params: { hostInfo: requestData, + permissionRequestId: id, }, }); } diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index a526edeb573..44c4055f195 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useContext, + useEffect, useMemo, useRef, useState, @@ -9,6 +10,7 @@ import React, { import { useSelector } from 'react-redux'; import { ImageSourcePropType } from 'react-native'; import { isEqual } from 'lodash'; +import { useNavigation } from '@react-navigation/native'; // External dependencies. import SheetBottom, { @@ -33,6 +35,7 @@ import { getUrlObj } from '../../../util/browser'; import { strings } from '../../../../locales/i18n'; import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; import { safeToChecksumAddress } from '../../../util/address'; +import USER_INTENT from '../../../constants/permissions'; // Internal dependencies. import { @@ -45,8 +48,9 @@ import AccountConnectMultiSelector from './AccountConnectMultiSelector'; const AccountConnect = (props: AccountConnectProps) => { const Engine = UntypedEngine as any; - const { hostInfo } = props.route.params; + const { hostInfo, permissionRequestId } = props.route.params; const [isLoading, setIsLoading] = useState(false); + const navigation = useNavigation(); const selectedWalletAddress = useSelector( (state: any) => state.engine.backgroundState.PreferencesController.selectedAddress, @@ -61,6 +65,9 @@ const AccountConnect = (props: AccountConnectProps) => { const { accounts, ensByAccountAddress } = useAccounts({ isLoading, }); + + const [userIntent, setUserIntent] = useState(USER_INTENT.None); + const { toastRef } = useContext(ToastContext); const accountAvatarType = useSelector((state: any) => state.settings.useBlockieIcon @@ -68,9 +75,8 @@ const AccountConnect = (props: AccountConnectProps) => { : AvatarAccountType.JazzIcon, ); const origin: string = useSelector(getActiveTabUrl, isEqual); - // TODO - Once we can pass metadata to permission system, pass origin instead of hostname into this component. const hostname = hostInfo.metadata.origin; - // const hostname = useMemo(() => new URL(origin).hostname, [origin]); + const secureIcon = useMemo( () => (getUrlObj(origin) as URL).protocol === 'https:' @@ -78,6 +84,7 @@ const AccountConnect = (props: AccountConnectProps) => { : IconName.LockSlashFilled, [origin], ); + /** * Get image url from favicon api. */ @@ -86,99 +93,176 @@ const AccountConnect = (props: AccountConnectProps) => { return { uri: iconUrl }; }, [hostname]); - const dismissSheet = useCallback( - () => sheetRef?.current?.hide?.(), - [sheetRef], + const cancelPermissionRequest = useCallback( + (requestId) => { + Engine.context.PermissionController.rejectPermissionsRequest(requestId); + }, + [Engine.context.PermissionController], ); - const dismissSheetWithCallback = useCallback( - (callback?: () => void) => sheetRef?.current?.hide?.(callback), - [sheetRef], - ); + const handleConnect = useCallback(async () => { + const selectedAccounts: SelectedAccount[] = selectedAddresses.map( + (address, index) => ({ address, lastUsed: Date.now() - index }), + ); + const request = { + ...hostInfo, + metadata: { + ...hostInfo.metadata, + origin: hostname, + }, + approvedAccounts: selectedAccounts, + }; + const connectedAccountLength = selectedAccounts.length; + const activeAddress = selectedAccounts[0].address; + const activeAccountName = getAccountNameWithENS({ + accountAddress: activeAddress, + accounts, + ensByAccountAddress, + }); - const onConnect = useCallback( - async () => { - const selectedAccounts: SelectedAccount[] = selectedAddresses.map( - (address, index) => ({ address, lastUsed: Date.now() - index }), + try { + setIsLoading(true); + await Engine.context.PermissionController.acceptPermissionsRequest( + request, ); - const request = { - ...hostInfo, - metadata: { - ...hostInfo.metadata, - origin: hostname, - }, - approvedAccounts: selectedAccounts, - }; - const connectedAccountLength = selectedAccounts.length; - const activeAddress = selectedAccounts[0].address; - const activeAccountName = getAccountNameWithENS({ + let labelOptions: ToastOptions['labelOptions'] = []; + if (connectedAccountLength > 1) { + labelOptions = [ + { label: `${connectedAccountLength} `, isBold: true }, + { + label: `${strings('toast.accounts_connected')}`, + }, + { label: `\n${activeAccountName} `, isBold: true }, + { label: strings('toast.now_active') }, + ]; + } else { + labelOptions = [ + { label: `${activeAccountName} `, isBold: true }, + { label: strings('toast.connected_and_active') }, + ]; + } + toastRef?.current?.showToast({ + variant: ToastVariants.Account, + labelOptions, accountAddress: activeAddress, - accounts, - ensByAccountAddress, + accountAvatarType, }); + } catch (e: any) { + Logger.error(e, 'Error while trying to connect to a dApp.'); + } finally { + setIsLoading(false); + } + }, [ + selectedAddresses, + hostInfo, + accounts, + ensByAccountAddress, + hostname, + accountAvatarType, + Engine.context.PermissionController, + toastRef, + ]); + const handleCreateAccount = useCallback( + async (isMultiSelect?: boolean) => { + const { KeyringController } = Engine.context; try { setIsLoading(true); - await Engine.context.PermissionController.acceptPermissionsRequest( - request, + const { addedAccountAddress } = await KeyringController.addNewAccount(); + const checksummedAddress = safeToChecksumAddress( + addedAccountAddress, + ) as string; + !isMultiSelect && setSelectedAddresses([checksummedAddress]); + AnalyticsV2.trackEvent( + ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT, + {}, ); - let labelOptions: ToastOptions['labelOptions'] = []; - if (connectedAccountLength > 1) { - labelOptions = [ - { label: `${connectedAccountLength} `, isBold: true }, - { - label: `${strings('toast.accounts_connected')}`, - }, - { label: `\n${activeAccountName} `, isBold: true }, - { label: strings('toast.now_active') }, - ]; - } else { - labelOptions = [ - { label: `${activeAccountName} `, isBold: true }, - { label: strings('toast.connected_and_active') }, - ]; - } - toastRef?.current?.showToast({ - variant: ToastVariants.Account, - labelOptions, - accountAddress: activeAddress, - accountAvatarType, - }); } catch (e: any) { - Logger.error(e, 'Error while trying to connect to a dApp.'); + Logger.error(e, 'error while trying to add a new account'); } finally { setIsLoading(false); - dismissSheet(); } }, - /* eslint-disable-next-line */ - [ - selectedAddresses, - hostInfo, - accounts, - ensByAccountAddress, - hostname, - accountAvatarType, - ], + [Engine.context], ); - const onCreateAccount = useCallback(async (isMultiSelect?: boolean) => { - const { KeyringController } = Engine.context; - try { - setIsLoading(true); - const { addedAccountAddress } = await KeyringController.addNewAccount(); - const checksummedAddress = safeToChecksumAddress( - addedAccountAddress, - ) as string; - !isMultiSelect && setSelectedAddresses([checksummedAddress]); - AnalyticsV2.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); - } catch (e: any) { - Logger.error(e, 'error while trying to add a new account'); - } finally { - setIsLoading(false); - } - /* eslint-disable-next-line */ - }, []); + const hideSheet = (callback?: () => void) => + sheetRef?.current?.hide?.(callback); + + /** + * User intent is set on AccountConnectSingle, + * AccountConnectSingleSelector & AccountConnectMultiSelector. + * + * We need to know where the user clicks to decide what + * should happen to the Permission Request Promise. + * We then trigger the corresponding side effects & + * control the Bottom Sheet visibility. + */ + useEffect(() => { + if (userIntent === USER_INTENT.None) return; + + const handleUserActions = (action: USER_INTENT) => { + switch (action) { + case USER_INTENT.Confirm: { + handleConnect(); + hideSheet(); + break; + } + case USER_INTENT.Create: { + handleCreateAccount(); + break; + } + case USER_INTENT.CreateMultiple: { + handleCreateAccount(true); + break; + } + case USER_INTENT.Cancel: { + hideSheet(() => cancelPermissionRequest(permissionRequestId)); + break; + } + case USER_INTENT.Import: { + hideSheet(() => { + navigation.navigate('ImportPrivateKeyView'); + // TODO: Confirm if this is where we want to track importing an account or within ImportPrivateKeyView screen. + AnalyticsV2.trackEvent( + ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + {}, + ); + }); + break; + } + case USER_INTENT.ConnectHW: { + hideSheet(() => { + navigation.navigate('ConnectQRHardwareFlow'); + // TODO: Confirm if this is where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen. + AnalyticsV2.trackEvent( + AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, + {}, + ); + }); + break; + } + } + }; + + handleUserActions(userIntent); + + setUserIntent(USER_INTENT.None); + }, [ + navigation, + userIntent, + sheetRef, + cancelPermissionRequest, + permissionRequestId, + handleCreateAccount, + handleConnect, + ]); + + const handleSheetDismiss = () => { + if (!permissionRequestId || userIntent !== USER_INTENT.None) return; + + cancelPermissionRequest(permissionRequestId); + }; const renderSingleConnectScreen = useCallback(() => { const selectedAddress = selectedAddresses[0]; @@ -199,8 +283,7 @@ const AccountConnect = (props: AccountConnectProps) => { { accounts, ensByAccountAddress, selectedAddresses, - onConnect, isLoading, setScreen, - dismissSheet, setSelectedAddresses, favicon, hostname, secureIcon, + setUserIntent, ]); const renderSingleConnectSelectorScreen = useCallback( @@ -231,8 +313,7 @@ const AccountConnect = (props: AccountConnectProps) => { onSetSelectedAddresses={setSelectedAddresses} selectedAddresses={selectedAddresses} isLoading={isLoading} - onCreateAccount={() => onCreateAccount()} - onDismissSheetWithCallback={dismissSheetWithCallback} + onUserAction={setUserIntent} /> ), [ @@ -240,8 +321,7 @@ const AccountConnect = (props: AccountConnectProps) => { ensByAccountAddress, selectedAddresses, isLoading, - onCreateAccount, - dismissSheetWithCallback, + setUserIntent, setSelectedAddresses, setScreen, ], @@ -255,12 +335,10 @@ const AccountConnect = (props: AccountConnectProps) => { selectedAddresses={selectedAddresses} onSelectAddress={setSelectedAddresses} isLoading={isLoading} - onDismissSheetWithCallback={dismissSheetWithCallback} - onConnect={onConnect} - onCreateAccount={() => onCreateAccount(true)} favicon={favicon} hostname={hostname} secureIcon={secureIcon} + onUserAction={setUserIntent} /> ), [ @@ -268,10 +346,8 @@ const AccountConnect = (props: AccountConnectProps) => { ensByAccountAddress, selectedAddresses, setSelectedAddresses, - onConnect, isLoading, - onCreateAccount, - dismissSheetWithCallback, + setUserIntent, favicon, hostname, secureIcon, @@ -295,7 +371,11 @@ const AccountConnect = (props: AccountConnectProps) => { ]); return ( - + {renderConnectScreens()} ); diff --git a/app/components/Views/AccountConnect/AccountConnect.types.ts b/app/components/Views/AccountConnect/AccountConnect.types.ts index 9e98bffc0d8..4d990f43445 100644 --- a/app/components/Views/AccountConnect/AccountConnect.types.ts +++ b/app/components/Views/AccountConnect/AccountConnect.types.ts @@ -19,6 +19,7 @@ export interface AccountConnectProps { hostInfo: { metadata: { origin: string }; }; + permissionRequestId: string; }; }; } diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx index e9908f0d2fb..3c848cf1d93 100644 --- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx @@ -1,7 +1,6 @@ // Third party dependencies. import React, { useCallback } from 'react'; import { View } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; // External dependencies. import SheetActions from '../../../../component-library/components-temp/SheetActions'; @@ -17,13 +16,12 @@ import Button, { } from '../../../../component-library/components/Buttons/Button'; import AccountSelectorList from '../../../UI/AccountSelectorList'; import ButtonLink from '../../../../component-library/components/Buttons/Button/variants/ButtonLink'; -import AnalyticsV2 from '../../../../util/analyticsV2'; -import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics'; // Internal dependencies. import styleSheet from './AccountConnectMultiSelector.styles'; import { AccountConnectMultiSelectorProps } from './AccountConnectMultiSelector.types'; import { ButtonSecondaryVariants } from '../../../../component-library/components/Buttons/Button/variants/ButtonSecondary'; +import USER_INTENT from '../../../../constants/permissions'; const AccountConnectMultiSelector = ({ accounts, @@ -31,35 +29,12 @@ const AccountConnectMultiSelector = ({ selectedAddresses, onSelectAddress, isLoading, - onDismissSheetWithCallback, - onConnect, - onCreateAccount, + onUserAction, hostname, favicon, secureIcon, }: AccountConnectMultiSelectorProps) => { const { styles } = useStyles(styleSheet, {}); - const navigation = useNavigation(); - - const onOpenImportAccount = useCallback(() => { - onDismissSheetWithCallback(() => { - navigation.navigate('ImportPrivateKeyView'); - // Is this where we want to track importing an account or within ImportPrivateKeyView screen? - AnalyticsV2.trackEvent( - ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, - ); - }); - }, [navigation, onDismissSheetWithCallback]); - - const onOpenConnectHardwareWallet = useCallback(() => { - onDismissSheetWithCallback(() => { - navigation.navigate('ConnectQRHardwareFlow'); - // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? - AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, - ); - }); - }, [navigation, onDismissSheetWithCallback]); const renderSheetActions = useCallback( () => ( @@ -67,28 +42,23 @@ const AccountConnectMultiSelector = ({ actions={[ { label: strings('accounts.create_new_account'), - onPress: onCreateAccount, + onPress: () => onUserAction(USER_INTENT.CreateMultiple), isLoading, }, { label: strings('accounts.import_account'), - onPress: onOpenImportAccount, + onPress: () => onUserAction(USER_INTENT.Import), disabled: isLoading, }, { label: strings('accounts.connect_hardware'), - onPress: onOpenConnectHardwareWallet, + onPress: () => onUserAction(USER_INTENT.ConnectHW), disabled: isLoading, }, ]} /> ), - [ - isLoading, - onCreateAccount, - onOpenImportAccount, - onOpenConnectHardwareWallet, - ], + [isLoading, onUserAction], ); const renderSelectAllButton = useCallback( @@ -141,7 +111,7 @@ const AccountConnectMultiSelector = ({ variant={ButtonVariants.Secondary} buttonSecondaryVariants={ButtonSecondaryVariants.Normal} label={strings('accounts.cancel')} - onPress={() => onDismissSheetWithCallback()} + onPress={() => onUserAction(USER_INTENT.Cancel)} size={ButtonSize.Lg} style={styles.button} /> @@ -154,7 +124,7 @@ const AccountConnectMultiSelector = ({ ? ` (${selectedAddresses.length})` : '', })} - onPress={onConnect} + onPress={() => onUserAction(USER_INTENT.Confirm)} size={ButtonSize.Lg} style={{ ...styles.button, @@ -164,13 +134,7 @@ const AccountConnectMultiSelector = ({ /> ); - }, [ - onConnect, - isLoading, - selectedAddresses, - onDismissSheetWithCallback, - styles, - ]); + }, [isLoading, onUserAction, selectedAddresses, styles]); const areAllAccountsSelected = accounts .map(({ address }) => address) diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts index 1b4f25c2612..a8015695c46 100644 --- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts @@ -4,6 +4,7 @@ import { ImageSourcePropType } from 'react-native'; // External dependencies. import { UseAccounts } from '../../../hooks/useAccounts'; import { IconName } from '../../../../component-library/components/Icon'; +import USER_INTENT from '../../../../constants/permissions'; /** * AccountConnectMultiSelector props. @@ -12,9 +13,7 @@ export interface AccountConnectMultiSelectorProps extends UseAccounts { selectedAddresses: string[]; onSelectAddress: (addresses: string[]) => void; isLoading?: boolean; - onDismissSheetWithCallback: (callback?: () => void) => void; - onCreateAccount: () => void; - onConnect: () => void; + onUserAction: React.Dispatch>; hostname: string; favicon: ImageSourcePropType; secureIcon: IconName; diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx index 224a7c9878b..685e72b7b4d 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx @@ -29,13 +29,13 @@ import { AccountConnectScreens } from '../AccountConnect.types'; // Internal dependencies. import { AccountConnectSingleProps } from './AccountConnectSingle.types'; import styleSheet from './AccountConnectSingle.styles'; +import USER_INTENT from '../../../../constants/permissions'; const AccountConnectSingle = ({ defaultSelectedAccount, onSetScreen, onSetSelectedAddresses, - onConnect, - onDismissSheet, + onUserAction, isLoading, favicon, hostname, @@ -88,7 +88,9 @@ const AccountConnectSingle = ({ variant={ButtonVariants.Secondary} buttonSecondaryVariants={ButtonSecondaryVariants.Normal} label={strings('accounts.cancel')} - onPress={onDismissSheet} + onPress={() => { + onUserAction(USER_INTENT.Cancel); + }} size={ButtonSize.Lg} style={styles.button} /> @@ -97,13 +99,15 @@ const AccountConnectSingle = ({ variant={ButtonVariants.Primary} buttonPrimaryVariants={ButtonPrimaryVariants.Normal} label={strings('accounts.connect')} - onPress={onConnect} + onPress={() => { + onUserAction(USER_INTENT.Confirm); + }} size={ButtonSize.Lg} style={styles.button} /> ), - [onDismissSheet, onConnect, isLoading, styles], + [onUserAction, isLoading, styles], ); const renderSelectedAccount = useCallback(() => { diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts index 96c3239443c..8f1f294b2f6 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts @@ -5,15 +5,15 @@ import { ImageSourcePropType } from 'react-native'; import { AccountConnectScreens } from '../AccountConnect.types'; import { Account } from '../../../hooks/useAccounts'; import { IconName } from '../../../../component-library/components/Icon'; +import USER_INTENT from '../../../../constants/permissions'; /** * AccountConnectSingle props. */ export interface AccountConnectSingleProps { defaultSelectedAccount: Account | undefined; - onConnect: () => void; isLoading?: boolean; - onDismissSheet: () => void; + onUserAction: React.Dispatch>; onSetScreen: (screen: AccountConnectScreens) => void; onSetSelectedAddresses: (addresses: string[]) => void; hostname: string; diff --git a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx index 10718019e8e..fdc06822918 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx +++ b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx @@ -1,33 +1,28 @@ // Third party dependencies. import React, { useCallback } from 'react'; import { View } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; // External dependencies. import SheetActions from '../../../../component-library/components-temp/SheetActions'; import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader'; import AccountSelectorList from '../../../../components/UI/AccountSelectorList'; import { strings } from '../../../../../locales/i18n'; -import AnalyticsV2 from '../../../../util/analyticsV2'; -import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics'; import { AccountConnectScreens } from '../AccountConnect.types'; // Internal dependencies. import { AccountConnectSingleSelectorProps } from './AccountConnectSingleSelector.types'; import styles from './AccountConnectSingleSelector.styles'; +import USER_INTENT from '../../../../constants/permissions'; const AccountConnectSingleSelector = ({ accounts, ensByAccountAddress, selectedAddresses, isLoading, - onCreateAccount, - onDismissSheetWithCallback, onSetScreen, onSetSelectedAddresses, + onUserAction, }: AccountConnectSingleSelectorProps) => { - const navigation = useNavigation(); - const onBack = useCallback( () => onSetScreen(AccountConnectScreens.SingleConnect), [onSetScreen], @@ -41,26 +36,6 @@ const AccountConnectSingleSelector = ({ [onSetScreen, onSetSelectedAddresses], ); - const onOpenImportAccount = useCallback(() => { - onDismissSheetWithCallback(() => { - navigation.navigate('ImportPrivateKeyView'); - // Is this where we want to track importing an account or within ImportPrivateKeyView screen? - AnalyticsV2.trackEvent( - ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, - ); - }); - }, [navigation, onDismissSheetWithCallback]); - - const onOpenConnectHardwareWallet = useCallback(() => { - onDismissSheetWithCallback(() => { - navigation.navigate('ConnectQRHardwareFlow'); - // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? - AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, - ); - }); - }, [navigation, onDismissSheetWithCallback]); - const renderSheetActions = useCallback( () => ( @@ -68,29 +43,24 @@ const AccountConnectSingleSelector = ({ actions={[ { label: strings('accounts.create_new_account'), - onPress: onCreateAccount, + onPress: () => onUserAction(USER_INTENT.Create), isLoading, }, { label: strings('accounts.import_account'), - onPress: onOpenImportAccount, + onPress: () => onUserAction(USER_INTENT.Import), disabled: isLoading, }, { label: strings('accounts.connect_hardware'), - onPress: onOpenConnectHardwareWallet, + onPress: () => onUserAction(USER_INTENT.ConnectHW), disabled: isLoading, }, ]} /> ), - [ - isLoading, - onCreateAccount, - onOpenImportAccount, - onOpenConnectHardwareWallet, - ], + [isLoading, onUserAction], ); return ( diff --git a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts index 1fbaad3e91c..a07edeb7466 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts +++ b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts @@ -1,5 +1,6 @@ // External dependencies. import { UseAccounts } from '../../../hooks/useAccounts'; +import USER_INTENT from '../../../../constants/permissions'; import { AccountConnectScreens } from '../AccountConnect.types'; /** @@ -8,8 +9,7 @@ import { AccountConnectScreens } from '../AccountConnect.types'; export interface AccountConnectSingleSelectorProps extends UseAccounts { selectedAddresses: string[]; isLoading?: boolean; - onCreateAccount: () => void; onSetScreen: (screen: AccountConnectScreens) => void; onSetSelectedAddresses: (addresses: string[]) => void; - onDismissSheetWithCallback: (callback?: () => void) => void; + onUserAction: React.Dispatch>; } diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index a8c54facc2b..c98b52acd92 100644 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useContext, + useEffect, useMemo, useRef, useState, @@ -9,6 +10,7 @@ import React, { import { useSelector } from 'react-redux'; import { ImageSourcePropType } from 'react-native'; import { isEqual } from 'lodash'; +import { useNavigation } from '@react-navigation/native'; // External dependencies. import SheetBottom, { @@ -43,8 +45,10 @@ import { } from './AccountPermissions.types'; import AccountPermissionsConnected from './AccountPermissionsConnected'; import AccountPermissionsRevoke from './AccountPermissionsRevoke'; +import USER_INTENT from '../../../constants/permissions'; const AccountPermissions = (props: AccountPermissionsProps) => { + const navigation = useNavigation(); const Engine = UntypedEngine as any; const { hostInfo: { @@ -92,12 +96,9 @@ const AccountPermissions = (props: AccountPermissionsProps) => { }); const activeAddress: string = permittedAccountsByHostname[0]; - const dismissSheet = useCallback( - () => sheetRef?.current?.hide?.(), - [sheetRef], - ); + const [userIntent, setUserIntent] = useState(USER_INTENT.None); - const dismissSheetWithCallback = useCallback( + const hideSheet = useCallback( (callback?: () => void) => sheetRef?.current?.hide?.(callback), [sheetRef], ); @@ -122,7 +123,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { return accountsByPermittedStatus; }, [accounts, permittedAccountsByHostname]); - const onCreateAccount = useCallback( + const handleCreateAccount = useCallback( async () => { const { KeyringController } = Engine.context; try { @@ -139,7 +140,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { [setIsLoading], ); - const onConnect = useCallback(async () => { + const handleConnect = useCallback(async () => { try { setIsLoading(true); const newActiveAddress = await addPermittedAccounts( @@ -178,26 +179,80 @@ const AccountPermissions = (props: AccountPermissionsProps) => { Logger.error(e, 'Error while trying to connect to a dApp.'); } finally { setIsLoading(false); - dismissSheetWithCallback(); } }, [ selectedAddresses, accounts, setIsLoading, - dismissSheetWithCallback, hostname, ensByAccountAddress, toastRef, accountAvatarType, ]); + useEffect(() => { + if (userIntent === USER_INTENT.None) return; + + const handleUserActions = (action: USER_INTENT) => { + switch (action) { + case USER_INTENT.Confirm: { + handleConnect(); + hideSheet(); + break; + } + case USER_INTENT.Create: + case USER_INTENT.CreateMultiple: { + handleCreateAccount(); + break; + } + case USER_INTENT.Cancel: { + hideSheet(); + break; + } + case USER_INTENT.Import: { + hideSheet(() => { + navigation.navigate('ImportPrivateKeyView'); + // Is this where we want to track importing an account or within ImportPrivateKeyView screen? + AnalyticsV2.trackEvent( + ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + {}, + ); + }); + break; + } + case USER_INTENT.ConnectHW: { + hideSheet(() => { + navigation.navigate('ConnectQRHardwareFlow'); + // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? + AnalyticsV2.trackEvent( + AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, + {}, + ); + }); + break; + } + } + }; + + handleUserActions(userIntent); + + setUserIntent(USER_INTENT.None); + }, [ + navigation, + userIntent, + sheetRef, + hideSheet, + handleCreateAccount, + handleConnect, + ]); + const renderConnectedScreen = useCallback( () => ( { accountsFilteredByPermissions, setSelectedAddresses, setPermissionsScreen, - dismissSheet, + hideSheet, favicon, hostname, secureIcon, @@ -230,9 +285,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { selectedAddresses={selectedAddresses} onSelectAddress={setSelectedAddresses} isLoading={isLoading} - onDismissSheetWithCallback={dismissSheetWithCallback} - onConnect={onConnect} - onCreateAccount={onCreateAccount} + onUserAction={setUserIntent} favicon={favicon} hostname={hostname} secureIcon={secureIcon} @@ -243,9 +296,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { selectedAddresses, isLoading, accountsFilteredByPermissions, - dismissSheetWithCallback, - onConnect, - onCreateAccount, + setUserIntent, favicon, hostname, secureIcon, @@ -260,7 +311,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { ensByAccountAddress={ensByAccountAddress} permittedAddresses={permittedAccountsByHostname} isLoading={isLoading} - onDismissSheet={dismissSheet} + onDismissSheet={hideSheet} favicon={favicon} hostname={hostname} secureIcon={secureIcon} @@ -273,7 +324,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { permittedAccountsByHostname, accountsFilteredByPermissions, setPermissionsScreen, - dismissSheet, + hideSheet, favicon, hostname, secureIcon, diff --git a/app/constants/permissions.ts b/app/constants/permissions.ts new file mode 100644 index 00000000000..8f711e7c8bb --- /dev/null +++ b/app/constants/permissions.ts @@ -0,0 +1,11 @@ +enum USER_INTENT { + None, + Create, + CreateMultiple, + Confirm, + Cancel, + Import, + ConnectHW, +} + +export default USER_INTENT; diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts index 976887f9f9d..0f2e6b300b3 100644 --- a/app/core/RPCMethods/RPCMethodMiddleware.ts +++ b/app/core/RPCMethods/RPCMethodMiddleware.ts @@ -295,23 +295,20 @@ export const getRpcMethodMiddleware = ({ } else { try { checkTabActive(); - await Engine.context.ApprovalController.clear( - ethErrors.provider.userRejectedRequest(), - ); + await Engine.context.ApprovalController.clear(); await Engine.context.PermissionController.requestPermissions( - { - // origin: url.current, - origin: hostname, - }, + { origin: hostname }, { eth_accounts: {} }, { id: random() }, ); const acc = await getPermittedAccounts(hostname); res.result = acc; - } catch (e) { - throw ethErrors.provider.userRejectedRequest( - 'User denied account authorization.', - ); + } catch (error) { + if (error) { + throw ethErrors.provider.userRejectedRequest( + 'User denied account authorization.', + ); + } } } } From 0dcd780327bba96ef089b39c9e23d6e1d529eec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Tue, 20 Dec 2022 17:50:10 +0000 Subject: [PATCH 41/91] (RootRPCMethodsUI): relocate requestData destruct on Permission System we use a Permission Request ID that is only relevant for the Permission Controller and not the other Approvals. Destructuring requestData to get said id should only happen on REQUEST_PERMISSION Approval Type. --- app/components/Nav/Main/RootRPCMethodsUI.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 32e90a87416..d76263b1a5c 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -709,13 +709,13 @@ const RootRPCMethodsUI = (props) => { setCurrentPageMeta(requestData.pageMeta); } - const { - metadata: { id }, - } = requestData; - switch (request.type) { case ApprovalTypes.REQUEST_PERMISSIONS: if (requestData?.permissions?.eth_accounts) { + const { + metadata: { id }, + } = requestData; + props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.ACCOUNT_CONNECT, params: { From 245764ac49e1d3754f1c41ef259bac2322335b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= <1649425+jpcloureiro@users.noreply.github.com> Date: Wed, 21 Dec 2022 19:00:02 +0000 Subject: [PATCH 42/91] move backup wallet reminder popup (#5402) so it does not block the navigation bottom bar --- app/components/UI/BackupAlert/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/UI/BackupAlert/index.js b/app/components/UI/BackupAlert/index.js index 25c58a7f8e7..db3160d2e69 100644 --- a/app/components/UI/BackupAlert/index.js +++ b/app/components/UI/BackupAlert/index.js @@ -62,10 +62,10 @@ const createStyles = (colors) => flexDirection: 'row', }, modalViewInBrowserView: { - bottom: Device.isIphoneX() ? 90 : 80, + bottom: Device.isIphoneX() ? 180 : 170, }, modalViewNotInBrowserView: { - bottom: Device.isIphoneX() ? 20 : 10, + bottom: Device.isIphoneX() ? 120 : 110, }, buttonsWrapper: { flexDirection: 'row-reverse', From 2015c93eb444ab254cdda974811673fefee67b9e Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Mon, 9 Jan 2023 17:17:24 -0800 Subject: [PATCH 43/91] Fix bug report 12 & 3 --- .../Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts | 2 +- app/component-library/components/Toast/Toast.tsx | 3 ++- app/component-library/components/Toast/Toast.types.ts | 1 + app/components/Nav/Main/index.js | 1 + app/components/UI/NetworkList/index.js | 6 +----- app/components/Views/AccountConnect/AccountConnect.tsx | 2 ++ 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts b/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts index 2de5532a5af..9bf206707d8 100644 --- a/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts +++ b/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts @@ -32,7 +32,7 @@ const styleSheet = (params: { : {}; return StyleSheet.create({ base: Object.assign(baseStyle, style) as ViewStyle, - label: size === AvatarSize.Xs ? { lineHeight: 16 } : {}, + label: size === AvatarSize.Xs ? { lineHeight: 16 } : { bottom: 2 }, image: { flex: 1, height: undefined, diff --git a/app/component-library/components/Toast/Toast.tsx b/app/component-library/components/Toast/Toast.tsx index 13e45ab9a19..64df38ba4db 100644 --- a/app/component-library/components/Toast/Toast.tsx +++ b/app/component-library/components/Toast/Toast.tsx @@ -146,10 +146,11 @@ const Toast = forwardRef((_, ref: React.ForwardedRef) => { ); } case ToastVariants.Network: { - const { networkImageSource } = toastOptions; + const { networkImageSource, networkName } = toastOptions; return ( { }, { label: strings('toast.now_active') }, ], + networkName, networkImageSource: networkImage, }); } diff --git a/app/components/UI/NetworkList/index.js b/app/components/UI/NetworkList/index.js index 9918407e43d..8618262a0e7 100644 --- a/app/components/UI/NetworkList/index.js +++ b/app/components/UI/NetworkList/index.js @@ -271,11 +271,7 @@ export class NetworkList extends PureComponent { (image ? ( ) : ( - + ))} {!isCustomRpc && (image ? ( diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 44c4055f195..7b4e0caae47 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -222,6 +222,7 @@ const AccountConnect = (props: AccountConnectProps) => { } case USER_INTENT.Import: { hideSheet(() => { + cancelPermissionRequest(permissionRequestId); navigation.navigate('ImportPrivateKeyView'); // TODO: Confirm if this is where we want to track importing an account or within ImportPrivateKeyView screen. AnalyticsV2.trackEvent( @@ -233,6 +234,7 @@ const AccountConnect = (props: AccountConnectProps) => { } case USER_INTENT.ConnectHW: { hideSheet(() => { + cancelPermissionRequest(permissionRequestId); navigation.navigate('ConnectQRHardwareFlow'); // TODO: Confirm if this is where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen. AnalyticsV2.trackEvent( From 32c93ba0722a72940f38eaa88c24f17352da0485 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Mon, 9 Jan 2023 20:32:02 -0800 Subject: [PATCH 44/91] Use MetaMetrics and update snapshots --- .../__snapshots__/GetStarted.test.tsx.snap | 6 +++-- .../__snapshots__/Regions.test.tsx.snap | 15 ++++++++---- app/components/UI/NetworkList/index.js | 1 - .../Views/AccountConnect/AccountConnect.tsx | 8 +++---- .../AccountPermissions/AccountPermissions.tsx | 11 +++++---- .../Views/AccountSelector/AccountSelector.tsx | 10 ++++---- .../Amount/__snapshots__/index.test.tsx.snap | 24 ++++++++++++------- 7 files changed, 47 insertions(+), 28 deletions(-) diff --git a/app/components/UI/FiatOnRampAggregator/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap b/app/components/UI/FiatOnRampAggregator/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap index 7c76bead989..430fd80a11e 100644 --- a/app/components/UI/FiatOnRampAggregator/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap +++ b/app/components/UI/FiatOnRampAggregator/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap @@ -194,7 +194,8 @@ exports[`GetStarted renders correctly 1`] = ` } > diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 7b4e0caae47..27cb15b3eff 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -20,7 +20,7 @@ import UntypedEngine from '../../../core/Engine'; import { isDefaultAccountName } from '../../../util/ENSUtils'; import Logger from '../../../util/Logger'; import AnalyticsV2 from '../../../util/analyticsV2'; -import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; +import { MetaMetricsEvents } from '../../../core/Analytics'; import { SelectedAccount } from '../../../components/UI/AccountSelectorList/AccountSelectorList.types'; import { ToastContext, @@ -174,7 +174,7 @@ const AccountConnect = (props: AccountConnectProps) => { ) as string; !isMultiSelect && setSelectedAddresses([checksummedAddress]); AnalyticsV2.trackEvent( - ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT, + MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, {}, ); } catch (e: any) { @@ -226,7 +226,7 @@ const AccountConnect = (props: AccountConnectProps) => { navigation.navigate('ImportPrivateKeyView'); // TODO: Confirm if this is where we want to track importing an account or within ImportPrivateKeyView screen. AnalyticsV2.trackEvent( - ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, {}, ); }); @@ -238,7 +238,7 @@ const AccountConnect = (props: AccountConnectProps) => { navigation.navigate('ConnectQRHardwareFlow'); // TODO: Confirm if this is where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen. AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, + MetaMetricsEvents.CONNECT_HARDWARE_WALLET, {}, ); }); diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index c98b52acd92..1212b359529 100644 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -29,7 +29,7 @@ import { } from '../../../component-library/components/Toast'; import { ToastOptions } from '../../../component-library/components/Toast/Toast.types'; import AnalyticsV2 from '../../../util/analyticsV2'; -import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; +import { MetaMetricsEvents } from '../../../core/Analytics'; import { useAccounts, Account } from '../../hooks/useAccounts'; import getAccountNameWithENS from '../../../util/accounts'; import { IconName } from '../../../component-library/components/Icon'; @@ -129,7 +129,10 @@ const AccountPermissions = (props: AccountPermissionsProps) => { try { setIsLoading(true); await KeyringController.addNewAccount(); - AnalyticsV2.trackEvent(ANALYTICS_EVENT_OPTS.ACCOUNTS_ADDED_NEW_ACCOUNT); + AnalyticsV2.trackEvent( + MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, + {}, + ); } catch (e: any) { Logger.error(e, 'Error while trying to add a new account.'); } finally { @@ -214,7 +217,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { navigation.navigate('ImportPrivateKeyView'); // Is this where we want to track importing an account or within ImportPrivateKeyView screen? AnalyticsV2.trackEvent( - ANALYTICS_EVENT_OPTS.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, {}, ); }); @@ -225,7 +228,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { navigation.navigate('ConnectQRHardwareFlow'); // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.CONNECT_HARDWARE_WALLET, + MetaMetricsEvents.CONNECT_HARDWARE_WALLET, {}, ); }); diff --git a/app/components/Views/AccountSelector/AccountSelector.tsx b/app/components/Views/AccountSelector/AccountSelector.tsx index 3c459801325..281844970ba 100644 --- a/app/components/Views/AccountSelector/AccountSelector.tsx +++ b/app/components/Views/AccountSelector/AccountSelector.tsx @@ -13,7 +13,6 @@ import SheetHeader from '../../../component-library/components/Sheet/SheetHeader import UntypedEngine from '../../../core/Engine'; import Logger from '../../../util/Logger'; import AnalyticsV2 from '../../../util/analyticsV2'; -import Analytics from '../../../core/Analytics/Analytics'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { strings } from '../../../../locales/i18n'; import { useAccounts } from '../../hooks/useAccounts'; @@ -65,7 +64,7 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { setIsLoading(true); const { addedAccountAddress } = await KeyringController.addNewAccount(); PreferencesController.setSelectedAddress(addedAccountAddress); - Analytics.trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT); + AnalyticsV2.trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, {}); } catch (e: any) { Logger.error(e, 'error while trying to add a new account'); } finally { @@ -79,7 +78,10 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { sheetRef.current?.hide(() => { navigation.navigate('ImportPrivateKeyView'); // Is this where we want to track importing an account or within ImportPrivateKeyView screen? - Analytics.trackEvent(MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT); + AnalyticsV2.trackEvent( + MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + {}, + ); }); onOpenImportAccount?.(); }, [onOpenImportAccount, navigation]); @@ -88,7 +90,7 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { sheetRef.current?.hide(() => { navigation.navigate('ConnectQRHardwareFlow'); // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? - Analytics.trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET); + AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET, {}); }); onOpenConnectHardwareWallet?.(); }, [onOpenConnectHardwareWallet, navigation]); diff --git a/app/components/Views/SendFlow/Amount/__snapshots__/index.test.tsx.snap b/app/components/Views/SendFlow/Amount/__snapshots__/index.test.tsx.snap index 3035009eb02..e84c2fb8245 100644 --- a/app/components/Views/SendFlow/Amount/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/SendFlow/Amount/__snapshots__/index.test.tsx.snap @@ -599,7 +599,8 @@ exports[`Amount should convert ERC-20 token value to USD 1`] = ` } > Date: Tue, 10 Jan 2023 15:22:09 +0000 Subject: [PATCH 45/91] (Loader): add color prop --- app/component-library/components-temp/Loader/Loader.tsx | 6 ++++-- .../components-temp/Loader/Loader.types.ts | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/component-library/components-temp/Loader/Loader.tsx b/app/component-library/components-temp/Loader/Loader.tsx index 4ce1c711cd4..30fdf5079d4 100644 --- a/app/component-library/components-temp/Loader/Loader.tsx +++ b/app/component-library/components-temp/Loader/Loader.tsx @@ -9,13 +9,15 @@ import { useStyles } from '../../hooks'; import styleSheet from './Loader.styles'; import { LoaderProps } from './Loader.types'; -const Loader = ({ size = 'large' }: LoaderProps) => { +const Loader = ({ size = 'large', color }: LoaderProps) => { const { styles, theme } = useStyles(styleSheet, {}); const { colors } = theme; + const indicatorColor = color ?? colors.primary.default; + return ( - + ); }; diff --git a/app/component-library/components-temp/Loader/Loader.types.ts b/app/component-library/components-temp/Loader/Loader.types.ts index 8cf3fcd2635..a1bb4c0149c 100644 --- a/app/component-library/components-temp/Loader/Loader.types.ts +++ b/app/component-library/components-temp/Loader/Loader.types.ts @@ -1,5 +1,5 @@ // Third party dependencies. -import { ActivityIndicatorProps } from 'react-native'; +import { ActivityIndicatorProps, ColorValue } from 'react-native'; /** * Loader props. @@ -9,4 +9,8 @@ export interface LoaderProps { * Activity indicator size. */ size?: ActivityIndicatorProps['size']; + /** + * Activity indicator color. + */ + color?: ColorValue; } From 9367b185743761150f3635ff0ec244e46c0846b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Tue, 10 Jan 2023 15:32:11 +0000 Subject: [PATCH 46/91] remove inline styles --- .../components-temp/SheetActions/SheetActions.tsx | 7 +++++-- .../UI/AccountSelectorList/AccountSelectorList.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/component-library/components-temp/SheetActions/SheetActions.tsx b/app/component-library/components-temp/SheetActions/SheetActions.tsx index b6d778a824b..20ab5075a67 100644 --- a/app/component-library/components-temp/SheetActions/SheetActions.tsx +++ b/app/component-library/components-temp/SheetActions/SheetActions.tsx @@ -24,6 +24,10 @@ const SheetActions = ({ actions }: SheetActionsProps) => { // Avoid drawing separator above the first element const isFirstElement = index === 0; + const buttonStyle = { + opacity: disabled ? 0.5 : 1, + }; + return ( {actions.length > 1 && !isFirstElement && ( @@ -35,8 +39,7 @@ const SheetActions = ({ actions }: SheetActionsProps) => { label={label} size={ButtonSize.Lg} disabled={disabled || isLoading} - /* eslint-disable-next-line */ - style={{ opacity: disabled ? 0.5 : 1 }} + style={buttonStyle} variant={variant} {...generateTestId(Platform, testID)} /> diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 51127fb576d..4b0eedaed6f 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -163,6 +163,10 @@ const AccountSelectorList = ({ isSelectedAccount = selectedAddresses.includes(address); } + const cellStyle = { + opacity: isLoading ? 0.5 : 1, + }; + return ( { @@ -186,8 +190,7 @@ const AccountSelectorList = ({ }} tagLabel={tagLabel} disabled={isDisabled} - /* eslint-disable-next-line */ - style={{ opacity: isLoading ? 0.5 : 1 }} + style={cellStyle} > {renderRightAccessory?.(address, accountName) || (assets && renderAccountBalances(assets))} From cd002164989df78925d316aeb32ae8683b77c8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Tue, 10 Jan 2023 16:43:08 +0000 Subject: [PATCH 47/91] (TabBar): remove null label condition. This condition is not needed anymore since we removed the Activity Route from the bottom navigation stack --- app/component-library/components/Navigation/TabBar/TabBar.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/component-library/components/Navigation/TabBar/TabBar.tsx b/app/component-library/components/Navigation/TabBar/TabBar.tsx index b02fe01e4f9..83f50e5012d 100644 --- a/app/component-library/components/Navigation/TabBar/TabBar.tsx +++ b/app/component-library/components/Navigation/TabBar/TabBar.tsx @@ -21,8 +21,6 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { const renderTabBarItem = useCallback( (route: { name: string; key: string }, index: number) => { const label = descriptors[route.key].options.tabBarLabel as TabBarLabel; - if (!label) return null; - const key = `tab-bar-item-${label}`; const isSelected = state.index === index; const icon = ICON_BY_TAB_BAR_LABEL[label]; From c9b2d2eb67d4a7cd30d39ed8ed1dd590101e1555 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 10 Jan 2023 09:52:18 -0800 Subject: [PATCH 48/91] Fix snapshots --- .../__snapshots__/GetStarted.test.tsx.snap | 6 ++--- .../__snapshots__/Regions.test.tsx.snap | 15 ++++-------- .../Amount/__snapshots__/index.test.tsx.snap | 24 +++++++------------ 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/app/components/UI/FiatOnRampAggregator/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap b/app/components/UI/FiatOnRampAggregator/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap index 430fd80a11e..7c76bead989 100644 --- a/app/components/UI/FiatOnRampAggregator/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap +++ b/app/components/UI/FiatOnRampAggregator/Views/GetStarted/__snapshots__/GetStarted.test.tsx.snap @@ -194,8 +194,7 @@ exports[`GetStarted renders correctly 1`] = ` } > Date: Tue, 10 Jan 2023 12:58:30 -0800 Subject: [PATCH 49/91] Improve logic to autoscroll account selector --- .../AccountSelectorList.tsx | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 4b0eedaed6f..865e23a40a2 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -1,5 +1,5 @@ // Third party dependencies. -import React, { useCallback, useEffect, useRef } from 'react'; +import React, { useCallback, useRef } from 'react'; import { Alert, ListRenderItem, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { useSelector } from 'react-redux'; @@ -39,6 +39,7 @@ const AccountSelectorList = ({ }: AccountSelectorListProps) => { const Engine = UntypedEngine as any; const accountListRef = useRef(null); + const accountsLengthRef = useRef(0); const { styles } = useStyles(styleSheet, {}); const accountAvatarType = useSelector((state: any) => state.settings.useBlockieIcon @@ -46,26 +47,6 @@ const AccountSelectorList = ({ : AvatarAccountType.JazzIcon, ); - useEffect(() => { - if (!accounts.length || isMultiSelect) return; - const selectedAddressOverride = selectedAddresses?.[0]; - const account = accounts.find(({ isSelected, address }) => - selectedAddressOverride - ? selectedAddressOverride === address - : isSelected, - ); - if (account) { - // Wrap in timeout to provide more time for the list to render. - setTimeout(() => { - accountListRef?.current?.scrollToOffset({ - offset: account.yOffset, - animated: false, - }); - }, 0); - } - // eslint-disable-next-line - }, [accounts.length, selectedAddresses, isMultiSelect]); - const getKeyExtractor = ({ address }: Account) => address; const getTagLabel = (type: KeyringTypes) => { @@ -211,9 +192,28 @@ const AccountSelectorList = ({ ], ); + const onContentSizeChanged = useCallback(() => { + // Handle auto scroll to account + if (!accounts.length || isMultiSelect) return; + if (accountsLengthRef.current !== accounts.length) { + const selectedAddressOverride = selectedAddresses?.[0]; + const account = accounts.find(({ isSelected, address }) => + selectedAddressOverride + ? selectedAddressOverride === address + : isSelected, + ); + accountListRef?.current?.scrollToOffset({ + offset: account?.yOffset, + animated: false, + }); + accountsLengthRef.current = accounts.length; + } + }, [accounts.length, selectedAddresses, isMultiSelect]); + return ( Date: Tue, 10 Jan 2023 19:04:35 -0800 Subject: [PATCH 50/91] Fix analytics --- .../AccountPermissionsConnected.tsx | 22 ++++++++++++++----- app/components/Views/ActivityView/index.js | 11 ++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx index 53542828d24..848de0ae6e2 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -23,6 +23,7 @@ import { } from '../../../../component-library/components/Toast'; import getAccountNameWithENS from '../../../../util/accounts'; import AnalyticsV2 from '../../../../util/analyticsV2'; +import { MetaMetricsEvents } from '../../../../core/Analytics'; // Internal dependencies. import { AccountPermissionsConnectedProps } from './AccountPermissionsConnected.types'; @@ -99,14 +100,21 @@ const AccountPermissionsConnected = ({ ], ); + /** + * Permission removal is already handled in AccountSelectorList. + */ + const onRemoveAccount = useCallback( + () => + // Check if the deleted account is the only account. + accounts.length == 1 && onDismissSheet(), + [accounts], + ); + const switchNetwork = useCallback(() => { dispatch(toggleNetworkModal(false)); - AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.BROWSER_SWITCH_NETWORK, - { - from_chain_id: networkController.network, - }, - ); + AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_SWITCH_NETWORK, { + from_chain_id: networkController.network, + }); }, [networkController.network, dispatch]); const renderSheetAction = useCallback( @@ -148,10 +156,12 @@ const AccountPermissionsConnected = ({ {renderSheetAction()} diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js index c7a35420cd0..604314aa49f 100644 --- a/app/components/Views/ActivityView/index.js +++ b/app/components/Views/ActivityView/index.js @@ -12,7 +12,7 @@ import FiatOrdersView from '../FiatOrdersView'; import ErrorBoundary from '../ErrorBoundary'; import { useTheme } from '../../../util/theme'; import Routes from '../../../constants/navigation/Routes'; -import AnalyticsV2 from '../../../util/analyticsV2'; +import { MetaMetricsEvents } from '../../../core/Analytics'; const styles = StyleSheet.create({ wrapper: { @@ -37,12 +37,9 @@ const ActivityView = () => { screen: Routes.SHEET.ACCOUNT_SELECTOR, }); // Track Event: "Opened Acount Switcher" - AnalyticsV2.trackEvent( - AnalyticsV2.ANALYTICS_EVENTS.BROWSER_OPEN_ACCOUNT_SWITCH, - { - number_of_accounts: Object.keys(accounts ?? {}).length, - }, - ); + AnalyticsV2.trackEvent(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, { + number_of_accounts: Object.keys(accounts ?? {}).length, + }); }, [navigation, accounts]); useEffect( From 59175ac62ca6589c8fce2cdb72d82fb94fdfa78c Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 10 Jan 2023 19:05:08 -0800 Subject: [PATCH 51/91] Enable ability to delete imported account while in connect mode --- .../AccountSelectorList.tsx | 16 ++++--- .../AccountSelectorList.types.ts | 7 +++ .../AccountConnectMultiSelector.tsx | 43 +++++++++++++------ .../Views/AccountSelector/AccountSelector.tsx | 10 +++++ 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 865e23a40a2..b5e0e563ba2 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -27,6 +27,7 @@ import styleSheet from './AccountSelectorList.styles'; const AccountSelectorList = ({ onSelectAccount, + onRemoveAccount, accounts, ensByAccountAddress, isLoading = false, @@ -98,7 +99,6 @@ const AccountSelectorList = ({ text: strings('accounts.yes_remove_it'), onPress: async () => { // TODO: Refactor account deletion logic to make more robust. - const { PreferencesController } = Engine.context; const selectedAddressOverride = selectedAddresses?.[0]; const account = accounts.find( ({ isSelected: isAccountSelected, address: accountAddress }) => @@ -109,12 +109,18 @@ const AccountSelectorList = ({ let nextActiveAddress = account.address; if (isSelected) { const nextActiveIndex = index === 0 ? 1 : index - 1; - nextActiveAddress = accounts[nextActiveIndex].address; - PreferencesController.setSelectedAddress(nextActiveAddress); + nextActiveAddress = accounts[nextActiveIndex]?.address; } + // Switching accounts on the PreferencesController must happen before account is removed from the KeyringController, otherwise UI will break. + // If needed, place PreferencesController.setSelectedAddress in onRemoveAccount callback. + onRemoveAccount?.({ + removedAddress: address, + nextActiveAddress, + }); await Engine.context.KeyringController.removeAccount(address); + // Revocation of accounts from PermissionController is needed whenever accounts are removed. + // If there is an instance where this is not the case, this logic will need to be updated. removeAccountFromPermissions(address); - onSelectAccount?.(nextActiveAddress, isSelected); }, }, ], @@ -122,7 +128,7 @@ const AccountSelectorList = ({ ); }, /* eslint-disable-next-line */ - [accounts, onSelectAccount, isRemoveAccountEnabled, selectedAddresses], + [accounts, onRemoveAccount, isRemoveAccountEnabled, selectedAddresses], ); const renderAccountItem: ListRenderItem = useCallback( diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts index 2dc4decc0bb..18b92ed7bbf 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts @@ -22,6 +22,13 @@ export interface AccountSelectorListProps * Optional callback to trigger when account is selected. */ onSelectAccount?: (address: string, isSelected: boolean) => void; + /** + * Optional callback to trigger when account is removed. + */ + onRemoveAccount?: (params: { + removedAddress: string; + nextActiveAddress: string; + }) => void; /** * Optional boolean that indicates if accounts are being processed in the background. The accounts will be unselectable as long as this is true. * @default false diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx index 3c848cf1d93..a809bd2aeb3 100644 --- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx @@ -36,6 +36,33 @@ const AccountConnectMultiSelector = ({ }: AccountConnectMultiSelectorProps) => { const { styles } = useStyles(styleSheet, {}); + const onRemoveAccount = useCallback( + ({ removedAddress }: { removedAddress: string }) => { + let newSelectedAccountAddresses = [...selectedAddresses]; + const selectedAddressIndex = selectedAddresses.indexOf(removedAddress); + newSelectedAccountAddresses.splice(selectedAddressIndex, 1); + onSelectAddress(newSelectedAccountAddresses); + }, + [selectedAddresses, onSelectAddress], + ); + + const onSelectAccount = useCallback( + (accAddress) => { + const selectedAddressIndex = selectedAddresses.indexOf(accAddress); + // Reconstruct selected addresses. + const newAccountAddresses = accounts.reduce((acc, { address }) => { + if (accAddress === address) { + selectedAddressIndex === -1 && acc.push(address); + } else if (selectedAddresses.includes(address)) { + acc.push(address); + } + return acc; + }, [] as string[]); + onSelectAddress(newAccountAddresses); + }, + [accounts, selectedAddresses, onSelectAddress], + ); + const renderSheetActions = useCallback( () => ( { - const selectedAddressIndex = selectedAddresses.indexOf(accAddress); - // Reconstruct selected addresses. - const newAccountAddresses = accounts.reduce((acc, { address }) => { - if (accAddress === address) { - selectedAddressIndex === -1 && acc.push(address); - } else if (selectedAddresses.includes(address)) { - acc.push(address); - } - return acc; - }, [] as string[]); - onSelectAddress(newAccountAddresses); - }} + onSelectAccount={onSelectAccount} accounts={accounts} ensByAccountAddress={ensByAccountAddress} isLoading={isLoading} selectedAddresses={selectedAddresses} isMultiSelect + isRemoveAccountEnabled + onRemoveAccount={onRemoveAccount} /> {renderSheetActions()} {renderCtaButtons()} diff --git a/app/components/Views/AccountSelector/AccountSelector.tsx b/app/components/Views/AccountSelector/AccountSelector.tsx index 281844970ba..407e2d636f7 100644 --- a/app/components/Views/AccountSelector/AccountSelector.tsx +++ b/app/components/Views/AccountSelector/AccountSelector.tsx @@ -58,6 +58,15 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { }); }; + const onRemoveAccount = useCallback( + ({ nextActiveAddress }: { nextActiveAddress: string }) => { + const { PreferencesController } = Engine.context; + nextActiveAddress && + PreferencesController.setSelectedAddress(nextActiveAddress); + }, + [], + ); + const createNewAccount = useCallback(async () => { const { KeyringController, PreferencesController } = Engine.context; try { @@ -134,6 +143,7 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { Date: Tue, 10 Jan 2023 19:38:43 -0800 Subject: [PATCH 52/91] Auto scroll multiselect when possible --- .../UI/AccountSelectorList/AccountSelectorList.tsx | 5 +++-- .../UI/AccountSelectorList/AccountSelectorList.types.ts | 4 ++++ .../AccountConnectMultiSelector.tsx | 2 ++ .../AccountConnectMultiSelector.types.ts | 1 + .../AccountConnectSingle/AccountConnectSingle.tsx | 6 +++++- .../Views/AccountPermissions/AccountPermissions.tsx | 1 + 6 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index b5e0e563ba2..1be650f7084 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -36,6 +36,7 @@ const AccountSelectorList = ({ renderRightAccessory, isSelectionDisabled, isRemoveAccountEnabled = false, + isAutoScrollEnabled = true, ...props }: AccountSelectorListProps) => { const Engine = UntypedEngine as any; @@ -200,7 +201,7 @@ const AccountSelectorList = ({ const onContentSizeChanged = useCallback(() => { // Handle auto scroll to account - if (!accounts.length || isMultiSelect) return; + if (!accounts.length || !isAutoScrollEnabled) return; if (accountsLengthRef.current !== accounts.length) { const selectedAddressOverride = selectedAddresses?.[0]; const account = accounts.find(({ isSelected, address }) => @@ -214,7 +215,7 @@ const AccountSelectorList = ({ }); accountsLengthRef.current = accounts.length; } - }, [accounts.length, selectedAddresses, isMultiSelect]); + }, [accounts.length, selectedAddresses, isAutoScrollEnabled]); return ( { const { styles } = useStyles(styleSheet, {}); @@ -188,6 +189,7 @@ const AccountConnectMultiSelector = ({ isMultiSelect isRemoveAccountEnabled onRemoveAccount={onRemoveAccount} + isAutoScrollEnabled={isAutoScrollEnabled} /> {renderSheetActions()} {renderCtaButtons()} diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts index a8015695c46..2f2a07bad10 100644 --- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts @@ -17,4 +17,5 @@ export interface AccountConnectMultiSelectorProps extends UseAccounts { hostname: string; favicon: ImageSourcePropType; secureIcon: IconName; + isAutoScrollEnabled?: boolean; } diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx index 685e72b7b4d..ca6abad16c0 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx @@ -69,7 +69,11 @@ const AccountConnectSingle = ({ { label: strings('accounts.connect_multiple_accounts'), onPress: () => { - onSetSelectedAddresses([]); + onSetSelectedAddresses( + defaultSelectedAccount?.address + ? [defaultSelectedAccount.address] + : [], + ); onSetScreen(AccountConnectScreens.MultiConnectSelector); }, disabled: isLoading, diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index 1212b359529..fe02a2545ff 100644 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -292,6 +292,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { favicon={favicon} hostname={hostname} secureIcon={secureIcon} + isAutoScrollEnabled={false} /> ), [ From 5e677b275c73f6435f07fb0b8c740301a1d0af2a Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 10 Jan 2023 23:47:56 -0800 Subject: [PATCH 53/91] Fix lint --- .../UI/AccountSelectorList/AccountSelectorList.tsx | 2 +- .../AccountConnectMultiSelector.tsx | 2 +- .../AccountConnectSingle/AccountConnectSingle.tsx | 8 +++++++- .../AccountPermissionsConnected.tsx | 4 ++-- app/components/Views/AccountSelector/AccountSelector.tsx | 2 +- app/components/Views/ActivityView/index.js | 1 + 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 1be650f7084..a403bb7c9b0 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -215,7 +215,7 @@ const AccountSelectorList = ({ }); accountsLengthRef.current = accounts.length; } - }, [accounts.length, selectedAddresses, isAutoScrollEnabled]); + }, [accounts, selectedAddresses, isAutoScrollEnabled]); return ( { - let newSelectedAccountAddresses = [...selectedAddresses]; + const newSelectedAccountAddresses = [...selectedAddresses]; const selectedAddressIndex = selectedAddresses.indexOf(removedAddress); newSelectedAccountAddresses.splice(selectedAddressIndex, 1); onSelectAddress(newSelectedAccountAddresses); diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx index ca6abad16c0..d630fdadb8d 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx @@ -82,7 +82,13 @@ const AccountConnectSingle = ({ /> ), - [onSetScreen, onSetSelectedAddresses, isLoading, styles], + [ + onSetScreen, + onSetSelectedAddresses, + isLoading, + styles, + defaultSelectedAccount?.address, + ], ); const renderCtaButtons = useCallback( diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx index 848de0ae6e2..4521a4abc7c 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -106,8 +106,8 @@ const AccountPermissionsConnected = ({ const onRemoveAccount = useCallback( () => // Check if the deleted account is the only account. - accounts.length == 1 && onDismissSheet(), - [accounts], + accounts.length === 1 && onDismissSheet(), + [accounts, onDismissSheet], ); const switchNetwork = useCallback(() => { diff --git a/app/components/Views/AccountSelector/AccountSelector.tsx b/app/components/Views/AccountSelector/AccountSelector.tsx index 407e2d636f7..cc99580cb37 100644 --- a/app/components/Views/AccountSelector/AccountSelector.tsx +++ b/app/components/Views/AccountSelector/AccountSelector.tsx @@ -64,7 +64,7 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { nextActiveAddress && PreferencesController.setSelectedAddress(nextActiveAddress); }, - [], + [Engine.context], ); const createNewAccount = useCallback(async () => { diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js index 604314aa49f..6e313500244 100644 --- a/app/components/Views/ActivityView/index.js +++ b/app/components/Views/ActivityView/index.js @@ -12,6 +12,7 @@ import FiatOrdersView from '../FiatOrdersView'; import ErrorBoundary from '../ErrorBoundary'; import { useTheme } from '../../../util/theme'; import Routes from '../../../constants/navigation/Routes'; +import AnalyticsV2 from '../../../util/analyticsV2'; import { MetaMetricsEvents } from '../../../core/Analytics'; const styles = StyleSheet.create({ From b14ca94e54d6f0cd867620522fd3d51822a191fb Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 11 Jan 2023 00:31:02 -0800 Subject: [PATCH 54/91] Replace @metamask/controllers imports --- .../UI/AccountSelectorList/AccountSelectorList.tsx | 2 +- .../AccountConnectSingle/AccountConnectSingle.tsx | 2 +- app/components/hooks/useAccounts/useAccounts.ts | 2 +- app/components/hooks/useAccounts/useAccounts.types.ts | 2 +- app/core/Permissions/specifications.js | 5 ++++- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index a403bb7c9b0..5e5fab5c441 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useRef } from 'react'; import { Alert, ListRenderItem, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { useSelector } from 'react-redux'; -import { KeyringTypes } from '@metamask/controllers'; +import { KeyringTypes } from '@metamask/keyring-controller'; // External dependencies. import Cell, { diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx index d630fdadb8d..c9054bbbe54 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx @@ -2,7 +2,7 @@ import React, { useCallback } from 'react'; import { View } from 'react-native'; import { useSelector } from 'react-redux'; -import { KeyringTypes } from '@metamask/controllers'; +import { KeyringTypes } from '@metamask/keyring-controller'; // External dependencies. import SheetActions from '../../../../component-library/components-temp/SheetActions'; diff --git a/app/components/hooks/useAccounts/useAccounts.ts b/app/components/hooks/useAccounts/useAccounts.ts index 4d1734a9e59..8e8787a96d0 100644 --- a/app/components/hooks/useAccounts/useAccounts.ts +++ b/app/components/hooks/useAccounts/useAccounts.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { toChecksumAddress } from 'ethereumjs-util'; -import { KeyringTypes } from '@metamask/controllers'; +import { KeyringTypes } from '@metamask/keyring-controller'; import { isEqual } from 'lodash'; // External Dependencies. diff --git a/app/components/hooks/useAccounts/useAccounts.types.ts b/app/components/hooks/useAccounts/useAccounts.types.ts index c77bfeb425b..135e886259e 100644 --- a/app/components/hooks/useAccounts/useAccounts.types.ts +++ b/app/components/hooks/useAccounts/useAccounts.types.ts @@ -1,5 +1,5 @@ // Third party dependencies. -import { KeyringTypes } from '@metamask/controllers'; +import { KeyringTypes } from '@metamask/keyring-controller'; // External dependencies. import { AvatarGroupToken } from '../../../component-library/components/Avatars/AvatarGroup/AvatarGroup.types'; diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js index d53475d46d7..c0255d85943 100644 --- a/app/core/Permissions/specifications.js +++ b/app/core/Permissions/specifications.js @@ -1,4 +1,7 @@ -import { constructPermission, PermissionType } from '@metamask/controllers'; +import { + constructPermission, + PermissionType, +} from '@metamask/permission-controller'; import { v1 as random } from 'uuid'; import { CaveatTypes, RestrictedMethods } from './constants'; From 572425d228ccf7b590e3e9ab50d1643b99e01226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Wed, 11 Jan 2023 15:24:46 +0000 Subject: [PATCH 55/91] (Navigation): adopt useNavigationDetails pattern --- app/components/Nav/App/index.js | 4 +- app/components/Nav/Main/RootRPCMethodsUI.js | 11 ++-- app/components/UI/AccountOverview/index.js | 5 +- app/components/UI/AssetOverview/index.js | 10 ++-- app/components/UI/DrawerView/index.js | 11 ++-- .../components/AccountSelector.tsx | 6 +- .../UI/OnboardingWizard/Step5/index.js | 7 +-- app/components/UI/Tokens/index.js | 6 +- .../TransactionReviewInformation/index.js | 12 ++-- .../AccountConnect/AccountConnect.types.ts | 14 +++-- app/components/Views/AccountConnect/index.ts | 9 +++ .../AccountSelector/AccountSelector.types.ts | 56 ++++++++++--------- app/components/Views/AccountSelector/index.ts | 9 +++ app/components/Views/Browser/Browser.types.ts | 14 +++++ app/components/Views/Browser/index.js | 2 + app/components/Views/DetectedTokens/index.tsx | 7 +++ .../SimpleWebview/SimpleWebview.types.ts | 14 +++++ app/components/Views/SimpleWebview/index.js | 2 + app/constants/navigation/Routes.ts | 5 ++ 19 files changed, 132 insertions(+), 72 deletions(-) create mode 100644 app/components/Views/Browser/Browser.types.ts create mode 100644 app/components/Views/SimpleWebview/SimpleWebview.types.ts diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index 4016baa69cc..71d3cb42d2e 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -149,7 +149,7 @@ const OnboardingNav = () => ( const SimpleWebviewScreen = () => ( @@ -173,7 +173,7 @@ const OnboardingRootNav = () => ( header={null} /> diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 7302b15d3c8..d3cb27c5c3e 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -58,7 +58,7 @@ import { useTheme } from '../../../util/theme'; import withQRHardwareAwareness from '../../UI/QRHardware/withQRHardwareAwareness'; import QRSigningModal from '../../UI/QRHardware/QRSigningModal'; import { networkSwitched } from '../../../actions/onboardNetwork'; -import Routes from '../../../constants/navigation/Routes'; +import { createAccountConnectNavDetails } from '../../Views/AccountConnect'; const hstInterface = new ethers.utils.Interface(abi); @@ -717,13 +717,12 @@ const RootRPCMethodsUI = (props) => { metadata: { id }, } = requestData; - props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.ACCOUNT_CONNECT, - params: { + props.navigation.navigate( + ...createAccountConnectNavDetails({ hostInfo: requestData, permissionRequestId: id, - }, - }); + }), + ); } break; case ApprovalTypes.CONNECT_ACCOUNTS: diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js index 787f51a6f8d..a5056076dac 100644 --- a/app/components/UI/AccountOverview/index.js +++ b/app/components/UI/AccountOverview/index.js @@ -48,6 +48,7 @@ import { WALLET_ACCOUNT_NAME_LABEL_TEXT, WALLET_ACCOUNT_NAME_LABEL_INPUT, } from '../../../../wdio/features/testIDs/Screens/WalletView.testIds'; +import { createAccountSelectorNavDetails } from '../../Views/AccountSelector'; const createStyles = (colors) => StyleSheet.create({ @@ -223,9 +224,7 @@ class AccountOverview extends PureComponent { openAccountSelector = () => { const { onboardingWizard, navigation } = this.props; !onboardingWizard && - navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.ACCOUNT_SELECTOR, - }); + navigation.navigate(...createAccountSelectorNavDetails()); }; isAccountLabelDefined = (accountLabel) => diff --git a/app/components/UI/AssetOverview/index.js b/app/components/UI/AssetOverview/index.js index 5d4418d71d0..5b75deeb866 100644 --- a/app/components/UI/AssetOverview/index.js +++ b/app/components/UI/AssetOverview/index.js @@ -42,6 +42,7 @@ import NetworkMainAssetLogo from '../NetworkMainAssetLogo'; import { ThemeContext, mockTheme } from '../../../util/theme'; import Routes from '../../../constants/navigation/Routes'; import { isTestNet } from '../../../util/networks'; +import { createWebviewNavDetails } from '../../Views/SimpleWebview'; const createStyles = (colors) => StyleSheet.create({ @@ -221,12 +222,11 @@ class AssetOverview extends PureComponent { }; goToBrowserUrl(url) { - this.props.navigation.navigate('Webview', { - screen: 'SimpleWebview', - params: { + this.props.navigation.navigate( + ...createWebviewNavDetails({ url, - }, - }); + }), + ); } renderLogo = () => { diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js index bca7f709ba5..c8a0f807b69 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -79,6 +79,7 @@ import { DRAWER_VIEW_LOCK_TEXT_ID, DRAWER_VIEW_SETTINGS_TEXT_ID, } from '../../../../wdio/features/testIDs/Screens/DrawerView.testIds'; +import { createAccountSelectorNavDetails } from '../../Views/AccountSelector'; const createStyles = (colors) => StyleSheet.create({ @@ -609,14 +610,14 @@ class DrawerView extends PureComponent { openAccountSelector = () => { const { navigation } = this.props; - navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.ACCOUNT_SELECTOR, - params: { + + navigation.navigate( + ...createAccountSelectorNavDetails({ onOpenImportAccount: this.hideDrawer, onOpenConnectHardwareWallet: this.hideDrawer, onSelectAccount: this.hideDrawer, - }, - }); + }), + ); this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_ACCOUNT_NAME); }; diff --git a/app/components/UI/FiatOnRampAggregator/components/AccountSelector.tsx b/app/components/UI/FiatOnRampAggregator/components/AccountSelector.tsx index b5ae8f04990..1376792c863 100644 --- a/app/components/UI/FiatOnRampAggregator/components/AccountSelector.tsx +++ b/app/components/UI/FiatOnRampAggregator/components/AccountSelector.tsx @@ -8,7 +8,7 @@ import JSIdenticon from '../../Identicon'; import BaseText from '../../../Base/Text'; import JSSelectorButton from '../../../Base/SelectorButton'; import { useNavigation } from '@react-navigation/native'; -import Routes from '../../../../constants/navigation/Routes'; +import { createAccountSelectorNavDetails } from '../../../Views/AccountSelector'; // TODO: Convert into typescript and correctly type const SelectorButton = JSSelectorButton as any; @@ -39,9 +39,7 @@ const AccountSelector = () => { ); const openAccountSelector = () => - navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.ACCOUNT_SELECTOR, - }); + navigation.navigate(...createAccountSelectorNavDetails()); return ( diff --git a/app/components/UI/OnboardingWizard/Step5/index.js b/app/components/UI/OnboardingWizard/Step5/index.js index 7dfd67a8922..68aaf6b01e4 100644 --- a/app/components/UI/OnboardingWizard/Step5/index.js +++ b/app/components/UI/OnboardingWizard/Step5/index.js @@ -19,7 +19,7 @@ import { } from '../../../../core/Analytics'; import AnalyticsV2 from '../../../../util/analyticsV2'; import { useTheme } from '../../../../util/theme'; -import Routes from '../../../../constants/navigation/Routes'; +import { createBrowserNavDetails } from '../../../Views/Browser'; const WIDTH = Dimensions.get('window').width; const styles = StyleSheet.create({ @@ -60,10 +60,7 @@ const Step5 = (props) => { */ const onNext = () => { setOnboardingWizardStep && setOnboardingWizardStep(6); - navigation && - navigation.navigate(Routes.BROWSER.HOME, { - screen: Routes.BROWSER.VIEW, - }); + navigation && navigation.navigate(...createBrowserNavDetails()); AnalyticsV2.trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { tutorial_step_count: 5, tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5], diff --git a/app/components/UI/Tokens/index.js b/app/components/UI/Tokens/index.js index a05d8144a84..a8818874105 100644 --- a/app/components/UI/Tokens/index.js +++ b/app/components/UI/Tokens/index.js @@ -31,9 +31,9 @@ import { ThemeContext, mockTheme } from '../../../util/theme'; import Text from '../../Base/Text'; import NotificationManager from '../../../core/NotificationManager'; import { getDecimalChainId, isTestNet } from '../../../util/networks'; -import Routes from '../../../constants/navigation/Routes'; import generateTestId from '../../../../wdio/utils/generateTestId'; import { IMPORT_TOKEN_BUTTON_ID } from '../../../../wdio/features/testIDs/Screens/WalletView.testIds'; +import { createDetectedTokensNavDetails } from '../../Views/DetectedTokens'; const createStyles = (colors) => StyleSheet.create({ @@ -326,9 +326,7 @@ class Tokens extends PureComponent { showDetectedTokens = () => { const { NetworkController } = Engine.context; const { detectedTokens } = this.props; - this.props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: 'DetectedTokens', - }); + this.props.navigation.navigate(...createDetectedTokensNavDetails()); InteractionManager.runAfterInteractions(() => { AnalyticsV2.trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CLICKED, { source: 'detected', diff --git a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js index fa3cd651da4..c3542c126fe 100644 --- a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js +++ b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js @@ -39,10 +39,10 @@ import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller'; import CustomNonce from '../../../UI/CustomNonce'; import Logger from '../../../../util/Logger'; import { ThemeContext, mockTheme } from '../../../../util/theme'; -import Routes from '../../../../constants/navigation/Routes'; import AppConstants from '../../../../core/AppConstants'; import WarningMessage from '../../../Views/SendFlow/WarningMessage'; import { allowedToBuy } from '../../FiatOnRampAggregator'; +import { createBrowserNavDetails } from '../../../Views/Browser'; const createStyles = (colors) => StyleSheet.create({ @@ -511,10 +511,12 @@ class TransactionReviewInformation extends PureComponent { goToFaucet = () => { InteractionManager.runAfterInteractions(() => { this.onCancelPress(); - this.props.navigation.navigate(Routes.BROWSER.VIEW, { - newTabUrl: AppConstants.URLS.MM_FAUCET, - timestamp: Date.now(), - }); + this.props.navigation.navigate( + ...createBrowserNavDetails({ + newTabUrl: AppConstants.URLS.MM_FAUCET, + timestamp: Date.now(), + }), + ); }); }; diff --git a/app/components/Views/AccountConnect/AccountConnect.types.ts b/app/components/Views/AccountConnect/AccountConnect.types.ts index 4d990f43445..8a5fe2697bb 100644 --- a/app/components/Views/AccountConnect/AccountConnect.types.ts +++ b/app/components/Views/AccountConnect/AccountConnect.types.ts @@ -7,6 +7,13 @@ export enum AccountConnectScreens { MultiConnectSelector = 'MultiConnectSelector', } +export interface AccountConnectParams { + hostInfo: { + metadata: { origin: string }; + }; + permissionRequestId: string; +} + /** * AccountConnect props. */ @@ -15,11 +22,6 @@ export interface AccountConnectProps { * Props that are passed in while navigating to screen. */ route: { - params: { - hostInfo: { - metadata: { origin: string }; - }; - permissionRequestId: string; - }; + params: AccountConnectParams; }; } diff --git a/app/components/Views/AccountConnect/index.ts b/app/components/Views/AccountConnect/index.ts index 3fdb783588e..0a3bed76567 100644 --- a/app/components/Views/AccountConnect/index.ts +++ b/app/components/Views/AccountConnect/index.ts @@ -1,2 +1,11 @@ +import Routes from '../../../constants/navigation/Routes'; +import { createNavigationDetails } from '../../../util/navigation/navUtils'; +import { AccountConnectParams } from './AccountConnect.types'; + export { default } from './AccountConnect'; +export const createAccountConnectNavDetails = + createNavigationDetails( + Routes.MODAL.ROOT_MODAL_FLOW, + Routes.SHEET.ACCOUNT_CONNECT, + ); export type { AccountConnectProps } from './AccountConnect.types'; diff --git a/app/components/Views/AccountSelector/AccountSelector.types.ts b/app/components/Views/AccountSelector/AccountSelector.types.ts index 87affc7d0a7..b8f5e2e18c2 100644 --- a/app/components/Views/AccountSelector/AccountSelector.types.ts +++ b/app/components/Views/AccountSelector/AccountSelector.types.ts @@ -1,6 +1,34 @@ // External dependencies. import { UseAccountsParams } from '../../../components/hooks/useAccounts'; +export interface AccountSelectorParams { + /** + * Optional callback that is called whenever a new account is being created. + */ + onCreateNewAccount?: () => void; + /** + * Optional callback that is called whenever import account is being opened. + */ + onOpenImportAccount?: () => void; + /** + * Optional callback that is called whenever connect hardware wallet is being opened. + */ + onOpenConnectHardwareWallet?: () => void; + /** + * Optional callback that is called whenever an account is selected. + */ + onSelectAccount?: (address: string) => void; + /** + * Optional boolean that indicates if the sheet is for selection only. Other account actions are disabled when this is true. + */ + isSelectOnly?: boolean; + /** + * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. + * @param balance - The ticker balance of an account in wei and hex string format. + */ + checkBalanceError?: UseAccountsParams['checkBalanceError']; +} + /** * AccountSelectorProps props. */ @@ -9,32 +37,6 @@ export interface AccountSelectorProps { * Props that are passed in while navigating to screen. */ route: { - params?: { - /** - * Optional callback that is called whenever a new account is being created. - */ - onCreateNewAccount?: () => void; - /** - * Optional callback that is called whenever import account is being opened. - */ - onOpenImportAccount?: () => void; - /** - * Optional callback that is called whenever connect hardware wallet is being opened. - */ - onOpenConnectHardwareWallet?: () => void; - /** - * Optional callback that is called whenever an account is selected. - */ - onSelectAccount?: (address: string) => void; - /** - * Optional boolean that indicates if the sheet is for selection only. Other account actions are disabled when this is true. - */ - isSelectOnly?: boolean; - /** - * Optional callback that is used to check for a balance requirement. Non-empty string will render the account item non-selectable. - * @param balance - The ticker balance of an account in wei and hex string format. - */ - checkBalanceError?: UseAccountsParams['checkBalanceError']; - }; + params?: AccountSelectorParams; }; } diff --git a/app/components/Views/AccountSelector/index.ts b/app/components/Views/AccountSelector/index.ts index ef2b9e5a0de..30dee41db6e 100644 --- a/app/components/Views/AccountSelector/index.ts +++ b/app/components/Views/AccountSelector/index.ts @@ -1 +1,10 @@ +import Routes from '../../../constants/navigation/Routes'; +import { createNavigationDetails } from '../../../util/navigation/navUtils'; +import { AccountSelectorParams } from './AccountSelector.types'; + +export const createAccountSelectorNavDetails = + createNavigationDetails( + Routes.MODAL.ROOT_MODAL_FLOW, + Routes.SHEET.ACCOUNT_SELECTOR, + ); export { default } from './AccountSelector'; diff --git a/app/components/Views/Browser/Browser.types.ts b/app/components/Views/Browser/Browser.types.ts new file mode 100644 index 00000000000..6bf2ff2a765 --- /dev/null +++ b/app/components/Views/Browser/Browser.types.ts @@ -0,0 +1,14 @@ +import { createNavigationDetails } from '../../../util/navigation/navUtils'; +import Routes from '../../../constants/navigation/Routes'; + +interface BrowserParams { + newTabUrl?: string; + timestamp?: number; +} + +const createBrowserNavDetails = createNavigationDetails( + Routes.BROWSER.HOME, + Routes.BROWSER.VIEW, +); + +export default createBrowserNavDetails; diff --git a/app/components/Views/Browser/index.js b/app/components/Views/Browser/index.js index 67aa80c813d..24cac4e1f8f 100644 --- a/app/components/Views/Browser/index.js +++ b/app/components/Views/Browser/index.js @@ -365,4 +365,6 @@ Browser.propTypes = { route: PropTypes.object, }; +export { default as createBrowserNavDetails } from './Browser.types'; + export default connect(mapStateToProps, mapDispatchToProps)(Browser); diff --git a/app/components/Views/DetectedTokens/index.tsx b/app/components/Views/DetectedTokens/index.tsx index f55b5ea82a0..3e787fa34fd 100644 --- a/app/components/Views/DetectedTokens/index.tsx +++ b/app/components/Views/DetectedTokens/index.tsx @@ -18,6 +18,8 @@ import AnalyticsV2 from '../../../util/analyticsV2'; import { getDecimalChainId } from '../../../util/networks'; import { FlatList } from 'react-native-gesture-handler'; +import { createNavigationDetails } from '../../../util/navigation/navUtils'; +import Routes from '../../../constants/navigation/Routes'; const createStyles = (colors: any) => StyleSheet.create({ @@ -281,4 +283,9 @@ const DetectedTokens = () => { ); }; +export const createDetectedTokensNavDetails = createNavigationDetails( + Routes.MODAL.ROOT_MODAL_FLOW, + Routes.MODAL.DETECTED_TOKENS, +); + export default DetectedTokens; diff --git a/app/components/Views/SimpleWebview/SimpleWebview.types.ts b/app/components/Views/SimpleWebview/SimpleWebview.types.ts new file mode 100644 index 00000000000..9b6aa9757c3 --- /dev/null +++ b/app/components/Views/SimpleWebview/SimpleWebview.types.ts @@ -0,0 +1,14 @@ +import { createNavigationDetails } from '../../../util/navigation/navUtils'; +import Routes from '../../../constants/navigation/Routes'; + +interface WebviewParams { + url?: string; + title?: string; +} + +const createWebviewNavDetails = createNavigationDetails( + Routes.WEBVIEW.MAIN, + Routes.WEBVIEW.SIMPLE, +); + +export default createWebviewNavDetails; diff --git a/app/components/Views/SimpleWebview/index.js b/app/components/Views/SimpleWebview/index.js index ed181295a3e..c9b9dd933b3 100644 --- a/app/components/Views/SimpleWebview/index.js +++ b/app/components/Views/SimpleWebview/index.js @@ -60,4 +60,6 @@ export default class SimpleWebview extends PureComponent { } } +export { default as createWebviewNavDetails } from './SimpleWebview.types'; + SimpleWebview.contextType = ThemeContext; diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index d49c58764e9..35dda1dfb8c 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -27,6 +27,7 @@ const Routes = { TURN_OFF_REMEMBER_ME: 'TurnOffRememberMeModal', UPDATE_NEEDED: 'UpdateNeededModal', ENABLE_AUTOMATIC_SECURITY_CHECKS: 'EnableAutomaticSecurityChecksModal', + DETECTED_TOKENS: 'DetectedTokens', }, ONBOARDING: { ROOT_NAV: 'OnboardingRootNav', @@ -54,6 +55,10 @@ const Routes = { URL_MODAL: 'BrowserUrlModal', VIEW: 'BrowserView', }, + WEBVIEW: { + MAIN: 'Webview', + SIMPLE: 'SimpleWebview', + }, WALLET: { HOME: 'WalletTabHome', }, From 763c07a26e48437fd68dc26084371123d093a745 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 11 Jan 2023 15:45:09 -0800 Subject: [PATCH 56/91] Improve wallet tab contrast --- app/components/Views/Wallet/index.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index cb881804539..db81f405b13 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -13,7 +13,9 @@ import { ActivityIndicator, StyleSheet, View, + TextStyle, } from 'react-native'; +import { Theme } from '@metamask/design-tokens'; import { useDispatch, useSelector } from 'react-redux'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import DefaultTabBar from 'react-native-scrollable-tab-view/DefaultTabBar'; @@ -42,7 +44,7 @@ import { import { toggleNetworkModal } from '../../../actions/modals'; import generateTestId from '../../../../wdio/utils/generateTestId'; -const createStyles = (colors: any) => +const createStyles = ({ colors, typography }: Theme) => StyleSheet.create({ wrapper: { flex: 1, @@ -57,11 +59,10 @@ const createStyles = (colors: any) => }, tabBar: { borderColor: colors.border.muted, + marginTop: 16, }, textStyle: { - fontSize: 12, - letterSpacing: 0.5, - ...(fontStyles.bold as any), + ...(typography.sHeadingSM as TextStyle), }, loader: { backgroundColor: colors.background.default, @@ -78,8 +79,9 @@ const Wallet = ({ navigation }: any) => { const { drawerRef } = useContext(DrawerContext); const [refreshing, setRefreshing] = useState(false); const accountOverviewRef = useRef(null); - const { colors } = useTheme(); - const styles = createStyles(colors); + const theme = useTheme(); + const styles = createStyles(theme); + const { colors } = theme; /** * Map of accounts to information objects including balances */ @@ -236,8 +238,8 @@ const Wallet = ({ navigation }: any) => { () => ( Date: Wed, 11 Jan 2023 16:20:27 -0800 Subject: [PATCH 57/91] Remove unused network prop from browser tab --- app/components/Views/BrowserTab/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index 44a744018d5..9f07bbe65b9 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -1381,10 +1381,6 @@ BrowserTab.propTypes = { * react-navigation object used to switch between screens */ navigation: PropTypes.object, - /** - * A string representing the network id - */ - network: PropTypes.string, /** * A string that represents the selected address */ @@ -1451,7 +1447,6 @@ BrowserTab.defaultProps = { const mapStateToProps = (state) => ({ bookmarks: state.bookmarks, ipfsGateway: state.engine.backgroundState.PreferencesController.ipfsGateway, - network: state.engine.backgroundState.NetworkController.network, selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress?.toLowerCase(), searchEngine: state.settings.searchEngine, From 0c10e6cf2affc6cf3b48b03e1f7f3e95a0e61ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Loureiro?= Date: Thu, 12 Jan 2023 00:42:47 +0000 Subject: [PATCH 58/91] force permitted accounts to be lower case --- app/core/Permissions/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/core/Permissions/index.ts b/app/core/Permissions/index.ts index e19ea904a27..1ea837547c2 100644 --- a/app/core/Permissions/index.ts +++ b/app/core/Permissions/index.ts @@ -185,8 +185,8 @@ export const getPermittedAccounts = async (hostname: string) => { hostname, RestrictedMethods.eth_accounts, ); - return accountsWithLastUsed.map( - ({ address }: { address: string }) => address, + return accountsWithLastUsed.map(({ address }: { address: string }) => + address.toLowerCase(), ); } catch (error: any) { if (error.code === rpcErrorCodes.provider.unauthorized) { From 57f543245b69c475a2591f85b812af82819c5137 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Wed, 11 Jan 2023 17:03:21 -0800 Subject: [PATCH 59/91] Change favicon size to 50. Opensea doesn't support 64 --- app/components/UI/WebsiteIcon/__snapshots__/index.test.tsx.snap | 2 +- app/components/UI/WebsiteIcon/index.js | 2 +- app/components/Views/AccountConnect/AccountConnect.tsx | 2 +- app/components/Views/AccountPermissions/AccountPermissions.tsx | 2 +- app/components/Views/BrowserTab/index.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/UI/WebsiteIcon/__snapshots__/index.test.tsx.snap b/app/components/UI/WebsiteIcon/__snapshots__/index.test.tsx.snap index 950df550ef5..b846db8937e 100644 --- a/app/components/UI/WebsiteIcon/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/WebsiteIcon/__snapshots__/index.test.tsx.snap @@ -14,7 +14,7 @@ exports[`WebsiteIcon should render correctly 1`] = ` onError={[Function]} source={ Object { - "uri": "https://api.faviconkit.com/url.com/64", + "uri": "https://api.faviconkit.com/url.com/50", } } /> diff --git a/app/components/UI/WebsiteIcon/index.js b/app/components/UI/WebsiteIcon/index.js index 1cf32121a4c..cfde080c528 100644 --- a/app/components/UI/WebsiteIcon/index.js +++ b/app/components/UI/WebsiteIcon/index.js @@ -69,7 +69,7 @@ export default class WebsiteIcon extends PureComponent { * Get image url from favicon api */ getIconUrl = (url) => { - const iconUrl = `https://api.faviconkit.com/${getHost(url)}/64`; + const iconUrl = `https://api.faviconkit.com/${getHost(url)}/50`; return iconUrl; }; diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 27cb15b3eff..c563e519593 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -89,7 +89,7 @@ const AccountConnect = (props: AccountConnectProps) => { * Get image url from favicon api. */ const favicon: ImageSourcePropType = useMemo(() => { - const iconUrl = `https://api.faviconkit.com/${hostname}/64`; + const iconUrl = `https://api.faviconkit.com/${hostname}/50`; return { uri: iconUrl }; }, [hostname]); diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index fe02a2545ff..0f7393d28bc 100644 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -74,7 +74,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { * Get image url from favicon api. */ const favicon: ImageSourcePropType = useMemo(() => { - const iconUrl = `https://api.faviconkit.com/${hostname}/64`; + const iconUrl = `https://api.faviconkit.com/${hostname}/50`; return { uri: iconUrl }; }, [hostname]); diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index 9f07bbe65b9..2071d4e179d 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -801,7 +801,7 @@ export const BrowserTab = (props) => { const { origin, pathname = '', query = '' } = urlObj; const realUrl = `${origin}${pathname}${query}`; // Generate favicon. - const favicon = `https://api.faviconkit.com/${getHost(realUrl)}/32`; + const favicon = `https://api.faviconkit.com/${getHost(realUrl)}/50`; // Update navigation bar address with title of loaded url. changeUrl({ ...nativeEvent, url: realUrl, icon: favicon }); changeAddressBar({ ...nativeEvent, url: realUrl, icon: favicon }); From 0795505520db9459969c095a512bb0cd1a14eef8 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Fri, 13 Jan 2023 12:40:13 -0800 Subject: [PATCH 60/91] Update revoke translations --- locales/languages/de.json | 4 +++- locales/languages/el.json | 4 +++- locales/languages/es.json | 4 +++- locales/languages/fr.json | 4 +++- locales/languages/hi.json | 4 +++- locales/languages/id.json | 4 +++- locales/languages/ja.json | 4 +++- locales/languages/ko.json | 4 +++- locales/languages/pt.json | 4 +++- locales/languages/ru.json | 4 +++- locales/languages/tl.json | 4 +++- locales/languages/tr.json | 4 +++- locales/languages/vi.json | 4 +++- locales/languages/zh.json | 4 +++- 14 files changed, 42 insertions(+), 14 deletions(-) diff --git a/locales/languages/de.json b/locales/languages/de.json index 60d9c4df080..4fb38d0d3d0 100644 --- a/locales/languages/de.json +++ b/locales/languages/de.json @@ -421,7 +421,9 @@ "select_all": "\nAlle auswählen", "permissions": "Berechtigungen", "disconnect": "Verbindung trennen", - "disconnect_all_accounts": "Alle Konten trennen" + "disconnect_all_accounts": "Alle Konten trennen", + "revoke": "Widerrufen", + "revoke_all": "Alle widerrufen" }, "toast": { "connected_and_active": "verbunden und aktiv.", diff --git a/locales/languages/el.json b/locales/languages/el.json index ebc0653215e..9ef6d7a59d9 100644 --- a/locales/languages/el.json +++ b/locales/languages/el.json @@ -421,7 +421,9 @@ "select_all": "Επιλογή όλων", "permissions": "Άδειες", "disconnect": "Αποσύνδεση", - "disconnect_all_accounts": "Αποσύνδεση όλων των λογαριασμών" + "disconnect_all_accounts": "Αποσύνδεση όλων των λογαριασμών", + "revoke": "Ανάκληση", + "revoke_all": "Ανάκληση όλων" }, "toast": { "connected_and_active": "συνδεδεμένοι και ενεργοί.", diff --git a/locales/languages/es.json b/locales/languages/es.json index d2653e159f7..f235742d95d 100644 --- a/locales/languages/es.json +++ b/locales/languages/es.json @@ -421,7 +421,9 @@ "select_all": "Seleccionar todo", "permissions": "Permisos", "disconnect": "Desconectar", - "disconnect_all_accounts": "Desconectar todas las cuentas" + "disconnect_all_accounts": "Desconectar todas las cuentas", + "revoke": "Revocar", + "revoke_all": "Revocar todo" }, "toast": { "connected_and_active": "conectado y activo.", diff --git a/locales/languages/fr.json b/locales/languages/fr.json index eea2a3e22ba..5887a8cb7e2 100644 --- a/locales/languages/fr.json +++ b/locales/languages/fr.json @@ -421,7 +421,9 @@ "select_all": "Tout sélectionner", "permissions": "Autorisations", "disconnect": "Déconnecter", - "disconnect_all_accounts": "Déconnecter tous les comptes" + "disconnect_all_accounts": "Déconnecter tous les comptes", + "revoke": "Révoquer", + "revoke_all": "Révoquer tout" }, "toast": { "connected_and_active": "connecté et actif.", diff --git a/locales/languages/hi.json b/locales/languages/hi.json index 467917de4f6..4daa195ff6a 100644 --- a/locales/languages/hi.json +++ b/locales/languages/hi.json @@ -421,7 +421,9 @@ "select_all": "सभी का चयन करें", "permissions": "अनुमतियां", "disconnect": "डिस्कनेक्ट करें", - "disconnect_all_accounts": "सभी खाते डिस्कनेक्ट करें" + "disconnect_all_accounts": "सभी खाते डिस्कनेक्ट करें", + "revoke": "निरस्त करें", + "revoke_all": "सभी निरस्त करें" }, "toast": { "connected_and_active": "कनेक्ट किया हुआ और सक्रिय।", diff --git a/locales/languages/id.json b/locales/languages/id.json index d03bd40489b..802de304630 100644 --- a/locales/languages/id.json +++ b/locales/languages/id.json @@ -421,7 +421,9 @@ "select_all": "Pilih semua", "permissions": "Izin", "disconnect": "Putuskan koneksi", - "disconnect_all_accounts": "Putuskan koneksi semua akun" + "disconnect_all_accounts": "Putuskan koneksi semua akun", + "revoke": "Cabut", + "revoke_all": "Cabut semua" }, "toast": { "connected_and_active": "terhubung dan aktif.", diff --git a/locales/languages/ja.json b/locales/languages/ja.json index 217b9b2465d..d172dd7e792 100644 --- a/locales/languages/ja.json +++ b/locales/languages/ja.json @@ -421,7 +421,9 @@ "select_all": "すべて選択", "permissions": "許可", "disconnect": "接続解除", - "disconnect_all_accounts": "すべてのアカウントを接続解除" + "disconnect_all_accounts": "すべてのアカウントを接続解除", + "revoke": "取り消す", + "revoke_all": "すべて取り消す" }, "toast": { "connected_and_active": "接続済みかつ有効です。", diff --git a/locales/languages/ko.json b/locales/languages/ko.json index c533509f329..3e8159efa3b 100644 --- a/locales/languages/ko.json +++ b/locales/languages/ko.json @@ -421,7 +421,9 @@ "select_all": "모두 선택", "permissions": "권한", "disconnect": "연결 해제", - "disconnect_all_accounts": "모든 계정 연결 해제" + "disconnect_all_accounts": "모든 계정 연결 해제", + "revoke": "취소", + "revoke_all": "모두 취소" }, "toast": { "connected_and_active": "연결되어 활성화됨.", diff --git a/locales/languages/pt.json b/locales/languages/pt.json index 7097f88d46c..5cb3f4a6251 100644 --- a/locales/languages/pt.json +++ b/locales/languages/pt.json @@ -421,7 +421,9 @@ "select_all": "Selecionar tudo", "permissions": "Permissões", "disconnect": "Desconectar", - "disconnect_all_accounts": "Desconectar todas as contas" + "disconnect_all_accounts": "Desconectar todas as contas", + "revoke": "Revogar", + "revoke_all": "Revogar tudo" }, "toast": { "connected_and_active": "conectada e ativa.", diff --git a/locales/languages/ru.json b/locales/languages/ru.json index 66017751115..da1c5c59669 100644 --- a/locales/languages/ru.json +++ b/locales/languages/ru.json @@ -421,7 +421,9 @@ "select_all": "Выбрать все", "permissions": "Разрешения", "disconnect": "Отключить", - "disconnect_all_accounts": "Отключить все счета" + "disconnect_all_accounts": "Отключить все счета", + "revoke": "Отозвано", + "revoke_all": "Отозвать все" }, "toast": { "connected_and_active": "подключен и активен.", diff --git a/locales/languages/tl.json b/locales/languages/tl.json index 9500dd570bd..c9a87ced3b4 100644 --- a/locales/languages/tl.json +++ b/locales/languages/tl.json @@ -421,7 +421,9 @@ "select_all": "Piliin lahat", "permissions": "Mga Pahintulot", "disconnect": "Idiskonekta", - "disconnect_all_accounts": "Idiskonekta ang lahat ng account" + "disconnect_all_accounts": "Idiskonekta ang lahat ng account", + "revoke": "Ipawalang-bisa", + "revoke_all": "Ipawalang-bisa lahat" }, "toast": { "connected_and_active": "konektado at aktibo.", diff --git a/locales/languages/tr.json b/locales/languages/tr.json index 42f38d10328..78746c29a59 100644 --- a/locales/languages/tr.json +++ b/locales/languages/tr.json @@ -421,7 +421,9 @@ "select_all": "Tümünü seç", "permissions": "İzinler", "disconnect": "Bağlantıyı kes", - "disconnect_all_accounts": "Tüm hesapların bağlantısını kes" + "disconnect_all_accounts": "Tüm hesapların bağlantısını kes", + "revoke": "İptal et", + "revoke_all": "Tümünü iptal et" }, "toast": { "connected_and_active": "bağlandı ve aktif.", diff --git a/locales/languages/vi.json b/locales/languages/vi.json index 34d7a274730..74a1b2bac41 100644 --- a/locales/languages/vi.json +++ b/locales/languages/vi.json @@ -421,7 +421,9 @@ "select_all": "Chọn tất cả", "permissions": "Quyền", "disconnect": "Ngắt kết nối", - "disconnect_all_accounts": "Ngắt kết nối tất cả các tài khoản" + "disconnect_all_accounts": "Ngắt kết nối tất cả các tài khoản", + "revoke": "Thu hồi", + "revoke_all": "Thu hồi tất cả" }, "toast": { "connected_and_active": "đã được kết nối và đang hoạt động.", diff --git a/locales/languages/zh.json b/locales/languages/zh.json index da0869bee51..8403af008f9 100644 --- a/locales/languages/zh.json +++ b/locales/languages/zh.json @@ -421,7 +421,9 @@ "select_all": "全部选择", "permissions": "权限", "disconnect": "断开连接", - "disconnect_all_accounts": "断开所有账户的连接" + "disconnect_all_accounts": "断开所有账户的连接", + "revoke": "撤销", + "revoke_all": "全部撤销" }, "toast": { "connected_and_active": "已连接且处于活动状态。", From d189ecda928a794fcbe51b9c5765f84d159b9938 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Fri, 13 Jan 2023 17:43:12 -0800 Subject: [PATCH 61/91] Fix lint issue --- app/components/Views/Wallet/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index db81f405b13..654e4b838bd 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -19,7 +19,7 @@ import { Theme } from '@metamask/design-tokens'; import { useDispatch, useSelector } from 'react-redux'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import DefaultTabBar from 'react-native-scrollable-tab-view/DefaultTabBar'; -import { fontStyles, baseStyles } from '../../../styles/common'; +import { baseStyles } from '../../../styles/common'; import AccountOverview from '../../UI/AccountOverview'; import Tokens from '../../UI/Tokens'; import { getWalletNavbarOptions } from '../../UI/Navbar'; From d402627ea03647c516585f22ce0740c9903bffa6 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Fri, 13 Jan 2023 17:55:06 -0800 Subject: [PATCH 62/91] Fix missing account name in toast --- .../UI/AccountSelectorList/AccountSelectorList.tsx | 8 +++++--- app/components/Views/AccountConnect/AccountConnect.tsx | 4 +++- app/util/accounts/index.ts | 6 +++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 5e5fab5c441..a9b552aa8c5 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -12,7 +12,7 @@ import Cell, { import { useStyles } from '../../../component-library/hooks'; import Text from '../../../component-library/components/Texts/Text'; import AvatarGroup from '../../../component-library/components/Avatars/AvatarGroup'; -import { formatAddress } from '../../../util/address'; +import { formatAddress, safeToChecksumAddress } from '../../../util/address'; import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; import { isDefaultAccountName } from '../../../util/ENSUtils'; import { strings } from '../../../../locales/i18n'; @@ -104,7 +104,8 @@ const AccountSelectorList = ({ const account = accounts.find( ({ isSelected: isAccountSelected, address: accountAddress }) => selectedAddressOverride - ? selectedAddressOverride === accountAddress + ? safeToChecksumAddress(selectedAddressOverride) === + safeToChecksumAddress(accountAddress) : isAccountSelected, ) as Account; let nextActiveAddress = account.address; @@ -206,7 +207,8 @@ const AccountSelectorList = ({ const selectedAddressOverride = selectedAddresses?.[0]; const account = accounts.find(({ isSelected, address }) => selectedAddressOverride - ? selectedAddressOverride === address + ? safeToChecksumAddress(selectedAddressOverride) === + safeToChecksumAddress(address) : isSelected, ); accountListRef?.current?.scrollToOffset({ diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index c563e519593..079e3ebc9db 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -269,7 +269,9 @@ const AccountConnect = (props: AccountConnectProps) => { const renderSingleConnectScreen = useCallback(() => { const selectedAddress = selectedAddresses[0]; const selectedAccount = accounts.find( - (account) => account.address === selectedAddress, + (account) => + safeToChecksumAddress(account.address) === + safeToChecksumAddress(selectedAddress), ); const ensName = ensByAccountAddress[selectedAddress]; const defaultSelectedAccount: Account | undefined = selectedAccount diff --git a/app/util/accounts/index.ts b/app/util/accounts/index.ts index b0157739b6a..bf104955449 100644 --- a/app/util/accounts/index.ts +++ b/app/util/accounts/index.ts @@ -3,6 +3,7 @@ import { Account, EnsByAccountAddress, } from '../../components/hooks/useAccounts'; +import { safeToChecksumAddress } from '../address'; import { isDefaultAccountName } from '../ENSUtils'; /** @@ -22,7 +23,10 @@ export const getAccountNameWithENS = ({ accounts: Account[]; ensByAccountAddress: EnsByAccountAddress; }) => { - const account = accounts.find(({ address }) => address === accountAddress); + const account = accounts.find( + ({ address }) => + safeToChecksumAddress(address) === safeToChecksumAddress(accountAddress), + ); const ensName = ensByAccountAddress[accountAddress]; return isDefaultAccountName(account?.name) && ensName ? ensName From 83ca69f7bb0591f1206de997b77cea780565c0a4 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 17 Jan 2023 12:23:15 -0800 Subject: [PATCH 63/91] Only allow removing imported wallets via seed --- app/components/UI/AccountSelectorList/AccountSelectorList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index a9b552aa8c5..c56c89efa8d 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -161,7 +161,7 @@ const AccountSelectorList = ({ onLongPress={() => { onLongPress({ address, - imported: type !== KeyringTypes.hd, + imported: type === KeyringTypes.simple, isSelected: isSelectedAccount, index, }); From dcf54ce66a1fe022951698b17ba4f2eee085583a Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 17 Jan 2023 15:10:51 -0800 Subject: [PATCH 64/91] Improve avatar network placeholder padding --- .../AvatarNetwork/AvatarNetwork.styles.ts | 2 +- app/components/Nav/Main/index.js | 6 +++++- .../UI/AccountRightButton/index.tsx | 20 ++++++++++++++---- app/components/UI/NetworkList/index.js | 12 +++++++++-- .../AccountPermissionsConnected.tsx | 9 ++++---- .../Views/Settings/NetworksSettings/index.js | 15 ++++++++++--- app/components/Views/Wallet/index.tsx | 8 +++++-- app/util/networks/index.js | 21 ++++++++++++++----- 8 files changed, 71 insertions(+), 22 deletions(-) diff --git a/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts b/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts index 4d8bae39310..35298ad4e29 100644 --- a/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts +++ b/app/component-library/components/Avatars/Avatar/variants/AvatarNetwork/AvatarNetwork.styles.ts @@ -32,7 +32,7 @@ const styleSheet = (params: { : {}; return StyleSheet.create({ base: Object.assign(baseStyle, style) as ViewStyle, - label: size === AvatarSize.Xs ? { lineHeight: 16 } : { bottom: 4 }, + label: size === AvatarSize.Xs ? { lineHeight: 16 } : { paddingBottom: 3 }, image: { flex: 1, height: undefined, diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index c6a7388acf1..1ace636e8f4 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -229,7 +229,11 @@ const Main = (props) => { prevNetworkProvider.current && networkProvider.chainId !== prevNetworkProvider.current.chainId ) { - const networkImage = getNetworkImageSource(networkProvider.chainId); + const { type, chainId } = networkProvider; + const networkImage = getNetworkImageSource({ + networkType: type, + chainId, + }); const networkName = getNetworkNameFromProvider(networkProvider); toastRef?.current?.showToast({ variant: ToastVariants.Network, diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx index b0c4938227f..3fd8e10dd1e 100644 --- a/app/components/UI/AccountRightButton/index.tsx +++ b/app/components/UI/AccountRightButton/index.tsx @@ -6,7 +6,10 @@ import AvatarAccount, { AvatarAccountType, } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; import { AccountRightButtonProps } from './AccountRightButton.types'; -import AvatarNetwork from '../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork'; +import Avatar, { + AvatarVariants, + AvatarSize, +} from '../../../component-library/components/Avatars/Avatar'; import { getNetworkImageSource, getNetworkNameFromProvider, @@ -58,8 +61,12 @@ const AccountRightButton = ({ ); const networkImageSource = useMemo( - () => getNetworkImageSource(networkProvider.chainId), - [networkProvider.chainId], + () => + getNetworkImageSource({ + networkType: networkProvider.type, + chainId: networkProvider.chainId, + }), + [networkProvider], ); const renderAvatarAccount = () => ( @@ -87,7 +94,12 @@ const AccountRightButton = ({ renderAvatarAccount() ) ) : ( - + )} ); diff --git a/app/components/UI/NetworkList/index.js b/app/components/UI/NetworkList/index.js index d61c37f9067..44beac73a09 100644 --- a/app/components/UI/NetworkList/index.js +++ b/app/components/UI/NetworkList/index.js @@ -34,7 +34,10 @@ import { NETWORK_SCROLL_ID, } from '../../../../wdio/features/testIDs/Components/NetworkListModal.TestIds'; import ImageIcon from '../ImageIcon'; -import AvatarNetwork from '../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork'; +import Avatar, { + AvatarVariants, + AvatarSize, +} from '../../../component-library/components/Avatars/Avatar'; import { ADD_NETWORK_BUTTON } from '../../../../wdio/features/testIDs/Screens/NetworksScreen.testids'; import generateTestId from '../../../../wdio/utils/generateTestId'; @@ -282,7 +285,12 @@ export class NetworkList extends PureComponent { (image ? ( ) : ( - + ))} {!isCustomRpc && (image ? ( diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx index 4521a4abc7c..1742c9a32cb 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -50,10 +50,11 @@ const AccountPermissionsConnected = ({ () => getNetworkNameFromProvider(networkController.provider), [networkController.provider], ); - const networkImageSource = useMemo( - () => getNetworkImageSource(networkController.provider.chainId), - [networkController.provider.chainId], - ); + const networkImageSource = useMemo(() => { + const { type, chainId } = networkController.provider; + return getNetworkImageSource({ networkType: type, chainId }); + }, [networkController.provider]); + const activeAddress = selectedAddresses[0]; const { toastRef } = useContext(ToastContext); diff --git a/app/components/Views/Settings/NetworksSettings/index.js b/app/components/Views/Settings/NetworksSettings/index.js index f58607c187b..407f9f9aebd 100644 --- a/app/components/Views/Settings/NetworksSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/index.js @@ -15,7 +15,7 @@ import { fontStyles } from '../../../../styles/common'; import CustomText from '../../../../components/Base/Text'; import { getNavigationOptionsTitle } from '../../../UI/Navbar'; import { strings } from '../../../../../locales/i18n'; -import Networks, { getAllNetworks } from '../../../../util/networks'; +import Networks, { getAllNetworks, isMainNet } from '../../../../util/networks'; import StyledButton from '../../../UI/StyledButton'; import Engine from '../../../../core/Engine'; import getImage from '../../../../util/getImage'; @@ -25,6 +25,10 @@ import { ThemeContext, mockTheme } from '../../../../util/theme'; import ImageIcons from '../../../UI/ImageIcon'; import { ADD_NETWORK_BUTTON } from '../../../../../wdio/features/testIDs/Screens/NetworksScreen.testids'; import { compareSanitizedUrl } from '../../../../util/sanitizeUrl'; +import Avatar, { + AvatarSize, + AvatarVariants, +} from '../../../../component-library/components/Avatars/Avatar'; const createStyles = (colors) => StyleSheet.create({ @@ -200,7 +204,7 @@ class NetworksSettings extends PureComponent { { // Do not change. This logic must check for 'mainnet' and is used for rendering the out of the box mainnet when searching. - network === MAINNET ? ( + isMainNet(network) ? ( this.renderMainnet() ) : ( ) : ( - + ))} {!isCustomRPC && ( { ); const networkImageSource = useMemo( - () => getNetworkImageSource(networkProvider.chainId), - [networkProvider.chainId], + () => + getNetworkImageSource({ + networkType: networkProvider.type, + chainId: networkProvider.chainId, + }), + [networkProvider], ); /** diff --git a/app/util/networks/index.js b/app/util/networks/index.js index 7fac6777457..753feebe67d 100644 --- a/app/util/networks/index.js +++ b/app/util/networks/index.js @@ -116,8 +116,16 @@ export default NetworkList; export const getAllNetworks = () => NetworkListKeys.filter((name) => name !== RPC); +/** + * Checks if network is default mainnet. + * + * @param {string} networkType - Type of network. + * @returns If the network is default mainnet. + */ +export const isDefaultMainnet = (networkType) => networkType === MAINNET; + export const isMainNet = (network) => - network?.provider?.type === MAINNET || network === String(1); + isDefaultMainnet(network?.provider?.type) || network === String(1); export const getDecimalChainId = (chainId) => { if (!chainId || typeof chainId !== 'string' || !chainId.startsWith('0x')) { @@ -323,14 +331,17 @@ export const getNetworkNameFromProvider = (provider) => { }; /** - * Gets the image source given the chain ID. + * Gets the image source given both the network type and the chain ID. * - * @param {string} chainId - ChainID of the network. + * @param {object} params - Params that contains information about the network. + * @param {string} params.networkType - Type of network from the provider. + * @param {string} params.chainId - ChainID of the network. * @returns {Object} - Image source of the network. */ -export const getNetworkImageSource = (chainId) => { +export const getNetworkImageSource = ({ networkType, chainId }) => { const defaultNetwork = getDefaultNetworkByChainId(chainId); - if (defaultNetwork) { + const isDefaultEthMainnet = isDefaultMainnet(networkType); + if (defaultNetwork && isDefaultEthMainnet) { return defaultNetwork.imageSource; } const popularNetwork = PopularList.find( From c522e658955d0875f1f2999a9e9699e9fe2d4a5e Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 17 Jan 2023 15:47:03 -0800 Subject: [PATCH 65/91] Improve network icon display when switching networks --- app/components/UI/NetworkInfo/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/UI/NetworkInfo/index.tsx b/app/components/UI/NetworkInfo/index.tsx index 0842c1786ba..59465c71af3 100644 --- a/app/components/UI/NetworkInfo/index.tsx +++ b/app/components/UI/NetworkInfo/index.tsx @@ -157,7 +157,7 @@ const NetworkInfo = (props: NetworkInfoProps) => { {ticker === undefined ? ( <> - ? + {nickname?.[0] || '?'} {`${nickname}` || From ce2c3f8fa201c9b9dcf551bf8d0bc3bfa9f5f22e Mon Sep 17 00:00:00 2001 From: Curtis David Date: Tue, 17 Jan 2023 20:12:36 -0500 Subject: [PATCH 66/91] E2e/permission system (#5539) * fix first set of broken tests * update tokens text * fix deep linking assertion --- app/components/UI/Navbar/index.js | 1 + app/components/UI/NavbarTitle/index.js | 2 +- .../AccountConnectSingle.tsx | 10 +++++++- e2e/pages/AccountListView.js | 3 +++ e2e/pages/Drawer/Browser.js | 4 ++-- e2e/pages/Drawer/DrawerView.js | 5 ---- e2e/pages/TransactionConfirmView.js | 4 ++-- e2e/pages/WalletView.js | 7 +++++- e2e/specs/browser-tests.spec.js | 24 ++----------------- e2e/specs/wallet-tests.spec.js | 1 + 10 files changed, 27 insertions(+), 34 deletions(-) diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index 43e55f96256..342d17eaca4 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -958,6 +958,7 @@ export function getWalletNavbarOptions( label={networkName} imageSource={networkImageSource} onPress={onPressTitle} + testID={'open-networks-button'} /> ), diff --git a/app/components/UI/NavbarTitle/index.js b/app/components/UI/NavbarTitle/index.js index 172e7bda8c5..fd7eeb9cd5f 100644 --- a/app/components/UI/NavbarTitle/index.js +++ b/app/components/UI/NavbarTitle/index.js @@ -123,7 +123,7 @@ class NavbarTitle extends PureComponent { onPress={this.openNetworkList} style={styles.wrapper} activeOpacity={this.props.disableNetwork ? 1 : 0.2} - testID={'open-networks-button'} + testID={'navbar-title-text'} > {title ? ( diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx index c9054bbbe54..8ab68cae1c1 100644 --- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx +++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx @@ -31,6 +31,12 @@ import { AccountConnectSingleProps } from './AccountConnectSingle.types'; import styleSheet from './AccountConnectSingle.styles'; import USER_INTENT from '../../../../constants/permissions'; +import { + ACCOUNT_APROVAL_MODAL_CONTAINER_ID, + CANCEL_BUTTON_ID, + CONNECT_BUTTON_ID, +} from '../../../../../app/constants/test-ids'; + const AccountConnectSingle = ({ defaultSelectedAccount, onSetScreen, @@ -103,6 +109,7 @@ const AccountConnectSingle = ({ }} size={ButtonSize.Lg} style={styles.button} + testID={CANCEL_BUTTON_ID} />