diff --git a/app/component-library/components/Pickers/PickerAccount/PickerAccount.styles.ts b/app/component-library/components/Pickers/PickerAccount/PickerAccount.styles.ts index 9bf03d0adbc..d4d99c40475 100644 --- a/app/component-library/components/Pickers/PickerAccount/PickerAccount.styles.ts +++ b/app/component-library/components/Pickers/PickerAccount/PickerAccount.styles.ts @@ -3,7 +3,6 @@ import { StyleSheet, ViewStyle } from 'react-native'; // External dependencies. import { Theme } from '../../../../util/theme/models'; -import { fontStyles } from '../../../../styles/common'; // Internal dependencies. import { PickerAccountStyleSheetVars } from './PickerAccount.types'; @@ -24,34 +23,39 @@ const styleSheet = (params: { const { colors } = theme; const { style, cellAccountContainerStyle } = vars; return StyleSheet.create({ - base: Object.assign({} as ViewStyle, style) as ViewStyle, + base: { + ...(style as ViewStyle), + flexDirection: 'row', + padding: 0, + borderWidth: 0, + }, accountAvatar: { - marginRight: 16, + marginRight: 8, }, accountAddressLabel: { color: colors.text.alternative, + textAlign: 'center', }, cellAccount: { - flex: 1, flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', ...cellAccountContainerStyle, }, accountNameLabel: { + alignItems: 'center', + justifyContent: 'center', + }, + accountNameAvatar: { flexDirection: 'row', alignItems: 'center', - justifyContent: 'flex-start', }, - accountNameLabelText: { - marginTop: 4, - marginHorizontal: 5, - paddingHorizontal: 5, - ...fontStyles.bold, - color: colors.text.alternative, - borderWidth: 1, - borderRadius: 10, - borderColor: colors.border.default, + pickerAccountContainer: { justifyContent: 'center', - textAlign: 'center', + alignItems: 'center', + }, + dropDownIcon: { + marginLeft: 8, }, }); }; diff --git a/app/component-library/components/Pickers/PickerAccount/PickerAccount.tsx b/app/component-library/components/Pickers/PickerAccount/PickerAccount.tsx index 4093e7da1ae..0bec81483f0 100644 --- a/app/component-library/components/Pickers/PickerAccount/PickerAccount.tsx +++ b/app/component-library/components/Pickers/PickerAccount/PickerAccount.tsx @@ -9,6 +9,7 @@ import Avatar, { AvatarSize, AvatarVariant } from '../../Avatars/Avatar'; import Text, { TextVariant } from '../../Texts/Text'; import { formatAddress } from '../../../../util/address'; import { useStyles } from '../../../hooks'; +import { IconSize } from '../../Icons/Icon'; // Internal dependencies. import PickerBase from '../PickerBase'; @@ -25,7 +26,6 @@ const PickerAccount: React.ForwardRefRenderFunction< accountAddress, accountName, accountAvatarType, - accountTypeLabel, showAddress = true, cellAccountContainerStyle = {}, ...props @@ -40,33 +40,46 @@ const PickerAccount: React.ForwardRefRenderFunction< const renderCellAccount = () => ( - - - {accountName} - - {showAddress && ( - - {shortenedAddress} + + + + {accountName} - )} + ); return ( - - {renderCellAccount()} - + + + {renderCellAccount()} + + {showAddress && ( + + {shortenedAddress} + + )} + ); }; diff --git a/app/component-library/components/Pickers/PickerAccount/__snapshots__/PickerAccount.test.tsx.snap b/app/component-library/components/Pickers/PickerAccount/__snapshots__/PickerAccount.test.tsx.snap index 0afdb8affeb..a078072c4da 100644 --- a/app/component-library/components/Pickers/PickerAccount/__snapshots__/PickerAccount.test.tsx.snap +++ b/app/component-library/components/Pickers/PickerAccount/__snapshots__/PickerAccount.test.tsx.snap @@ -1,223 +1,242 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PickerAccount should render correctly 1`] = ` - - - - - - - + + + + + + + + + + - - + } + testID="account-label" + > + Orangefox.eth + + - - - Orangefox.eth - - - 0x2990...a21a - - - - + + - + > + 0x2990...a21a + + `; diff --git a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.test.tsx b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.test.tsx index 948721d6ac4..65666c2e125 100644 --- a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.test.tsx +++ b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.test.tsx @@ -45,6 +45,20 @@ describe('PickerNetwork', () => { ).toBeNull(); }); + it('shows network name when hideNetworkName is false', () => { + const { queryByTestId } = render( + , + ); + + expect( + queryByTestId(WalletViewSelectorsIDs.NAVBAR_NETWORK_TEXT), + ).not.toBeNull(); + }); + it('calls onPress when pressed', () => { const onPress = jest.fn(); const { getByTestId } = render( diff --git a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.tsx b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.tsx index 1b4642ba967..29c48def333 100644 --- a/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.tsx +++ b/app/component-library/components/Pickers/PickerNetwork/PickerNetwork.tsx @@ -34,6 +34,8 @@ const PickerNetwork = ({ size={AvatarSize.Xs} name={label} imageSource={imageSource} + testID={WalletViewSelectorsIDs.NAVBAR_NETWORK_PICKER} + accessibilityLabel={label} /> {hideNetworkName ? null : ( 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 f59fab74933..c2965c38bb0 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 @@ -19,6 +19,7 @@ exports[`PickerNetwork renders correctly 1`] = ` style={null} > ( name={Routes.SHEET.REVOKE_ALL_ACCOUNT_PERMISSIONS} component={AccountPermissionsConfirmRevokeAll} /> + { const sdkInit = useRef(); const [onboarded, setOnboarded] = useState(false); + trace({ + name: TraceName.NavInit, + parentContext: getUIStartupSpan(), + op: TraceOperation.NavInit, + }); + const triggerSetCurrentRoute = (route) => { dispatch(setCurrentRoute(route)); if (route === 'Wallet' || route === 'BrowserView') { @@ -594,9 +610,10 @@ const App = (props) => { setOnboarded(!!existingUser); try { if (existingUser) { + // This should only be called if the auth type is not password, which is not the case so consider removing it await trace( { - name: TraceName.BiometricAuthentication, + name: TraceName.AppStartBiometricAuthentication, op: TraceOperation.BiometricAuthentication, }, async () => { @@ -619,6 +636,7 @@ const App = (props) => { }), ); } + await Authentication.lockApp({ reset: false }); trackErrorAsAnalytics( 'App: Max Attempts Reached', @@ -627,9 +645,15 @@ const App = (props) => { ); } }; - appTriggeredAuth().catch((error) => { - Logger.error(error, 'App: Error in appTriggeredAuth'); - }); + appTriggeredAuth() + .catch((error) => { + Logger.error(error, 'App: Error in appTriggeredAuth'); + }) + .finally(() => { + endTrace({ name: TraceName.NavInit }); + + endTrace({ name: TraceName.UIStartup }); + }); }, [navigator, queueOfHandleDeeplinkFunctions]); const handleDeeplink = useCallback(({ error, params, uri }) => { @@ -679,8 +703,6 @@ const App = (props) => { }); if (!prevNavigator.current) { - // Setup navigator with Sentry instrumentation - routingInstrumentation.registerNavigationContainer(navigator); // Subscribe to incoming deeplinks // Branch.io documentation: https://help.branch.io/developers-hub/docs/react-native branch.subscribe((opts) => { @@ -965,7 +987,7 @@ const App = (props) => { { }; }); +const mockNavigate = jest.fn(); + +jest.mock('@react-navigation/native', () => ({ + ...jest.requireActual('@react-navigation/native'), + useNavigation: () => ({ + navigate: mockNavigate, + }), +})); + const initialState = { engine: { backgroundState: { @@ -73,8 +83,9 @@ const initialState = { const onSelectAccount = jest.fn(); const onRemoveImportedAccount = jest.fn(); - -const AccountSelectorListUseAccounts = () => { +const AccountSelectorListUseAccounts: React.FC = ({ + privacyMode = false, +}) => { const { accounts, ensByAccountAddress } = useAccounts(); return ( { accounts={accounts} ensByAccountAddress={ensByAccountAddress} isRemoveAccountEnabled + privacyMode={privacyMode} /> ); }; @@ -109,7 +121,7 @@ const renderComponent = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any state: any = {}, AccountSelectorListTest = AccountSelectorListUseAccounts, -) => renderWithProvider(, { state }); +) => renderWithProvider(, { state }); describe('AccountSelectorList', () => { beforeEach(() => { @@ -229,4 +241,46 @@ describe('AccountSelectorList', () => { expect(snapTag).toBeDefined(); }); }); + it('Text is not hidden when privacy mode is off', async () => { + const state = { + ...initialState, + privacyMode: false, + }; + + const { queryByTestId } = renderComponent(state); + + await waitFor(() => { + const businessAccountItem = queryByTestId( + `${AccountListViewSelectorsIDs.ACCOUNT_BALANCE_BY_ADDRESS_TEST_ID}-${BUSINESS_ACCOUNT}`, + ); + + expect(within(businessAccountItem).getByText(regex.eth(1))).toBeDefined(); + expect( + within(businessAccountItem).getByText(regex.usd(3200)), + ).toBeDefined(); + + expect(within(businessAccountItem).queryByText('••••••')).toBeNull(); + }); + }); + it('Text is hidden when privacy mode is on', async () => { + const state = { + ...initialState, + privacyMode: true, + }; + + const { queryByTestId } = renderComponent(state); + + await waitFor(() => { + const businessAccountItem = queryByTestId( + `${AccountListViewSelectorsIDs.ACCOUNT_BALANCE_BY_ADDRESS_TEST_ID}-${BUSINESS_ACCOUNT}`, + ); + + expect(within(businessAccountItem).queryByText(regex.eth(1))).toBeNull(); + expect( + within(businessAccountItem).queryByText(regex.usd(3200)), + ).toBeNull(); + + expect(within(businessAccountItem).getByText('••••••')).toBeDefined(); + }); + }); }); diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 27405b354f7..30b8241836f 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -1,17 +1,19 @@ // Third party dependencies. import React, { useCallback, useRef } from 'react'; -import { Alert, ListRenderItem, View } from 'react-native'; +import { Alert, ListRenderItem, View, ViewStyle } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { useSelector } from 'react-redux'; +import { useNavigation } from '@react-navigation/native'; import { KeyringTypes } from '@metamask/keyring-controller'; import type { Hex } from '@metamask/utils'; // External dependencies. +import { selectInternalAccounts } from '../../../selectors/accountsController'; import Cell, { CellVariant, } from '../../../component-library/components/Cells/Cell'; +import { InternalAccount } from '@metamask/keyring-api'; import { useStyles } from '../../../component-library/hooks'; -import { selectPrivacyMode } from '../../../selectors/preferencesController'; import { TextColor } from '../../../component-library/components/Texts/Text'; import SensitiveText, { SensitiveTextLength, @@ -29,11 +31,13 @@ import { AvatarVariant } from '../../../component-library/components/Avatars/Ava import { Account, Assets } from '../../hooks/useAccounts'; import UntypedEngine from '../../../core/Engine'; import { removeAccountsFromPermissions } from '../../../core/Permissions'; +import Routes from '../../../constants/navigation/Routes'; // Internal dependencies. import { AccountSelectorListProps } from './AccountSelectorList.types'; import styleSheet from './AccountSelectorList.styles'; import { AccountListViewSelectorsIDs } from '../../../../e2e/selectors/AccountListView.selectors'; +import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors'; const AccountSelectorList = ({ onSelectAccount, @@ -47,8 +51,10 @@ const AccountSelectorList = ({ isSelectionDisabled, isRemoveAccountEnabled = false, isAutoScrollEnabled = true, + privacyMode = false, ...props }: AccountSelectorListProps) => { + const { navigate } = useNavigation(); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any const Engine = UntypedEngine as any; @@ -64,7 +70,8 @@ const AccountSelectorList = ({ ? AvatarAccountType.Blockies : AvatarAccountType.JazzIcon, ); - const privacyMode = useSelector(selectPrivacyMode); + + const internalAccounts = useSelector(selectInternalAccounts); const getKeyExtractor = ({ address }: Account) => address; const renderAccountBalances = useCallback( @@ -170,6 +177,23 @@ const AccountSelectorList = ({ ], ); + const onNavigateToAccountActions = useCallback( + (selectedAccount: string) => { + const account = internalAccounts.find( + (accountData: InternalAccount) => + accountData.address.toLowerCase() === selectedAccount.toLowerCase(), + ); + + if (!account) return; + + navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.ACCOUNT_ACTIONS, + params: { selectedAccount: account }, + }); + }, + [navigate, internalAccounts], + ); + const renderAccountItem: ListRenderItem = useCallback( ({ item: { name, address, assets, type, isSelected, balanceError }, @@ -183,7 +207,7 @@ const AccountSelectorList = ({ const isDisabled = !!balanceError || isLoading || isSelectionDisabled; const cellVariant = isMultiSelect ? CellVariant.MultiSelect - : CellVariant.Select; + : CellVariant.SelectWithMenu; let isSelectedAccount = isSelected; if (selectedAddresses) { const lowercasedSelectedAddresses = selectedAddresses.map( @@ -194,12 +218,16 @@ const AccountSelectorList = ({ ); } - const cellStyle = { + const cellStyle: ViewStyle = { opacity: isLoading ? 0.5 : 1, }; + if (!isMultiSelect) { + cellStyle.alignItems = 'center'; + } return ( { onLongPress({ address, @@ -212,6 +240,7 @@ const AccountSelectorList = ({ isSelected={isSelectedAccount} title={accountName} secondaryText={shortAddress} + showSecondaryTextIcon={false} tertiaryText={balanceError} onPress={() => onSelectAccount?.(address, isSelectedAccount)} avatarProps={{ @@ -222,6 +251,10 @@ const AccountSelectorList = ({ tagLabel={tagLabel} disabled={isDisabled} style={cellStyle} + buttonProps={{ + onButtonClick: () => onNavigateToAccountActions(address), + buttonTestId: `${WalletViewSelectorsIDs.ACCOUNT_ACTIONS}-${index}`, + }} > {renderRightAccessory?.(address, accountName) || (assets && renderAccountBalances(assets, address))} @@ -229,6 +262,7 @@ const AccountSelectorList = ({ ); }, [ + onNavigateToAccountActions, accountAvatarType, onSelectAccount, renderAccountBalances, diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts index 4059c710cc9..a2f651c718e 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.types.ts @@ -56,4 +56,8 @@ export interface AccountSelectorListProps * Optional boolean to enable removing accounts. */ isRemoveAccountEnabled?: boolean; + /** + * Optional boolean to indicate if privacy mode is enabled. + */ + privacyMode?: boolean; } diff --git a/app/components/UI/AccountSelectorList/__snapshots__/AccountSelector.test.tsx.snap b/app/components/UI/AccountSelectorList/__snapshots__/AccountSelector.test.tsx.snap index 036a2be8d53..dd4954812d8 100644 --- a/app/components/UI/AccountSelectorList/__snapshots__/AccountSelector.test.tsx.snap +++ b/app/components/UI/AccountSelectorList/__snapshots__/AccountSelector.test.tsx.snap @@ -57,591 +57,772 @@ exports[`AccountSelectorList renders all accounts with balances 1`] = ` onLayout={[Function]} style={null} > - - - - - - + - - - + propList={ + [ + "fill", + ] + } + width={32} + x={0} + y={0} + /> + + + + + - - - - Account 1 - - - 0xC495...D272 - - - - $3200.00 + Account 1 - - 1 ETH - + + 0xC495...D272 + + + + + + + $3200.00 + + + 1 ETH + + - + + + + - + testID="main-wallet-account-actions-0" + > + + - + - - - - - - + - - - + propList={ + [ + "fill", + ] + } + width={32} + x={0} + y={0} + /> + + + + + - - - - Account 2 - - - 0xd018...78E7 - - - - $6400.00 + Account 2 - - 2 ETH - + + 0xd018...78E7 + + + + + + + $6400.00 + + + 2 ETH + + + + + + + - + @@ -704,492 +885,672 @@ exports[`AccountSelectorList renders all accounts with right accessory 1`] = ` onLayout={[Function]} style={null} > - - - - - - + - - - - - - - + + + + + + + - Account 1 - - + Account 1 + + + + 0xC495...D272 + + + + - 0xC495...D272 - - - - - 0xC4955C0d639D99699Bfd7Ec54d9FaFEe40e4D272 - Account 1 + + 0xC4955C0d639D99699Bfd7Ec54d9FaFEe40e4D272 - Account 1 + + + + + + - + - - - - - - + - - - + propList={ + [ + "fill", + ] + } + width={32} + x={0} + y={0} + /> + + + + + - - - - Account 2 - - + Account 2 + + + + 0xd018...78E7 + + + + - 0xd018...78E7 - - - - - 0xd018538C87232FF95acbCe4870629b75640a78E7 - Account 2 + + 0xd018538C87232FF95acbCe4870629b75640a78E7 - Account 2 + + + + + + - + @@ -1252,591 +1613,772 @@ exports[`AccountSelectorList renders correctly 1`] = ` onLayout={[Function]} style={null} > - - - - - - + - - - + propList={ + [ + "fill", + ] + } + width={32} + x={0} + y={0} + /> + + + + + - - - - Account 1 - - - 0xC495...D272 - - - - $3200.00 + Account 1 - - 1 ETH - + + 0xC495...D272 + + + + + + + $3200.00 + + + 1 ETH + + - + + + + - + testID="main-wallet-account-actions-0" + > + + - + - - - - - - + - - - + propList={ + [ + "fill", + ] + } + width={32} + x={0} + y={0} + /> + + + + + - - - - Account 2 - - - 0xd018...78E7 - - - - $6400.00 + Account 2 - - 2 ETH - + + 0xd018...78E7 + + + + + + + $6400.00 + + + 2 ETH + + + + + + + - + @@ -1896,309 +2438,490 @@ exports[`AccountSelectorList should render all accounts but only the balance for onLayout={[Function]} style={null} > - - - - - - - Account 1 - - + style={ + { + "flex": 1, + } + } + /> + - $3200.00 + Account 1 - - 1 ETH - + + 0xC495...D272 + + + + + + + $3200.00 + + + 1 ETH + + - + + + + - + testID="main-wallet-account-actions-0" + > + + - + - - - - - - - Account 2 - - + + - 0xd018...78E7 - + + Account 2 + + + + 0xd018...78E7 + + + + + + + + - + diff --git a/app/components/UI/AddressCopy/AddressCopy.styles.ts b/app/components/UI/AddressCopy/AddressCopy.styles.ts index 089c48d5136..46d5ba9d066 100644 --- a/app/components/UI/AddressCopy/AddressCopy.styles.ts +++ b/app/components/UI/AddressCopy/AddressCopy.styles.ts @@ -1,26 +1,14 @@ import { StyleSheet } from 'react-native'; -// External dependencies. -import { Theme } from '../../../util/theme/models'; -const styleSheet = (params: { theme: Theme }) => { - const { theme } = params; - const { colors } = theme; - - return StyleSheet.create({ +const styleSheet = () => + StyleSheet.create({ address: { flexDirection: 'row', alignItems: 'center', }, copyButton: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: colors.primary.muted, - borderRadius: 20, - paddingHorizontal: 12, padding: 4, - marginLeft: 12, }, - icon: { marginLeft: 4 }, }); -}; + export default styleSheet; diff --git a/app/components/UI/AddressCopy/AddressCopy.tsx b/app/components/UI/AddressCopy/AddressCopy.tsx index d295c5f75ad..a99e46a4727 100644 --- a/app/components/UI/AddressCopy/AddressCopy.tsx +++ b/app/components/UI/AddressCopy/AddressCopy.tsx @@ -3,12 +3,7 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; // External dependencies -import Text, { - TextColor, - TextVariant, -} from '../../../component-library/components/Texts/Text'; import { TouchableOpacity } from 'react-native-gesture-handler'; -import { formatAddress } from '../../../util/address'; import Icon, { IconColor, IconName, @@ -25,12 +20,11 @@ import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletV // Internal dependencies import styleSheet from './AddressCopy.styles'; -import { AddressCopyProps } from './AddressCopy.types'; import { selectSelectedInternalAccount } from '../../../selectors/accountsController'; import { useMetrics } from '../../../components/hooks/useMetrics'; import { toChecksumHexAddress } from '@metamask/controller-utils'; -const AddressCopy = ({ formatAddressType = 'full' }: AddressCopyProps) => { +const AddressCopy = () => { const { styles } = useStyles(styleSheet, {}); const dispatch = useDispatch(); @@ -69,28 +63,15 @@ const AddressCopy = ({ formatAddressType = 'full' }: AddressCopyProps) => { }; return ( - - {strings('asset_overview.address')}: - - - {selectedInternalAccount - ? formatAddress(selectedInternalAccount.address, formatAddressType) - : null} - diff --git a/app/components/UI/AddressCopy/AddressCopy.types.ts b/app/components/UI/AddressCopy/AddressCopy.types.ts deleted file mode 100644 index 6efa05bbea0..00000000000 --- a/app/components/UI/AddressCopy/AddressCopy.types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface AddressCopyProps { - formatAddressType?: 'short' | 'mid' | 'full'; -} diff --git a/app/components/UI/ConfirmAddAsset/ConfirmAddAsset.test.tsx b/app/components/UI/ConfirmAddAsset/ConfirmAddAsset.test.tsx index ed1a2f5fdc3..99054057d7e 100644 --- a/app/components/UI/ConfirmAddAsset/ConfirmAddAsset.test.tsx +++ b/app/components/UI/ConfirmAddAsset/ConfirmAddAsset.test.tsx @@ -44,6 +44,7 @@ jest.mock('../../../util/navigation/navUtils', () => ({ ticker: 'ETH', addTokenList: jest.fn(), }), + createNavigationDetails: jest.fn(), })); const mockUseBalanceInitialValue: Partial> = { @@ -101,14 +102,12 @@ describe('ConfirmAddAsset', () => { expect(getByText('USDT')).toBeTruthy(); expect(getByText('$27.02')).toBeTruthy(); }); - it('handles cancel button click', () => { const { getByText } = renderWithProvider(, { state: mockInitialState, }); const cancelButton = getByText('Cancel'); fireEvent.press(cancelButton); - expect(getByText('Are you sure you want to exit?')).toBeTruthy(); expect( getByText('Your search information will not be saved.'), diff --git a/app/components/UI/ManageNetworks/__snapshots__/ManageNetworks.test.js.snap b/app/components/UI/ManageNetworks/__snapshots__/ManageNetworks.test.js.snap index 4d89a952349..3aef46b9799 100644 --- a/app/components/UI/ManageNetworks/__snapshots__/ManageNetworks.test.js.snap +++ b/app/components/UI/ManageNetworks/__snapshots__/ManageNetworks.test.js.snap @@ -87,6 +87,7 @@ exports[`ManageNetworks should render correctly 1`] = ` style={null} > { @@ -95,7 +101,7 @@ const styles = StyleSheet.create({ disabled: { opacity: 0.3, }, - leftButtonContainer: { + rightElementContainer: { marginRight: 12, flexDirection: 'row', alignItems: 'flex-end', @@ -113,16 +119,11 @@ const styles = StyleSheet.create({ metamaskNameWrapper: { marginLeft: Device.isAndroid() ? 20 : 0, }, - fox: { - width: 24, - height: 24, + leftElementContainer: { marginLeft: 16, }, notificationsWrapper: { - position: 'relative', - flex: 1, - justifyContent: 'center', - alignItems: 'center', + marginHorizontal: 4, }, notificationsBadge: { width: 8, @@ -133,6 +134,9 @@ const styles = StyleSheet.create({ top: 2, right: 10, }, + addressCopyWrapper: { + marginHorizontal: 4, + }, }); const metamask_name = require('../../../images/metamask-name.png'); // eslint-disable-line @@ -903,12 +907,28 @@ export function getOfflineModalNavbar() { } /** - * Function that returns the navigation options - * for our wallet screen, + * Function that returns the navigation options for the wallet screen. * - * @returns {Object} - Corresponding navbar options containing headerTitle, headerTitle and headerTitle + * @param {Object} accountActionsRef - The ref object for the account actions + * @param {string} selectedAddress - The currently selected Ethereum address + * @param {string} accountName - The name of the currently selected account + * @param {string} accountAvatarType - The type of avatar for the currently selected account + * @param {string} networkName - The name of the current network + * @param {Object} networkImageSource - The image source for the network icon + * @param {Function} onPressTitle - Callback function when the title is pressed + * @param {Object} navigation - The navigation object + * @param {Object} themeColors - The theme colors object + * @param {boolean} isNotificationEnabled - Whether notifications are enabled + * @param {boolean | null} isProfileSyncingEnabled - Whether profile syncing is enabled + * @param {number} unreadNotificationCount - The number of unread notifications + * @param {number} readNotificationCount - The number of read notifications + * @returns {Object} An object containing the navbar options for the wallet screen */ export function getWalletNavbarOptions( + accountActionsRef, + selectedAddress, + accountName, + accountAvatarType, networkName, networkImageSource, onPressTitle, @@ -921,7 +941,7 @@ export function getWalletNavbarOptions( ) { const innerStyles = StyleSheet.create({ headerStyle: { - backgroundColor: themeColors.background.default, + backgroundColor: themeColors.background, shadowColor: importedColors.transparent, elevation: 0, }, @@ -1005,24 +1025,40 @@ export function getWalletNavbarOptions( return { headerTitle: () => ( + { + navigation.navigate(...createAccountSelectorNavDetails({})); + }} + accountTypeLabel={getLabelTextByAddress(selectedAddress) || undefined} + showAddress + cellAccountContainerStyle={styles.account} + testID={WalletViewSelectorsIDs.ACCOUNT_ICON} + /> + + ), + headerLeft: () => ( + ), - headerLeft: () => ( - - ), headerRight: () => ( - + + + + {isNotificationsFeatureEnabled() && ( diff --git a/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap b/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap index 8099909473b..fa6b7d2bf5b 100644 --- a/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap @@ -233,6 +233,7 @@ exports[`NetworkDetails renders correctly 1`] = ` style={null} > { const url = new URLPARSE(rpcUrl); const existingNetwork = networkConfigurationByChainId[chainId]; - CurrencyRateController.updateExchangeRate(ticker); + CurrencyRateController.updateExchangeRate([ticker]); if (!isPrivateConnection(url.hostname)) { url.set('protocol', 'https:'); diff --git a/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap b/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap index bbcf6abbafd..7b3fccd6bdf 100644 --- a/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap +++ b/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap @@ -94,6 +94,7 @@ exports[`NetworkVerificationInfo renders correctly 1`] = ` style={null} > {renderTopIcon()} - + + {!isRenderedAsBottomSheet && ( + { + navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.CONNECTION_DETAILS, + params: { + hostInfo: { + metadata: { + origin: + currentPageInformation?.url && + new URL(currentPageInformation?.url).hostname, + }, + }, + connectionDateTime: new Date().getTime(), + }, + }); + }} + testID={SDKSelectorsIDs.CONNECTION_DETAILS_BUTTON} + /> + )} + ); } @@ -150,20 +178,24 @@ const PermissionsSummary = ({ ); + const onRevokeAllHandler = useCallback(async () => { + await Engine.context.PermissionController.revokeAllPermissions(hostname); + navigate('PermissionsManager'); + }, [hostname, navigate]); + const toggleRevokeAllPermissionsModal = useCallback(() => { navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.REVOKE_ALL_ACCOUNT_PERMISSIONS, params: { hostInfo: { metadata: { - origin: - currentPageInformation?.url && - new URL(currentPageInformation?.url).hostname, + origin: hostname, }, }, + onRevokeAll: !isRenderedAsBottomSheet && onRevokeAllHandler, }, }); - }, [navigate, currentPageInformation?.url]); + }, [navigate, isRenderedAsBottomSheet, onRevokeAllHandler, hostname]); const getAccountLabel = useCallback(() => { if (isAlreadyConnected) { @@ -346,10 +378,10 @@ const PermissionsSummary = ({ {!isAlreadyConnected || isNetworkSwitch ? strings('permissions.title_dapp_url_wants_to', { - dappUrl: new URL(currentPageInformation.url).hostname, + dappUrl: hostname, }) : strings('permissions.title_dapp_url_has_approval_to', { - dappUrl: new URL(currentPageInformation.url).hostname, + dappUrl: hostname, })} diff --git a/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap b/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap index 5fb871e2b3d..dc178c8b497 100644 --- a/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap +++ b/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap @@ -99,6 +99,7 @@ exports[`PermissionsSummary should render correctly 1`] = ` { ).toMatchInlineSnapshot(` [ [ - "POL", + [ + "POL", + ], ], ] `); diff --git a/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx b/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx index d1235a4f8b4..5178c067e10 100644 --- a/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx +++ b/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx @@ -168,7 +168,7 @@ function NetworkSwitcher() { const { networkClientId } = rpcEndpoints?.[defaultRpcEndpointIndex] ?? {}; - CurrencyRateController.updateExchangeRate(ticker); + CurrencyRateController.updateExchangeRate([ticker]); NetworkController.setActiveNetwork(networkClientId); navigateToGetStarted(); } diff --git a/app/components/UI/Ramp/Views/NetworkSwitcher/__snapshots__/NetworkSwitcher.test.tsx.snap b/app/components/UI/Ramp/Views/NetworkSwitcher/__snapshots__/NetworkSwitcher.test.tsx.snap index bdf21b1ce92..1459c889bad 100644 --- a/app/components/UI/Ramp/Views/NetworkSwitcher/__snapshots__/NetworkSwitcher.test.tsx.snap +++ b/app/components/UI/Ramp/Views/NetworkSwitcher/__snapshots__/NetworkSwitcher.test.tsx.snap @@ -1065,6 +1065,7 @@ exports[`NetworkSwitcher View renders and dismisses network modal when pressing style={null} > ({ refresh: jest.fn(() => Promise.resolve()), }, CurrencyRateController: { - startPolling: jest.fn(() => Promise.resolve()), + updateExchangeRate: jest.fn(() => Promise.resolve()), }, TokenRatesController: { updateExchangeRates: jest.fn(() => Promise.resolve()), @@ -356,7 +356,7 @@ describe('Tokens', () => { Engine.context.AccountTrackerController.refresh, ).toHaveBeenCalled(); expect( - Engine.context.CurrencyRateController.startPolling, + Engine.context.CurrencyRateController.updateExchangeRate, ).toHaveBeenCalled(); expect( Engine.context.TokenRatesController.updateExchangeRates, diff --git a/app/components/UI/Tokens/index.tsx b/app/components/UI/Tokens/index.tsx index 35f7c393774..6f16616cc3f 100644 --- a/app/components/UI/Tokens/index.tsx +++ b/app/components/UI/Tokens/index.tsx @@ -11,7 +11,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; import Logger from '../../../util/Logger'; import { selectChainId, - selectNetworkClientId, + selectNetworkConfigurations, } from '../../../selectors/networkController'; import { getDecimalChainId } from '../../../util/networks'; import { isZero } from '../../../util/lodash'; @@ -75,7 +75,9 @@ const Tokens: React.FC = ({ tokens }) => { const { data: tokenBalances } = useTokenBalancesController(); const tokenSortConfig = useSelector(selectTokenSortConfig); const chainId = useSelector(selectChainId); - const networkClientId = useSelector(selectNetworkClientId); + const networkConfigurationsByChainId = useSelector( + selectNetworkConfigurations, + ); const hideZeroBalanceTokens = useSelector( (state: RootState) => state.settings.hideZeroBalanceTokens, ); @@ -83,6 +85,13 @@ const Tokens: React.FC = ({ tokens }) => { const tokenExchangeRates = useSelector(selectContractExchangeRates); const currentCurrency = useSelector(selectCurrentCurrency); const conversionRate = useSelector(selectConversionRate); + const nativeCurrencies = [ + ...new Set( + Object.values(networkConfigurationsByChainId).map( + (n) => n.nativeCurrency, + ), + ), + ]; const actionSheet = useRef(); const [tokenToRemove, setTokenToRemove] = useState(); @@ -159,9 +168,7 @@ const Tokens: React.FC = ({ tokens }) => { const actions = [ TokenDetectionController.detectTokens(), AccountTrackerController.refresh(), - CurrencyRateController.startPolling({ - networkClientId, - }), + CurrencyRateController.updateExchangeRate(nativeCurrencies), TokenRatesController.updateExchangeRates(), ]; await Promise.all(actions).catch((error) => { diff --git a/app/components/UI/WalletAccount/WalletAccount.test.tsx b/app/components/UI/WalletAccount/WalletAccount.test.tsx index 709b497bb02..d9a1de2c7d4 100644 --- a/app/components/UI/WalletAccount/WalletAccount.test.tsx +++ b/app/components/UI/WalletAccount/WalletAccount.test.tsx @@ -57,6 +57,9 @@ const mockInitialState: DeepPartial = { engine: { backgroundState: { ...backgroundState, + PreferencesController: { + privacyMode: false, + }, AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE, NetworkController: { ...mockNetworkState({ @@ -101,14 +104,21 @@ jest.mock('../../../util/ENSUtils', () => ({ }), })); +const mockSelector = jest + .fn() + .mockImplementation((callback) => callback(mockInitialState)); + jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), - useSelector: jest - .fn() - .mockImplementation((callback) => callback(mockInitialState)), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + useSelector: (selector: any) => mockSelector(selector), })); describe('WalletAccount', () => { + beforeEach(() => { + mockSelector.mockImplementation((callback) => callback(mockInitialState)); + }); + it('renders correctly', () => { const { toJSON } = renderWithProvider(, { state: mockInitialState, @@ -116,13 +126,6 @@ describe('WalletAccount', () => { expect(toJSON()).toMatchSnapshot(); }); - it('shows the account address', () => { - const { getByTestId } = renderWithProvider(, { - state: mockInitialState, - }); - expect(getByTestId(WalletViewSelectorsIDs.ACCOUNT_ADDRESS)).toBeDefined(); - }); - it('copies the account address to the clipboard when the copy button is pressed', async () => { const { getByTestId } = renderWithProvider(, { state: mockInitialState, @@ -139,7 +142,9 @@ describe('WalletAccount', () => { fireEvent.press(getByTestId(WalletViewSelectorsIDs.ACCOUNT_ICON)); expect(mockNavigate).toHaveBeenCalledWith( - ...createAccountSelectorNavDetails({}), + ...createAccountSelectorNavDetails({ + privacyMode: false, + }), ); }); it('displays the correct account name', () => { @@ -171,4 +176,47 @@ describe('WalletAccount', () => { expect(getByText(customAccountName)).toBeDefined(); }); }); + + it('should navigate to account selector with privacy mode disabled', () => { + const { getByTestId } = renderWithProvider(, { + state: mockInitialState, + }); + + fireEvent.press(getByTestId(WalletViewSelectorsIDs.ACCOUNT_ICON)); + expect(mockNavigate).toHaveBeenCalledWith( + ...createAccountSelectorNavDetails({ + privacyMode: false, + }), + ); + }); + + it('should navigate to account selector with privacy mode enabled', () => { + const stateWithPrivacyMode = { + ...mockInitialState, + engine: { + ...mockInitialState.engine, + backgroundState: { + ...mockInitialState.engine?.backgroundState, + PreferencesController: { + privacyMode: true, + }, + }, + }, + }; + + mockSelector.mockImplementation((callback) => + callback(stateWithPrivacyMode), + ); + + const { getByTestId } = renderWithProvider(, { + state: stateWithPrivacyMode, + }); + + fireEvent.press(getByTestId(WalletViewSelectorsIDs.ACCOUNT_ICON)); + expect(mockNavigate).toHaveBeenCalledWith( + ...createAccountSelectorNavDetails({ + privacyMode: true, + }), + ); + }); }); diff --git a/app/components/UI/WalletAccount/WalletAccount.tsx b/app/components/UI/WalletAccount/WalletAccount.tsx index e96ea0e1e29..63cf33fccdd 100644 --- a/app/components/UI/WalletAccount/WalletAccount.tsx +++ b/app/components/UI/WalletAccount/WalletAccount.tsx @@ -5,6 +5,7 @@ import { useNavigation } from '@react-navigation/native'; import { View } from 'react-native'; // External dependencies +import { selectPrivacyMode } from '../../../selectors/preferencesController'; import { IconName } from '../../../component-library/components/Icons/Icon'; import PickerAccount from '../../../component-library/components/Pickers/PickerAccount'; import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; @@ -24,6 +25,9 @@ import Logger from '../../../util/Logger'; // Internal dependencies import styleSheet from './WalletAccount.styles'; import { WalletAccountProps } from './WalletAccount.types'; +import { TraceName, TraceOperation, trace } from '../../../util/trace'; +import { store } from '../../../store'; +import { getTraceTags } from '../../../util/sentry/tags'; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -34,6 +38,7 @@ const WalletAccount = ({ style }: WalletAccountProps, ref: React.Ref) => { const yourAccountRef = useRef(null); const accountActionsRef = useRef(null); const selectedAccount = useSelector(selectSelectedInternalAccount); + const privacyMode = useSelector(selectPrivacyMode); const { ensName } = useEnsNameByAddress(selectedAccount?.address); const defaultName = selectedAccount?.metadata?.name; const accountName = useMemo( @@ -78,7 +83,16 @@ const WalletAccount = ({ style }: WalletAccountProps, ref: React.Ref) => { accountName={accountName} accountAvatarType={accountAvatarType} onPress={() => { - navigate(...createAccountSelectorNavDetails({})); + trace({ + name: TraceName.AccountList, + tags: getTraceTags(store.getState()), + op: TraceOperation.AccountList, + }); + navigate( + ...createAccountSelectorNavDetails({ + privacyMode, + }), + ); }} accountTypeLabel={ getLabelTextByAddress(selectedAccount?.address) || undefined @@ -90,7 +104,7 @@ const WalletAccount = ({ style }: WalletAccountProps, ref: React.Ref) => { /> - + - - - - - - + + + + + + + + + + - - - + } + testID="account-label" + > + Account 2 + + - - - Account 2 - - - - - + width={12} + /> + + - - Address - : - - - 0xC496...a756 - diff --git a/app/components/Views/AccountActions/AccountActions.test.tsx b/app/components/Views/AccountActions/AccountActions.test.tsx index 02655b77080..730761ea373 100644 --- a/app/components/Views/AccountActions/AccountActions.test.tsx +++ b/app/components/Views/AccountActions/AccountActions.test.tsx @@ -66,6 +66,18 @@ jest.mock('@react-navigation/native', () => { navigate: mockNavigate, goBack: mockGoBack, }), + useRoute: () => ({ + params: { + selectedAccount: { + address: '0xC4966c0D659D99699BFD7EB54D8fafEE40e4a756', + metadata: { + keyring: { + type: 'HD Key Tree', + }, + }, + }, + }, + }), }; }); @@ -130,7 +142,7 @@ describe('AccountActions', () => { expect(mockNavigate).toHaveBeenCalledWith('Webview', { screen: 'SimpleWebview', params: { - url: 'https://etherscan.io/address/0xc4966c0d659d99699bfd7eb54d8fafee40e4a756', + url: 'https://etherscan.io/address/0xC4966c0D659D99699BFD7EB54D8fafEE40e4a756', title: 'etherscan.io', }, }); @@ -144,7 +156,7 @@ describe('AccountActions', () => { fireEvent.press(getByTestId(AccountActionsModalSelectorsIDs.SHARE_ADDRESS)); expect(Share.open).toHaveBeenCalledWith({ - message: '0xc4966c0d659d99699bfd7eb54d8fafee40e4a756', + message: '0xC4966c0D659D99699BFD7EB54D8fafEE40e4a756', }); }); @@ -162,6 +174,14 @@ describe('AccountActions', () => { { credentialName: 'private_key', shouldUpdateNav: true, + selectedAccount: { + address: '0xC4966c0D659D99699BFD7EB54D8fafEE40e4a756', + metadata: { + keyring: { + type: 'HD Key Tree', + }, + }, + }, }, ); }); @@ -173,7 +193,16 @@ describe('AccountActions', () => { fireEvent.press(getByTestId(AccountActionsModalSelectorsIDs.EDIT_ACCOUNT)); - expect(mockNavigate).toHaveBeenCalledWith('EditAccountName'); + expect(mockNavigate).toHaveBeenCalledWith('EditAccountName', { + selectedAccount: { + address: '0xC4966c0D659D99699BFD7EB54D8fafEE40e4a756', + metadata: { + keyring: { + type: 'HD Key Tree', + }, + }, + }, + }); }); describe('clicks remove account', () => { diff --git a/app/components/Views/AccountActions/AccountActions.tsx b/app/components/Views/AccountActions/AccountActions.tsx index 99547d6cfda..c44a1bc31a1 100644 --- a/app/components/Views/AccountActions/AccountActions.tsx +++ b/app/components/Views/AccountActions/AccountActions.tsx @@ -1,11 +1,17 @@ // Third party dependencies. import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Alert, View, Text } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; +import { + useNavigation, + RouteProp, + ParamListBase, + useRoute, +} from '@react-navigation/native'; import { useDispatch, useSelector } from 'react-redux'; import Share from 'react-native-share'; -// External dependencies. +// External dependencies +import { InternalAccount } from '@metamask/keyring-api'; import BottomSheet, { BottomSheetRef, } from '../../../component-library/components/BottomSheets/BottomSheet'; @@ -25,7 +31,6 @@ import { selectNetworkConfigurations, selectProviderConfig, } from '../../../selectors/networkController'; -import { selectSelectedInternalAccount } from '../../../selectors/accountsController'; import { strings } from '../../../../locales/i18n'; // Internal dependencies import styleSheet from './AccountActions.styles'; @@ -50,7 +55,13 @@ import BlockingActionModal from '../../UI/BlockingActionModal'; import { useTheme } from '../../../util/theme'; import { Hex } from '@metamask/utils'; +interface AccountActionsParams { + selectedAccount: InternalAccount; +} + const AccountActions = () => { + const route = useRoute>(); + const { selectedAccount } = route.params as AccountActionsParams; const { colors } = useTheme(); const styles = styleSheet(colors); const sheetRef = useRef(null); @@ -67,7 +78,6 @@ const AccountActions = () => { const providerConfig = useSelector(selectProviderConfig); - const selectedAccount = useSelector(selectSelectedInternalAccount); const selectedAddress = selectedAccount?.address; const keyring = selectedAccount?.metadata.keyring; @@ -140,6 +150,7 @@ const AccountActions = () => { navigate(Routes.SETTINGS.REVEAL_PRIVATE_CREDENTIAL, { credentialName: 'private_key', shouldUpdateNav: true, + selectedAccount, }); }); }; @@ -305,7 +316,7 @@ const AccountActions = () => { ]); const goToEditAccountName = () => { - navigate('EditAccountName'); + navigate('EditAccountName', { selectedAccount }); }; const isExplorerVisible = Boolean( diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index a9f976fe494..0819d6d647e 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -13,7 +13,6 @@ import Modal from 'react-native-modal'; import { useSelector } from 'react-redux'; // External dependencies. import { strings } from '../../../../locales/i18n'; -import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; import BottomSheet, { BottomSheetRef, } from '../../../component-library/components/BottomSheets/BottomSheet'; @@ -33,7 +32,6 @@ import { } from '../../../selectors/accountsController'; import { isDefaultAccountName } from '../../../util/ENSUtils'; import Logger from '../../../util/Logger'; -import getAccountNameWithENS from '../../../util/accounts'; import { getAddressAccountType, safeToChecksumAddress, @@ -133,11 +131,6 @@ const AccountConnect = (props: AccountConnectProps) => { >([]); const { toastRef } = useContext(ToastContext); - const accountAvatarType = useSelector((state: RootState) => - state.settings.useBlockieIcon - ? AvatarAccountType.Blockies - : AvatarAccountType.JazzIcon, - ); // origin is set to the last active tab url in the browser which can conflict with sdk const inappBrowserOrigin: string = useSelector(getActiveTabUrl, isEqual); @@ -439,11 +432,6 @@ const AccountConnect = (props: AccountConnectProps) => { }; const connectedAccountLength = selectedAddresses.length; const activeAddress = selectedAddresses[0]; - const activeAccountName = getAccountNameWithENS({ - accountAddress: activeAddress, - accounts, - ensByAccountAddress, - }); try { setIsLoading(true); @@ -463,26 +451,15 @@ const AccountConnect = (props: AccountConnectProps) => { source: eventSource, }); 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') }, - ]; + + if (connectedAccountLength >= 1) { + labelOptions = [{ label: `${strings('toast.permissions_updated')}` }]; } + toastRef?.current?.showToast({ - variant: ToastVariants.Account, + variant: ToastVariants.Network, labelOptions, - accountAddress: activeAddress, - accountAvatarType, + networkImageSource: faviconSource, hasNoTimeout: false, }); } catch (e) { @@ -496,14 +473,12 @@ const AccountConnect = (props: AccountConnectProps) => { eventSource, selectedAddresses, hostInfo, - accounts, - ensByAccountAddress, - accountAvatarType, toastRef, accountsLength, channelIdOrHostname, triggerDappViewedEvent, trackEvent, + faviconSource, ]); const handleCreateAccount = useCallback( diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx index 157534a5c53..3dc556a5f01 100644 --- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx +++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx @@ -21,6 +21,7 @@ import AccountSelectorList from '../../../UI/AccountSelectorList'; import HelpText, { HelpTextSeverity, } from '../../../../component-library/components/Form/HelpText'; +import Engine from '../../../../core/Engine'; // Internal dependencies. import { ConnectAccountBottomSheetSelectorsIDs } from '../../../../../e2e/selectors/Browser/ConnectAccountBottomSheet.selectors'; @@ -84,6 +85,11 @@ const AccountConnectMultiSelector = ({ [accounts, selectedAddresses, onSelectAddress], ); + const onRevokeAllHandler = useCallback(async () => { + await Engine.context.PermissionController.revokeAllPermissions(hostname); + navigate('PermissionsManager'); + }, [hostname, navigate]); + const toggleRevokeAllAccountPermissionsModal = useCallback(() => { navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.REVOKE_ALL_ACCOUNT_PERMISSIONS, @@ -93,9 +99,10 @@ const AccountConnectMultiSelector = ({ origin: urlWithProtocol && new URL(urlWithProtocol).hostname, }, }, + onRevokeAll: !isRenderedAsBottomSheet && onRevokeAllHandler, }, }); - }, [navigate, urlWithProtocol]); + }, [navigate, urlWithProtocol, isRenderedAsBottomSheet, onRevokeAllHandler]); const renderSelectAllButton = useCallback( () => diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index ed6589becd9..42101ac25ef 100755 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -196,8 +196,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { useEffect(() => { if ( previousPermittedAccounts.current === undefined && - permittedAccountsByHostname.length === 0 && - isRenderedAsBottomSheet + permittedAccountsByHostname.length === 0 ) { // TODO - Figure out better UX instead of auto dismissing. However, we cannot be in this state as long as accounts are not connected. hideSheet(); diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConfirmRevokeAll/AccountPermissionsConfirmRevokeAll.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConfirmRevokeAll/AccountPermissionsConfirmRevokeAll.tsx index fa06c49bbc6..8d3082a2ef7 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConfirmRevokeAll/AccountPermissionsConfirmRevokeAll.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConfirmRevokeAll/AccountPermissionsConfirmRevokeAll.tsx @@ -26,6 +26,7 @@ interface AccountPermissionsConfirmRevokeAllProps { hostInfo: { metadata: { origin: string }; }; + onRevokeAll?: () => void; }; }; } @@ -37,6 +38,7 @@ const AccountPermissionsConfirmRevokeAll = ( hostInfo: { metadata: { origin: hostname }, }, + onRevokeAll, } = props.route.params; const { styles } = useStyles(styleSheet, {}); @@ -48,12 +50,18 @@ const AccountPermissionsConfirmRevokeAll = ( const revokeAllAccounts = useCallback(async () => { try { - await Engine.context.PermissionController.revokeAllPermissions(hostname); - sheetRef.current?.onCloseBottomSheet(); + if (onRevokeAll) { + onRevokeAll(); + } else { + await Engine.context.PermissionController.revokeAllPermissions( + hostname, + ); + sheetRef.current?.onCloseBottomSheet(); + } } catch (e) { Logger.log(`Failed to revoke all accounts for ${hostname}`, e); } - }, [hostname, Engine.context.PermissionController]); + }, [hostname, Engine.context.PermissionController, onRevokeAll]); const onCancel = () => { sheetRef.current?.onCloseBottomSheet(); diff --git a/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.styles.ts b/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.styles.ts new file mode 100644 index 00000000000..4cd67a1f432 --- /dev/null +++ b/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.styles.ts @@ -0,0 +1,26 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +/** + * Style sheet function for AccountConnectMultiSelector screen. + * @returns StyleSheet object. + */ +const styleSheet = () => + StyleSheet.create({ + container: { + paddingHorizontal: 16, + alignItems: 'center', + }, + descriptionContainer: { + marginBottom: 16, + }, + buttonsContainer: { + flexDirection: 'row', + gap: 16, + }, + button: { + flex: 1, + }, + }); + +export default styleSheet; diff --git a/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.tsx b/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.tsx new file mode 100644 index 00000000000..49463cd91de --- /dev/null +++ b/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.tsx @@ -0,0 +1,76 @@ +// Third party dependencies +import React, { useRef } from 'react'; + +// External dependencies +import { View } from 'react-native'; +import BottomSheetHeader from '../../../../component-library/components/BottomSheets/BottomSheetHeader'; +import Button, { + ButtonVariants, + ButtonSize, +} from '../../../../component-library/components/Buttons/Button'; +import Text, { + TextVariant, +} from '../../../../component-library/components/Texts/Text'; +import { strings } from '../../../../../locales/i18n'; +import BottomSheet, { + BottomSheetRef, +} from '../../../../component-library/components/BottomSheets/BottomSheet'; +import { useStyles } from '../../../../component-library/hooks'; +import styleSheet from './ConnectionDetails.styles'; + +interface ConnectionDetailsProps { + route: { + params: { + connectionDateTime?: number; + }; + }; +} + +const AccountPermissionsConfirmRevokeAll = (props: ConnectionDetailsProps) => { + const { connectionDateTime = 123456789 } = props.route.params; + + const { styles } = useStyles(styleSheet, {}); + + const sheetRef = useRef(null); + + const onDismiss = () => { + sheetRef.current?.onCloseBottomSheet(); + }; + + const formatConnectionDate = (timestamp: number) => + new Date(timestamp).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }); + + return ( + + + + + {strings('permissions.connection_details_title')} + + + + + {strings('permissions.connection_details_description', { + connectionDateTime: formatConnectionDate(connectionDateTime), + })} + + + +