diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index 2ca02dd680e..52b8b5d1f7f 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -122,6 +122,7 @@ import OriginSpamModal from '../../Views/OriginSpamModal/OriginSpamModal'; ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { SnapsExecutionWebView } from '../../../lib/snaps'; ///: END:ONLY_INCLUDE_IF +import isNetworkUiRedesignEnabled from '../../../util/networks/isNetworkUiRedesignEnabled'; const clearStackNavigatorOptions = { headerShown: false, @@ -924,6 +925,14 @@ const App = ({ userLoggedIn }) => { component={AddNetworkFlow} options={{ animationEnabled: true }} /> + {isNetworkUiRedesignEnabled() ? ( + + ) : null} + { - isNetworkUiRedesignEnabled && setIsSearchFieldFocused(true); + isNetworkUiRedesignEnabled() && setIsSearchFieldFocused(true); }, onBlur: () => { - isNetworkUiRedesignEnabled && setIsSearchFieldFocused(false); + isNetworkUiRedesignEnabled() && setIsSearchFieldFocused(false); }, } : {}; - const inputStylesWhichAreFeatureFlagged = !isNetworkUiRedesignEnabled + const inputStylesWhichAreFeatureFlagged = !isNetworkUiRedesignEnabled() ? styles.input : isSearchFieldFocused ? styles.input : styles.unfocusedInput; - const containerInputStylesWhichAreFeatureFlagged = !isNetworkUiRedesignEnabled - ? styles.inputWrapper - : isSearchFieldFocused - ? styles.focusedInputWrapper - : styles.inputWrapper; + const containerInputStylesWhichAreFeatureFlagged = + !isNetworkUiRedesignEnabled() + ? styles.inputWrapper + : isSearchFieldFocused + ? styles.focusedInputWrapper + : styles.inputWrapper; return ( diff --git a/app/components/Views/NetworkSelector/NetworkSelector.styles.ts b/app/components/Views/NetworkSelector/NetworkSelector.styles.ts index 868d5445cd6..80bfbddbcfa 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.styles.ts +++ b/app/components/Views/NetworkSelector/NetworkSelector.styles.ts @@ -2,8 +2,8 @@ import Device from '../../../util/device'; import { StyleSheet } from 'react-native'; import { fontStyles } from '../../../styles/common'; -import { isNetworkUiRedesignEnabled } from '../../../util/networks'; import { Colors } from '../../../util/theme/models'; +import { isNetworkUiRedesignEnabled } from '../../../util/networks/isNetworkUiRedesignEnabled'; /** * Style sheet function for NetworkSelector screen. @@ -15,7 +15,7 @@ const createStyles = (colors: Colors) => marginHorizontal: 16, marginBottom: Device.isAndroid() ? 16 - : isNetworkUiRedesignEnabled + : isNetworkUiRedesignEnabled() ? 12 : 0, }, @@ -83,7 +83,7 @@ const createStyles = (colors: Colors) => marginTop: 1, }, networkListContainer: { - height: isNetworkUiRedesignEnabled ? '100%' : undefined, + height: isNetworkUiRedesignEnabled() ? '100%' : undefined, }, networkIcon: { width: 20, diff --git a/app/components/Views/NetworkSelector/NetworkSelector.tsx b/app/components/Views/NetworkSelector/NetworkSelector.tsx index 3a2d2431d9c..80b6d194a79 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.tsx @@ -32,7 +32,6 @@ import Networks, { getDecimalChainId, isTestNet, getNetworkImageSource, - isNetworkUiRedesignEnabled, } from '../../../util/networks'; import { LINEA_MAINNET, @@ -78,6 +77,7 @@ import { ButtonsAlignment } from '../../../component-library/components/BottomSh import { ButtonProps } from '../../../component-library/components/Buttons/Button/Button.types'; import BottomSheetFooter from '../../../component-library/components/BottomSheets/BottomSheetFooter/BottomSheetFooter'; import { ExtendedNetwork } from '../Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.types'; +import { isNetworkUiRedesignEnabled } from '../../../util/networks/isNetworkUiRedesignEnabled'; const NetworkSelector = () => { const [showPopularNetworkModal, setShowPopularNetworkModal] = useState(false); @@ -95,14 +95,14 @@ const NetworkSelector = () => { const providerConfig: ProviderConfig = useSelector(selectProviderConfig); const networkConfigurations = useSelector(selectNetworkConfigurations); - const avatarSize = isNetworkUiRedesignEnabled ? AvatarSize.Sm : undefined; - const modalTitle = isNetworkUiRedesignEnabled + const avatarSize = isNetworkUiRedesignEnabled() ? AvatarSize.Sm : undefined; + const modalTitle = isNetworkUiRedesignEnabled() ? 'networks.additional_network_information_title' : 'networks.network_warning_title'; - const modalDescription = isNetworkUiRedesignEnabled + const modalDescription = isNetworkUiRedesignEnabled() ? 'networks.additonial_network_information_desc' : 'networks.network_warning_desc'; - const buttonLabelAddNetwork = isNetworkUiRedesignEnabled + const buttonLabelAddNetwork = isNetworkUiRedesignEnabled() ? 'app_settings.network_add_custom_network' : 'app_settings.network_add_network'; const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState({ @@ -269,9 +269,9 @@ const NetworkSelector = () => { const renderMainnet = () => { const { name: mainnetName, chainId } = Networks.mainnet; - if (isNetworkUiRedesignEnabled && isNoSearchResults(MAINNET)) return null; + if (isNetworkUiRedesignEnabled() && isNoSearchResults(MAINNET)) return null; - if (isNetworkUiRedesignEnabled) { + if (isNetworkUiRedesignEnabled()) { return ( { const renderLineaMainnet = () => { const { name: lineaMainnetName, chainId } = Networks['linea-mainnet']; - if (isNetworkUiRedesignEnabled && isNoSearchResults('linea-mainnet')) + if (isNetworkUiRedesignEnabled() && isNoSearchResults('linea-mainnet')) return null; - if (isNetworkUiRedesignEnabled) { + if (isNetworkUiRedesignEnabled()) { return ( { if (!chainId) return null; const { name } = { name: nickname || rpcUrl }; - if (isNetworkUiRedesignEnabled && isNoSearchResults(name)) return null; + if (isNetworkUiRedesignEnabled() && isNoSearchResults(name)) + return null; //@ts-expect-error - The utils/network file is still JS and this function expects a networkType, and should be optional const image = getNetworkImageSource({ chainId: chainId?.toString() }); - if (isNetworkUiRedesignEnabled) { + if (isNetworkUiRedesignEnabled()) { return ( { // eslint-disable-next-line @typescript-eslint/no-explicit-any const { name, imageSource, chainId } = (Networks as any)[networkType]; - if (isNetworkUiRedesignEnabled && isNoSearchResults(name)) return null; + if (isNetworkUiRedesignEnabled() && isNoSearchResults(name)) return null; - if (isNetworkUiRedesignEnabled) { + if (isNetworkUiRedesignEnabled()) { return ( { const renderAdditonalNetworks = () => { let filteredNetworks; - if (isNetworkUiRedesignEnabled && searchString.length > 0) + if (isNetworkUiRedesignEnabled() && searchString.length > 0) filteredNetworks = PopularList.filter(({ nickname }) => nickname.toLowerCase().includes(searchString.toLowerCase()), ); @@ -615,7 +616,7 @@ const NetworkSelector = () => { <> - {isNetworkUiRedesignEnabled && ( + {isNetworkUiRedesignEnabled() && ( { /> )} - {isNetworkUiRedesignEnabled && + {isNetworkUiRedesignEnabled() && searchString.length === 0 && renderEnabledNetworksTitle()} {renderMainnet()} {renderLineaMainnet()} {renderRpcNetworks()} - {isNetworkUiRedesignEnabled && + {isNetworkUiRedesignEnabled() && searchString.length === 0 && renderPopularNetworksTitle()} - {isNetworkUiRedesignEnabled && renderAdditonalNetworks()} + {isNetworkUiRedesignEnabled() && renderAdditonalNetworks()} {searchString.length === 0 && renderTestNetworksSwitch()} {showTestNetworks && renderOtherNetworks()} @@ -656,7 +657,7 @@ const NetworkSelector = () => { return ( - {isNetworkUiRedesignEnabled ? ( + {isNetworkUiRedesignEnabled() ? ( {renderBottomSheetContent()} diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx b/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx index 57902d7a092..50120794cfa 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/CustomNetworkView/CustomNetwork.tsx @@ -17,7 +17,7 @@ import { } from '../../../../../../selectors/networkController'; import AvatarNetwork from '../../../../../../component-library/components/Avatars/Avatar/variants/AvatarNetwork'; import { AvatarSize } from '../../../../../../component-library/components/Avatars/Avatar'; -import { isNetworkUiRedesignEnabled } from '../../../../../../util/networks'; +import { isNetworkUiRedesignEnabled } from '../../../../../../util/networks/isNetworkUiRedesignEnabled'; const CustomNetwork = ({ isNetworkModalVisible, @@ -98,7 +98,7 @@ const CustomNetwork = ({ } /> - + {networkConfiguration.nickname} diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/Settings/NetworksSettings/NetworkSettings/__snapshots__/index.test.tsx.snap index c3228995d25..ba068aa9447 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/__snapshots__/index.test.tsx.snap @@ -27,6 +27,68 @@ exports[`NetworkSettings should render correctly 1`] = ` } } > - + + +`; + +exports[`NetworkSettings should render the component correctly when isNetworkUiRedesignEnabled is false 1`] = ` + + + +`; + +exports[`NetworkSettings should render the component correctly when isNetworkUiRedesignEnabled is true 1`] = ` + + `; diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js index ad606a6149e..b61e57079bb 100644 --- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js +++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js @@ -20,7 +20,6 @@ import Networks, { isprivateConnection, getAllNetworks, getIsNetworkOnboarded, - isNetworkUiRedesignEnabled, } from '../../../../../util/networks'; import { getEtherscanBaseUrl } from '../../../../../util/etherscan'; import Engine from '../../../../../core/Engine'; @@ -78,6 +77,12 @@ import Routes from '../../../../../constants/navigation/Routes'; import { selectUseSafeChainsListValidation } from '../../../../../../app/selectors/preferencesController'; import withIsOriginalNativeToken from './withIsOriginalNativeToken'; import { compose } from 'redux'; +import Icon, { + IconColor, + IconName, + IconSize, +} from '../../../../../component-library/components/Icons/Icon'; +import { isNetworkUiRedesignEnabled } from '../../../../../util/networks/isNetworkUiRedesignEnabled'; const createStyles = (colors) => StyleSheet.create({ @@ -143,6 +148,10 @@ const createStyles = (colors) => flexGrow: 1, flexShrink: 1, }, + newWarningContainer: { + flexGrow: 1, + flexShrink: 1, + }, label: { fontSize: 14, paddingVertical: 12, @@ -163,6 +172,18 @@ const createStyles = (colors) => color: colors.text.default, ...fontStyles.normal, }, + messageWarning: { + paddingVertical: 2, + fontSize: 14, + color: colors.warning.default, + ...typography.sBodyMD, + }, + suggestionButton: { + color: colors.text.default, + paddingLeft: 2, + paddingRight: 4, + marginTop: 4, + }, inlineWarning: { paddingVertical: 2, fontSize: 14, @@ -252,7 +273,7 @@ const allNetworksblockExplorerUrl = (networkName) => /** * Main view for app configurations */ -class NetworkSettings extends PureComponent { +export class NetworkSettings extends PureComponent { static propTypes = { /** * Network configurations @@ -321,6 +342,8 @@ class NetworkSettings extends PureComponent { showNetworkDetailsModal: false, isNameFieldFocused: false, isSymbolFieldFocused: false, + isRpcUrlFieldFocused: false, + isChainIdFieldFocused: false, networkList: [], }; @@ -523,12 +546,34 @@ class NetworkSettings extends PureComponent { return true; }; + checkIfChainIdExists = async (chainId) => { + const { networkConfigurations } = this.props; + + // Convert the chainId to hex format + const hexChainId = toHex(chainId); + + // Check if any network configuration matches the given chainId + const chainIdExists = Object.values(networkConfigurations).some( + (item) => item.chainId === hexChainId, + ); + + // Return true if the chainId exists and the UI redesign is enabled, otherwise false + return isNetworkUiRedesignEnabled() && chainIdExists; + }; + checkIfNetworkExists = async (rpcUrl) => { const checkCustomNetworks = Object.values( this.props.networkConfigurations, ).filter((item) => item.rpcUrl === rpcUrl); + if (checkCustomNetworks.length > 0) { - this.setState({ warningRpcUrl: strings('app_settings.network_exists') }); + if (!isNetworkUiRedesignEnabled()) { + this.setState({ + warningRpcUrl: strings('app_settings.network_exists'), + }); + return checkCustomNetworks; + } + return checkCustomNetworks; } const defaultNetworks = getAllNetworks().map((item) => Networks[item]); @@ -689,6 +734,14 @@ class NetworkSettings extends PureComponent { } if (isNetworkExists.length > 0) { + if (isNetworkUiRedesignEnabled()) { + return this.setState({ + validatedRpcURL: false, + warningRpcUrl: strings( + 'app_settings.url_associated_to_another_chain_id', + ), + }); + } return this.setState({ validatedRpcURL: true, warningRpcUrl: strings('app_settings.network_exists'), @@ -712,8 +765,38 @@ class NetworkSettings extends PureComponent { /** * Validates that chain id is a valid integer number, setting a warningChainId if is invalid */ - validateChainId = () => { - const { chainId } = this.state; + validateChainId = async () => { + const { chainId, rpcUrl, editable } = this.state; + + const isChainIdExists = await this.checkIfChainIdExists(chainId); + const isNetworkExists = await this.checkIfNetworkExists(rpcUrl); + + if ( + isChainIdExists && + isNetworkExists.length > 0 && + isNetworkUiRedesignEnabled() && + !editable + ) { + return this.setState({ + validateChainId: true, + warningChainId: strings( + 'app_settings.chain_id_associated_with_another_network', + ), + }); + } + + if ( + isChainIdExists && + isNetworkExists.length === 0 && + isNetworkUiRedesignEnabled() && + !editable + ) { + return this.setState({ + validateChainId: true, + warningChainId: strings('app_settings.network_already_exist'), + }); + } + if (!chainId) { return this.setState({ warningChainId: strings('app_settings.chain_id_required'), @@ -753,6 +836,37 @@ class NetworkSettings extends PureComponent { }); } + let endpointChainId; + let providerError; + try { + endpointChainId = await jsonRpcRequest(rpcUrl, 'eth_chainId'); + } catch (err) { + Logger.error(err, 'Failed to fetch the chainId from the endpoint.'); + providerError = err; + } + + if ( + (providerError || typeof endpointChainId !== 'string') && + isNetworkUiRedesignEnabled() + ) { + return this.setState({ + validatedRpcURL: false, + warningRpcUrl: strings('app_settings.unMatched_chain'), + }); + } + + if (endpointChainId !== toHex(chainId)) { + if (isNetworkUiRedesignEnabled()) { + return this.setState({ + warningRpcUrl: strings( + 'app_settings.url_associated_to_another_chain_id', + ), + validatedRpcURL: false, + warningChainId: strings('app_settings.unMatched_chain_name'), + }); + } + } + this.validateRpcAndChainId(); this.setState({ warningChainId: undefined, validatedChainId: true }); }; @@ -846,6 +960,13 @@ class NetworkSettings extends PureComponent { */ disabledByChainId = () => { const { chainId, validatedChainId, warningChainId } = this.state; + + if (isNetworkUiRedesignEnabled()) { + return ( + !chainId || + (chainId && (!validatedChainId || warningChainId !== undefined)) + ); + } if (!chainId) return true; return validatedChainId && !!warningChainId; }; @@ -926,6 +1047,22 @@ class NetworkSettings extends PureComponent { this.setState({ isSymbolFieldFocused: false }); }; + onRpcUrlFocused = () => { + this.setState({ isRpcUrlFieldFocused: true }); + }; + + onRpcUrlBlur = () => { + this.setState({ isRpcUrlFieldFocused: false }); + }; + + onChainIdFocused = () => { + this.setState({ isChainIdFieldFocused: true }); + }; + + onChainIdBlur = () => { + this.setState({ isChainIdFieldFocused: false }); + }; + jumpToRpcURL = () => { const { current } = this.inputRpcURL; current && current.focus(); @@ -976,6 +1113,17 @@ class NetworkSettings extends PureComponent { navigation.goBack(); }; + goToNetworkEdit = () => { + const { rpcUrl } = this.state; + const { navigation } = this.props; + navigation.goBack(); + navigation.navigate(Routes.EDIT_NETWORK, { + network: rpcUrl, + shouldNetworkSwitchPopToWallet: false, + shouldShowPopularNetworks: false, + }); + }; + showNetworkModal = (networkConfiguration) => { this.setState({ showPopularNetworkModal: true, @@ -1005,6 +1153,8 @@ class NetworkSettings extends PureComponent { inputWidth, isNameFieldFocused, isSymbolFieldFocused, + isRpcUrlFieldFocused, + isChainIdFieldFocused, } = this.state; const { route } = this.props; const isCustomMainnet = route.params?.isCustomMainnet; @@ -1050,6 +1200,26 @@ class NetworkSettings extends PureComponent { isCustomMainnet ? styles.onboardingInput : undefined, ]; + const inputErrorRpcStyle = [ + warningRpcUrl + ? isRpcUrlFieldFocused + ? styles.inputWithFocus + : styles.inputWithError + : styles.input, + inputWidth, + isCustomMainnet ? styles.onboardingInput : undefined, + ]; + + const inputChainIdStyle = [ + warningChainId + ? isChainIdFieldFocused + ? styles.inputWithFocus + : styles.inputWithError + : styles.input, + inputWidth, + isCustomMainnet ? styles.onboardingInput : undefined, + ]; + const isRPCEditable = isCustomMainnet || editable; const isActionDisabled = !enableAction || @@ -1075,6 +1245,65 @@ class NetworkSettings extends PureComponent { const shouldNetworkSwitchPopToWallet = route.params?.shouldNetworkSwitchPopToWallet ?? true; + const renderWarningChainId = () => { + const CHAIN_LIST_URL = 'https://chainid.network/'; + const containerStyle = isNetworkUiRedesignEnabled() + ? styles.newWarningContainer + : styles.warningContainer; + + if (warningChainId) { + if (warningChainId === strings('app_settings.unMatched_chain_name')) { + return ( + + {warningChainId} + + + {strings('app_settings.find_the_right_one')}{' '} + Linking.openURL(CHAIN_LIST_URL)} + > + chainid.network{' '} + + + + + + ); + } + if ( + warningChainId === + strings('app_settings.chain_id_associated_with_another_network') + ) { + return ( + + + {strings( + 'app_settings.chain_id_associated_with_another_network', + )}{' '} + this.goToNetworkEdit()} + > + {strings('app_settings.edit_original_network')} + + + + ); + } + return ( + + {warningChainId} + + ); + } + return null; + }; + const renderWarningSymbol = () => { const { validatedSymbol } = this.state; if (warningSymbol) { @@ -1117,6 +1346,51 @@ class NetworkSettings extends PureComponent { return null; }; + const renderButtons = () => { + if (addMode || editable) { + return ( + + {editable ? ( + +