diff --git a/.js.env.example b/.js.env.example
index fd39a84c0d33..661eeafe5e18 100644
--- a/.js.env.example
+++ b/.js.env.example
@@ -99,6 +99,9 @@ export MM_ENABLE_SETTINGS_PAGE_DEV_OPTIONS="true"
# Per dapp selected network feature flag
export MM_PER_DAPP_SELECTED_NETWORK=""
+# Remove global network selector flag
+export MM_REMOVE_GLOBAL_NETWORK_SELECTOR=""
+
export MM_CHAIN_PERMISSIONS="true"
# Permissions Settings feature flag specific to UI changes
diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js
index 2a9af3ba0b16..9b4f5a562fcc 100644
--- a/app/components/UI/Navbar/index.js
+++ b/app/components/UI/Navbar/index.js
@@ -358,9 +358,8 @@ export function getPaymentRequestOptionsTitle(
const goBack = route.params?.dispatch;
const innerStyles = StyleSheet.create({
headerTitleStyle: {
- fontSize: 20,
- color: themeColors.text.default,
- ...fontStyles.normal,
+ justifyContent: 'center',
+ alignItems: 'center',
},
headerIcon: {
color: themeColors.primary.default,
@@ -370,11 +369,18 @@ export function getPaymentRequestOptionsTitle(
shadowColor: importedColors.transparent,
elevation: 0,
},
+ headerCloseButton: {
+ marginRight: 16,
+ },
});
return {
- title,
- headerTitleStyle: innerStyles.headerTitleStyle,
+ headerTitleAlign: 'center',
+ headerTitle: () => (
+
+ {title}
+
+ ),
headerLeft: () =>
goBack ? (
// eslint-disable-next-line react/jsx-no-bind
@@ -393,17 +399,13 @@ export function getPaymentRequestOptionsTitle(
),
headerRight: () => (
- // eslint-disable-next-line react/jsx-no-bind
- navigation.pop()}
- style={styles.closeButton}
- >
-
-
+ style={innerStyles.headerCloseButton}
+ testID={RequestPaymentViewSelectors.BACK_BUTTON_ID}
+ />
),
headerStyle: innerStyles.headerStyle,
headerTintColor: themeColors.primary.default,
diff --git a/app/components/UI/Navbar/index.test.jsx b/app/components/UI/Navbar/index.test.jsx
index cb8d1e930ad1..cb795b9a9611 100644
--- a/app/components/UI/Navbar/index.test.jsx
+++ b/app/components/UI/Navbar/index.test.jsx
@@ -1,6 +1,7 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
+import { fireEvent } from '@testing-library/react-native';
import renderWithProvider from '../../../util/test/renderWithProvider';
import { backgroundState } from '../../../util/test/initial-root-state';
import {
@@ -28,7 +29,11 @@ describe('getNetworkNavbarOptions', () => {
const TestNavigator = ({ options }) => (
- options.header()} />
+ null}
+ options={options}
+ />
);
@@ -42,9 +47,10 @@ describe('getNetworkNavbarOptions', () => {
'Test Title',
false,
mockNavigation,
+ mockTheme.colors
);
- const { getByText, getByRole } = renderWithProvider(
+ const { getByText } = renderWithProvider(
,
{
state: {
diff --git a/app/components/UI/PaymentRequest/__snapshots__/index.test.tsx.snap b/app/components/UI/PaymentRequest/__snapshots__/index.test.tsx.snap
index e5380faebfda..348f2c469b6f 100644
--- a/app/components/UI/PaymentRequest/__snapshots__/index.test.tsx.snap
+++ b/app/components/UI/PaymentRequest/__snapshots__/index.test.tsx.snap
@@ -459,3 +459,543 @@ exports[`PaymentRequest renders correctly 1`] = `
`;
+
+exports[`PaymentRequest renders correctly with network picker when feature flag is enabled 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Choose an asset to request
+
+
+
+
+
+
+
+
+
+
+ Top picks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ETH
+
+
+ Ether
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SAI
+
+
+ Sai Stablecoin v1.0
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/UI/PaymentRequest/index.js b/app/components/UI/PaymentRequest/index.js
index 192780a80c05..fb6f5f9a375b 100644
--- a/app/components/UI/PaymentRequest/index.js
+++ b/app/components/UI/PaymentRequest/index.js
@@ -45,12 +45,18 @@ import { getTicker } from '../../../util/transactions';
import { toLowerCaseEquals } from '../../../util/general';
import { utils as ethersUtils } from 'ethers';
import { ThemeContext, mockTheme } from '../../../util/theme';
-import { isTestNet } from '../../../util/networks';
+import {
+ isTestNet,
+ getDecimalChainId,
+ isRemoveGlobalNetworkSelectorEnabled,
+} from '../../../util/networks';
import { isTokenDetectionSupportedForNetwork } from '@metamask/assets-controllers';
import {
selectChainId,
selectEvmTicker,
+ selectNetworkConfigurations,
} from '../../../selectors/networkController';
+import { selectNetworkImageSource } from '../../../selectors/networkInfos';
import {
selectConversionRate,
selectCurrentCurrency,
@@ -59,8 +65,11 @@ import { selectTokenListArray } from '../../../selectors/tokenListController';
import { selectTokens } from '../../../selectors/tokensController';
import { selectContractExchangeRates } from '../../../selectors/tokenRatesController';
import { selectSelectedInternalAccountFormattedAddress } from '../../../selectors/accountsController';
-
+import PickerNetwork from '../../../component-library/components/Pickers/PickerNetwork/PickerNetwork';
+import Routes from '../../../constants/navigation/Routes';
+import { withMetricsAwareness } from '../../../components/hooks/useMetrics';
import { RequestPaymentViewSelectors } from '../../../../e2e/selectors/Receive/RequestPaymentView.selectors';
+import { MetaMetricsEvents } from '../../../core/Analytics';
const KEYBOARD_OFFSET = 120;
const createStyles = (colors) =>
@@ -302,6 +311,18 @@ class PaymentRequest extends PureComponent {
* Object that represents the current route info like params passed to it
*/
route: PropTypes.object,
+ /**
+ * Metrics injected by withMetricsAwareness HOC
+ */
+ metrics: PropTypes.object,
+ /**
+ * Network configurations
+ */
+ networkConfigurations: PropTypes.object,
+ /**
+ * Network image source
+ */
+ networkImageSource: PropTypes.string,
};
amountInput = React.createRef();
@@ -868,13 +889,39 @@ class PaymentRequest extends PureComponent {
);
};
+ handleNetworkPickerPress = () => {
+ this.props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, {
+ screen: Routes.SHEET.NETWORK_SELECTOR,
+ });
+ this.props.metrics.trackEvent(
+ this.props.metrics
+ .createEventBuilder(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED)
+ .addProperties({
+ chain_id: getDecimalChainId(this.props.chainId),
+ })
+ .build(),
+ );
+ };
+
render() {
const { mode } = this.state;
const colors = this.context.colors || mockTheme.colors;
const styles = createStyles(colors);
+ const networkName =
+ this.props.networkConfigurations?.[this.props.chainId]?.name;
+ const networkImageSource = this.props.networkImageSource;
return (
+ {isRemoveGlobalNetworkSelectorEnabled() && (
+
+
+
+ )}
({
ticker: selectEvmTicker(state),
chainId: selectChainId(state),
tokenList: selectTokenListArray(state),
+ networkConfigurations: selectNetworkConfigurations(state),
+ networkImageSource: selectNetworkImageSource(state),
});
-export default connect(mapStateToProps)(PaymentRequest);
+export default withMetricsAwareness(connect(mapStateToProps)(PaymentRequest));
diff --git a/app/components/UI/PaymentRequest/index.test.tsx b/app/components/UI/PaymentRequest/index.test.tsx
index 3eb9798cc25f..075df9bcd613 100644
--- a/app/components/UI/PaymentRequest/index.test.tsx
+++ b/app/components/UI/PaymentRequest/index.test.tsx
@@ -8,9 +8,14 @@ import {
import PaymentRequest from './index';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
+import { SolScope } from '@metamask/keyring-api';
import { ThemeContext, mockTheme } from '../../../util/theme';
import { MOCK_ACCOUNTS_CONTROLLER_STATE } from '../../../util/test/accountsControllerTestUtils';
-import { SolScope } from '@metamask/keyring-api';
+import Routes from '../../../constants/navigation/Routes';
+import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors';
+// eslint-disable-next-line import/no-namespace
+import * as networks from '../../../util/networks';
+import ethLogo from '../../../assets/images/eth-logo.png';
jest.mock('react', () => ({
...jest.requireActual('react'),
@@ -131,6 +136,7 @@ const renderComponent = (props = {}) =>
@@ -143,6 +149,18 @@ describe('PaymentRequest', () => {
expect(toJSON()).toMatchSnapshot();
});
+ it('renders correctly with network picker when feature flag is enabled', () => {
+ jest
+ .spyOn(networks, 'isRemoveGlobalNetworkSelectorEnabled')
+ .mockReturnValue(true);
+
+ const { toJSON } = renderComponent({
+ chainId: '0x1',
+ networkImageSource: ethLogo,
+ });
+ expect(toJSON()).toMatchSnapshot();
+ });
+
it('displays the correct title for asset selection', () => {
const { getByText } = renderComponent();
expect(getByText('Choose an asset to request')).toBeTruthy();
@@ -202,4 +220,56 @@ describe('PaymentRequest', () => {
expect(mockSetShowError).toHaveBeenCalledWith(true);
expect(queryByText('Invalid request, please try again')).toBeTruthy();
});
+
+ describe('handleNetworkPickerPress', () => {
+ it('should navigate to network selector modal when feature flag is enabled', () => {
+ jest
+ .spyOn(networks, 'isRemoveGlobalNetworkSelectorEnabled')
+ .mockReturnValue(true);
+
+ const mockMetrics = {
+ trackEvent: jest.fn(),
+ createEventBuilder: jest.fn(() => ({
+ addProperties: jest.fn(() => ({
+ build: jest.fn(() => 'builtEvent'),
+ })),
+ })),
+ };
+
+ const { getByTestId } = renderComponent({
+ metrics: mockMetrics,
+ chainId: '0x1',
+ networkImageSource: ethLogo,
+ });
+
+ const networkPicker = getByTestId(
+ WalletViewSelectorsIDs.NAVBAR_NETWORK_PICKER,
+ );
+
+ act(() => {
+ fireEvent.press(networkPicker);
+ });
+
+ expect(mockNavigation.navigate).toHaveBeenCalledWith(
+ Routes.MODAL.ROOT_MODAL_FLOW,
+ {
+ screen: Routes.SHEET.NETWORK_SELECTOR,
+ },
+ );
+ });
+
+ it('should not render network picker when feature flag is disabled', () => {
+ // Feature flag is already set to false in beforeEach
+ const { queryByTestId } = renderComponent({
+ chainId: '0x1',
+ networkImageSource: ethLogo,
+ });
+
+ const networkPicker = queryByTestId(
+ WalletViewSelectorsIDs.NAVBAR_NETWORK_PICKER,
+ );
+
+ expect(networkPicker).toBeNull();
+ });
+ });
});
diff --git a/app/util/networks/index.js b/app/util/networks/index.js
index b83edf09bbf6..c4cf7c395713 100644
--- a/app/util/networks/index.js
+++ b/app/util/networks/index.js
@@ -61,7 +61,11 @@ import {
} from '../../selectors/multichainNetworkController';
import { formatBlockExplorerAddressUrl } from '../../core/Multichain/networks';
import { CHAIN_IDS } from '@metamask/transaction-controller';
-import { isCaipChainId, KnownCaipNamespace, parseCaipChainId } from '@metamask/utils';
+import {
+ isCaipChainId,
+ KnownCaipNamespace,
+ parseCaipChainId,
+} from '@metamask/utils';
/**
* List of the supported networks
@@ -555,14 +559,14 @@ const getEvmNetworkImageSource = ({ networkType, chainId }) => {
export const getNetworkImageSource = ({ networkType, chainId }) => {
let hexChainId = chainId;
if (isCaipChainId(chainId)) {
- const {namespace, reference} = parseCaipChainId(chainId);
+ const { namespace, reference } = parseCaipChainId(chainId);
if (namespace !== KnownCaipNamespace.Eip155) {
return getNonEvmNetworkImageSourceByChainId(chainId);
}
hexChainId = toHex(reference === '0' ? '1' : reference); // default to mainnet if chainId is 0
}
- return getEvmNetworkImageSource({ networkType, chainId: hexChainId});
+ return getEvmNetworkImageSource({ networkType, chainId: hexChainId });
};
/**
@@ -639,6 +643,9 @@ export const isPerDappSelectedNetworkEnabled = () =>
export const isPortfolioViewEnabled = () =>
process.env.PORTFOLIO_VIEW === 'true';
+export const isRemoveGlobalNetworkSelectorEnabled = () =>
+ process.env.MM_REMOVE_GLOBAL_NETWORK_SELECTOR === 'true';
+
// The whitelisted network names for the given chain IDs to prevent showing warnings on Network Settings.
export const WHILELIST_NETWORK_NAME = {
[ChainId.mainnet]: 'Mainnet',
diff --git a/bitrise.yml b/bitrise.yml
index e743ed0f93a4..15dc093b32a1 100644
--- a/bitrise.yml
+++ b/bitrise.yml
@@ -2365,6 +2365,9 @@ app:
- opts:
is_expand: false
MM_PER_DAPP_SELECTED_NETWORK: false
+ - opts:
+ is_expand: false
+ MM_REMOVE_GLOBAL_NETWORK_SELECTOR: false
- opts:
is_expand: false
MM_CHAIN_PERMISSIONS: true