From 23b13d3075986bcf370c697b0870244e142b385b Mon Sep 17 00:00:00 2001 From: Owen Craston Date: Mon, 26 Feb 2024 21:06:46 -0800 Subject: [PATCH] remove drawer view changes --- app/components/UI/DrawerView/index.js | 702 +++++++++++++++++++++++++- 1 file changed, 700 insertions(+), 2 deletions(-) diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js index 5bf499a81485..fb48b8e3c45e 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -1,39 +1,92 @@ import React, { PureComponent } from 'react'; -import { View, StyleSheet, Text, InteractionManager } from 'react-native'; +import { + Alert, + TouchableOpacity, + View, + Image, + StyleSheet, + Text, + InteractionManager, + Platform, +} from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import Share from 'react-native-share'; +import Icon from 'react-native-vector-icons/FontAwesome'; import FeatherIcon from 'react-native-vector-icons/Feather'; +import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons'; import { fontStyles } from '../../../styles/common'; +import { + hasBlockExplorer, + findBlockExplorerForRpc, + getBlockExplorerName, + getDecimalChainId, +} from '../../../util/networks'; +import Identicon from '../Identicon'; import StyledButton from '../StyledButton'; +import { renderFromWei, renderFiat } from '../../../util/number'; import { strings } from '../../../../locales/i18n'; import Modal from 'react-native-modal'; import { toggleInfoNetworkModal, + toggleNetworkModal, toggleReceiveModal, } from '../../../actions/modals'; +import { showAlert } from '../../../actions/alert'; +import { + getEtherscanAddressUrl, + getEtherscanBaseUrl, +} from '../../../util/etherscan'; import Engine from '../../../core/Engine'; +import Logger from '../../../util/Logger'; import Device from '../../../util/device'; import ReceiveRequest from '../ReceiveRequest'; import AppConstants from '../../../core/AppConstants'; import { MetaMetricsEvents } from '../../../core/Analytics'; +import URL from 'url-parse'; +import EthereumAddress from '../EthereumAddress'; +import { getEther } from '../../../util/transactions'; +import { newAssetTransaction } from '../../../actions/transaction'; +import { protectWalletModalVisible } from '../../../actions/user'; import DeeplinkManager from '../../../core/DeeplinkManager/SharedDeeplinkManager'; +import SettingsNotification from '../SettingsNotification'; +import { RPC } from '../../../constants/network'; import { findRouteNameFromNavigatorState } from '../../../util/general'; +import { + isDefaultAccountName, + doENSReverseLookup, +} from '../../../util/ENSUtils'; +import ClipboardManager from '../../../core/ClipboardManager'; import { collectiblesSelector } from '../../../reducers/collectibles'; +import { getCurrentRoute } from '../../../reducers/navigation'; +import { ScrollView } from 'react-native-gesture-handler'; import { isZero } from '../../../util/lodash'; +import { Authentication } from '../../../core/'; import { ThemeContext, mockTheme } from '../../../util/theme'; +import { getLabelTextByAddress } from '../../../util/address'; import { onboardNetworkAction, networkSwitched, } from '../../../actions/onboardNetwork'; import Routes from '../../../constants/navigation/Routes'; import { scale } from 'react-native-size-matters'; +import generateTestId from '../../../../wdio/utils/generateTestId'; +import { DRAWER_VIEW_LOCK_TEXT_ID } from '../../../../wdio/screen-objects/testIDs/Screens/DrawerView.testIds'; import { selectNetworkConfigurations, selectProviderConfig, selectTicker, } from '../../../selectors/networkController'; +import { selectCurrentCurrency } from '../../../selectors/currencyRateController'; import { selectTokens } from '../../../selectors/tokensController'; +import { selectAccounts } from '../../../selectors/accountTrackerController'; import { selectContractBalances } from '../../../selectors/tokenBalancesController'; +import { + selectIdentities, + selectSelectedAddress, +} from '../../../selectors/preferencesController'; + +import { createAccountSelectorNavDetails } from '../../Views/AccountSelector'; import NetworkInfo from '../NetworkInfo'; import { withMetricsAwareness } from '../../../components/hooks/useMetrics'; @@ -270,6 +323,10 @@ const createStyles = (colors) => const metamask_name = require('../../../images/metamask-name.png'); // eslint-disable-line const metamask_fox = require('../../../images/fox.png'); // eslint-disable-line +const ICON_IMAGES = { + wallet: require('../../../images/wallet-icon.png'), // eslint-disable-line + 'selected-wallet': require('../../../images/selected-wallet-icon.png'), // eslint-disable-line +}; /** * View component that displays the MetaMask fox @@ -285,22 +342,66 @@ class DrawerView extends PureComponent { * Object representing the configuration of the current selected network */ providerConfig: PropTypes.object.isRequired, + /** + * Selected address as string + */ + selectedAddress: PropTypes.string, + /** + * List of accounts from the AccountTrackerController + */ + accounts: PropTypes.object, + /** + * List of accounts from the PreferencesController + */ + identities: PropTypes.object, + /** + /* Selected currency + */ + currentCurrency: PropTypes.string, + /** + * List of keyrings + */ + keyrings: PropTypes.array, + /** + * Action that toggles the network modal + */ + toggleNetworkModal: PropTypes.func, /** * Action that toggles the receive modal */ toggleReceiveModal: PropTypes.func, + /** + * Action that shows the global alert + */ + showAlert: PropTypes.func.isRequired, + /** + * Boolean that determines the status of the networks modal + */ + networkModalVisible: PropTypes.bool.isRequired, /** * Boolean that determines the status of the receive modal */ receiveModalVisible: PropTypes.bool.isRequired, + /** + * Start transaction with asset + */ + newAssetTransaction: PropTypes.func.isRequired, /** * Boolean that determines if the user has set a password before */ passwordSet: PropTypes.bool, + /** + * Wizard onboarding state + */ + wizard: PropTypes.object, /** * Current provider ticker */ ticker: PropTypes.string, + /** + * Network configurations + */ + networkConfigurations: PropTypes.object, /** * Array of ERC20 assets */ @@ -318,10 +419,26 @@ class DrawerView extends PureComponent { * An object containing token balances for current account and network in the format address => balance */ tokenBalances: PropTypes.object, + /** + * Prompts protect wallet modal + */ + protectWalletModalVisible: PropTypes.func, + /** + * Callback to close drawer + */ + onCloseDrawer: PropTypes.func, + /** + * Latest navigation route + */ + currentRoute: PropTypes.string, /** * handles action for onboarding to a network */ onboardNetworkAction: PropTypes.func, + /** + * returns switched network state + */ + switchedNetwork: PropTypes.object, /** * updates when network is switched */ @@ -342,11 +459,54 @@ class DrawerView extends PureComponent { state = { showProtectWalletModal: undefined, + account: { + ens: undefined, + name: undefined, + address: undefined, + currentChainId: undefined, + }, + networkType: undefined, + showModal: false, networkUrl: undefined, }; + browserSectionRef = React.createRef(); + currentBalance = null; previousBalance = null; + processedNewBalance = false; + animatingNetworksModal = false; + + isCurrentAccountImported() { + let ret = false; + const { keyrings, selectedAddress } = this.props; + const allKeyrings = + keyrings && keyrings.length + ? keyrings + : Engine.context.KeyringController.state.keyrings; + for (const keyring of allKeyrings) { + if (keyring.accounts.includes(selectedAddress)) { + ret = keyring.type !== 'HD Key Tree'; + break; + } + } + + return ret; + } + + renderTag() { + const colors = this.context.colors || mockTheme.colors; + const styles = createStyles(colors); + const label = getLabelTextByAddress(this.props.selectedAddress); + + return label ? ( + + + {strings(label)} + + + ) : null; + } async componentDidUpdate() { const route = findRouteNameFromNavigatorState( @@ -417,7 +577,46 @@ class DrawerView extends PureComponent { origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, }); } + await this.updateAccountInfo(); } + + updateAccountInfo = async () => { + const { identities, providerConfig, selectedAddress } = this.props; + const { currentChainId, address, name } = this.state.account; + const accountName = identities[selectedAddress]?.name; + if ( + currentChainId !== providerConfig.chainId || + address !== selectedAddress || + name !== accountName + ) { + const ens = await doENSReverseLookup( + selectedAddress, + providerConfig.chainId, + ); + this.setState((state) => ({ + account: { + ens, + name: accountName, + currentChainId: providerConfig.chainId, + address: selectedAddress, + }, + })); + } + }; + + openAccountSelector = () => { + const { navigation } = this.props; + + navigation.navigate( + ...createAccountSelectorNavDetails({ + onOpenImportAccount: this.hideDrawer, + onOpenConnectHardwareWallet: this.hideDrawer, + onSelectAccount: this.hideDrawer, + }), + ); + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_ACCOUNT_NAME); + }; + toggleReceiveModal = () => { this.props.toggleReceiveModal(); }; @@ -426,6 +625,302 @@ class DrawerView extends PureComponent { this.toggleReceiveModal(); }; + trackEvent = (event) => { + this.props.metrics.trackEvent(event); + }; + + // NOTE: do we need this event? + trackOpenBrowserEvent = () => { + const { providerConfig } = this.props; + this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_OPENED, { + source: 'In-app Navigation', + chain_id: getDecimalChainId(providerConfig.chainId), + }); + }; + + onReceive = () => { + this.toggleReceiveModal(); + this.hideDrawer(); + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_RECEIVE); + }; + + onSend = async () => { + this.props.newAssetTransaction(getEther(this.props.ticker)); + this.props.navigation.navigate('SendFlowView'); + this.hideDrawer(); + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SEND); + }; + + goToBrowser = () => { + this.props.navigation.navigate(Routes.BROWSER.HOME); + this.hideDrawer(); + // Q: duplicated analytic event? + this.trackOpenBrowserEvent(); + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_BROWSER); + }; + + showWallet = () => { + this.props.navigation.navigate('WalletTabHome'); + this.hideDrawer(); + this.trackEvent(MetaMetricsEvents.WALLET_OPENED); + }; + + onPressLock = async () => { + const { passwordSet } = this.props; + await Authentication.lockApp(); + if (!passwordSet) { + this.props.navigation.navigate('OnboardingRootNav', { + screen: Routes.ONBOARDING.NAV, + params: { screen: 'Onboarding' }, + }); + } else { + this.props.navigation.replace(Routes.ONBOARDING.LOGIN, { locked: true }); + } + }; + + lock = () => { + Alert.alert( + strings('drawer.lock_title'), + '', + [ + { + text: strings('drawer.lock_cancel'), + onPress: () => null, + style: 'cancel', + }, + { + text: strings('drawer.lock_ok'), + onPress: this.onPressLock, + }, + ], + { cancelable: false }, + ); + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_LOGOUT); + }; + + viewInEtherscan = () => { + const { selectedAddress, providerConfig, networkConfigurations } = + this.props; + if (providerConfig.type === RPC) { + const blockExplorer = findBlockExplorerForRpc( + providerConfig.rpcUrl, + networkConfigurations, + ); + const url = `${blockExplorer}/address/${selectedAddress}`; + const title = new URL(blockExplorer).hostname; + this.goToBrowserUrl(url, title); + } else { + const url = getEtherscanAddressUrl(providerConfig.type, selectedAddress); + const etherscan_url = getEtherscanBaseUrl(providerConfig.type).replace( + 'https://', + '', + ); + this.goToBrowserUrl(url, etherscan_url); + } + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_VIEW_ETHERSCAN); + }; + + submitFeedback = () => { + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SEND_FEEDBACK); + this.goToBrowserUrl( + 'https://community.metamask.io/c/feature-requests-ideas/', + strings('drawer.request_feature'), + ); + }; + + showHelp = () => { + this.props.navigation.navigate(Routes.BROWSER.HOME, { + screen: Routes.BROWSER.VIEW, + params: { + newTabUrl: 'https://support.metamask.io', + timestamp: Date.now(), + }, + }); + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_GET_HELP); + this.hideDrawer(); + }; + + goToBrowserUrl(url, title) { + this.props.navigation.navigate('Webview', { + screen: 'SimpleWebview', + params: { + url, + title, + }, + }); + this.hideDrawer(); + } + + hideDrawer = () => { + this.props.onCloseDrawer(); + }; + + hasBlockExplorer = (providerType) => { + const { networkConfigurations } = this.props; + if (providerType === RPC) { + const { + providerConfig: { rpcUrl }, + } = this.props; + const blockExplorer = findBlockExplorerForRpc( + rpcUrl, + networkConfigurations, + ); + if (blockExplorer) { + return true; + } + } + return hasBlockExplorer(providerType); + }; + + getIcon(name, size) { + const colors = this.context.colors || mockTheme.colors; + + return ( + + ); + } + + getFeatherIcon(name, size) { + const colors = this.context.colors || mockTheme.colors; + + return ( + + ); + } + + getMaterialIcon(name, size) { + const colors = this.context.colors || mockTheme.colors; + + return ( + + ); + } + + getImageIcon(name) { + const colors = this.context.colors || mockTheme.colors; + const styles = createStyles(colors); + + return ( + + ); + } + + getSelectedIcon(name, size) { + const colors = this.context.colors || mockTheme.colors; + + return ( + + ); + } + + getSelectedMaterialIcon(name, size) { + const colors = this.context.colors || mockTheme.colors; + + return ( + + ); + } + + getSelectedImageIcon(name) { + const colors = this.context.colors || mockTheme.colors; + const styles = createStyles(colors); + + return ( + + ); + } + + getSections = () => { + const { + providerConfig: { type, rpcUrl }, + networkConfigurations, + } = this.props; + let blockExplorer, blockExplorerName; + if (type === RPC) { + blockExplorer = findBlockExplorerForRpc(rpcUrl, networkConfigurations); + blockExplorerName = getBlockExplorerName(blockExplorer); + } + return [ + [ + { + name: strings('drawer.share_address'), + icon: this.getMaterialIcon('share-variant'), + action: this.onShare, + }, + { + name: + (blockExplorer && + `${strings('drawer.view_in')} ${blockExplorerName}`) || + strings('drawer.view_in_etherscan'), + icon: this.getIcon('eye'), + action: this.viewInEtherscan, + }, + ], + [ + { + name: strings('drawer.help'), + icon: this.getIcon('comments'), + action: this.showHelp, + }, + { + name: strings('drawer.request_feature'), + icon: this.getFeatherIcon('message-square'), + action: this.submitFeedback, + }, + { + name: strings('drawer.lock'), + icon: this.getFeatherIcon('log-out'), + action: this.lock, + // ...generateTestId(Platform, DRAWER_VIEW_LOCK_ICON_ID), + testID: DRAWER_VIEW_LOCK_TEXT_ID, + }, + ], + ]; + }; + + copyAccountToClipboard = async () => { + const { selectedAddress } = this.props; + await ClipboardManager.setString(selectedAddress); + this.toggleReceiveModal(); + InteractionManager.runAfterInteractions(() => { + this.props.showAlert({ + isVisible: true, + autodismiss: 1500, + content: 'clipboard-alert', + data: { msg: strings('account_details.account_copied_to_clipboard') }, + }); + }); + }; + + onShare = () => { + const { selectedAddress } = this.props; + Share.open({ + message: selectedAddress, + }) + .then(() => { + this.props.protectWalletModalVisible(); + }) + .catch((err) => { + Logger.log('Error while trying to share address', err); + }); + this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SHARE_PUBLIC_ADDRESS); + }; + onSecureWalletModalAction = () => { this.setState({ showProtectWalletModal: false }); this.props.navigation.navigate( @@ -500,16 +995,207 @@ class DrawerView extends PureComponent { }; render() { - const { providerConfig, navigation, infoNetworkModalVisible } = this.props; + const { + providerConfig, + accounts, + identities, + selectedAddress, + currentCurrency, + seedphraseBackedUp, + currentRoute, + navigation, + infoNetworkModalVisible, + } = this.props; const colors = this.context.colors || mockTheme.colors; const styles = createStyles(colors); + const { + account: { name: nameFromState, ens: ensFromState }, + } = this.state; + + const account = { + address: selectedAddress, + name: nameFromState, + ens: ensFromState, + ...identities[selectedAddress], + ...accounts[selectedAddress], + }; + const { name, ens } = account; + account.balance = + (accounts[selectedAddress] && + renderFromWei(accounts[selectedAddress].balance)) || + 0; const fiatBalance = Engine.getTotalFiatAccountBalance(); if (fiatBalance !== this.previousBalance) { this.previousBalance = this.currentBalance; } + this.currentBalance = fiatBalance; + const fiatBalanceStr = renderFiat(this.currentBalance, currentCurrency); + const accountName = isDefaultAccountName(name) && ens ? ens : name; return ( + + + + + + + + + + + + + + + + + + {accountName} + + + + {fiatBalanceStr} + + {this.renderTag()} + + + + + + + + + {strings('drawer.send_button')} + + + + + + + + {strings('drawer.receive_button')} + + + + + + {this.getSections().map( + (section, i) => + section?.length > 0 && ( + + {section + .filter((item) => { + if (!item) return undefined; + const { name = undefined } = item; + if ( + name && + name.toLowerCase().indexOf('etherscan') !== -1 + ) { + const type = providerConfig?.type; + return ( + (type && this.hasBlockExplorer(type)) || undefined + ); + } + return true; + }) + .map((item, j) => ( + item.action()} // eslint-disable-line + > + {item.icon + ? item.routeNames && + item.routeNames.includes(currentRoute) + ? item.selectedIcon + : item.icon + : null} + + {item.name} + + {!seedphraseBackedUp && item.warning ? ( + + + {item.warning} + + + ) : null} + + ))} + + ), + )} + + + ({ providerConfig: selectProviderConfig(state), + accounts: selectAccounts(state), + selectedAddress: selectSelectedAddress(state), + identities: selectIdentities(state), networkConfigurations: selectNetworkConfigurations(state), + currentCurrency: selectCurrentCurrency(state), + keyrings: state.engine.backgroundState.KeyringController.keyrings, networkModalVisible: state.modals.networkModalVisible, receiveModalVisible: state.modals.receiveModalVisible, infoNetworkModalVisible: state.modals.infoNetworkModalVisible, @@ -563,10 +1254,17 @@ const mapStateToProps = (state) => ({ tokenBalances: selectContractBalances(state), collectibles: collectiblesSelector(state), seedphraseBackedUp: state.user.seedphraseBackedUp, + currentRoute: getCurrentRoute(state), + switchedNetwork: state.networkOnboarded.switchedNetwork, }); const mapDispatchToProps = (dispatch) => ({ + toggleNetworkModal: () => dispatch(toggleNetworkModal()), toggleReceiveModal: () => dispatch(toggleReceiveModal()), + showAlert: (config) => dispatch(showAlert(config)), + newAssetTransaction: (selectedAsset) => + dispatch(newAssetTransaction(selectedAsset)), + protectWalletModalVisible: () => dispatch(protectWalletModalVisible()), onboardNetworkAction: (chainId) => dispatch(onboardNetworkAction(chainId)), networkSwitched: ({ networkUrl, networkStatus }) => dispatch(networkSwitched({ networkUrl, networkStatus })),