Skip to content

Commit

Permalink
feat: multi rpc modal (#11685)
Browse files Browse the repository at this point in the history
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**
This PR introduces the UI implementation for the Multi-RPC Modal, which
is part of the broader initiative to support multiple RPC endpoints for
networks. The modal allows users to view and select from multiple RPC
options for a specific network, ensuring a smoother transition or switch
between different endpoints.

**UI Only: This PR focuses solely on the implementation of the UI. The
modal is not yet integrated or functional in the application as it
requires the upgrade of the Network Controller to version v21.**

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->

## **Related issues**

Fixes:

## **Manual testing steps**

1. this Modal is not called yet , it's require the network controller
v21 to be used
2. the use of this modal will be done on this
[PR](#11622)

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<img width="438" alt="Screenshot 2024-10-04 at 11 44 15"
src="https://github.com/user-attachments/assets/f558a5a8-796a-423c-b957-f7e7aee8d0b2">

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
salimtb authored Oct 9, 2024
1 parent f35f102 commit ea3948c
Show file tree
Hide file tree
Showing 28 changed files with 1,095 additions and 57 deletions.
16 changes: 15 additions & 1 deletion app/actions/security/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum ActionType {
SET_AUTOMATIC_SECURITY_CHECKS_MODAL_OPEN = 'SET_AUTOMATIC_SECURITY_CHECKS_MODAL_OPEN',
SET_DATA_COLLECTION_FOR_MARKETING = 'SET_DATA_COLLECTION_FOR_MARKETING',
SET_NFT_AUTO_DETECTION_MODAL_OPEN = 'SET_NFT_AUTO_DETECTION_MODAL_OPEN',
SET_MULTI_RPC_MIGRATION_MODAL_OPEN = 'SET_MULTI_RPC_MIGRATION_MODAL_OPEN',
}

export interface AllowLoginWithRememberMeUpdated
Expand Down Expand Up @@ -35,6 +36,11 @@ export interface SetNftAutoDetectionModalOpen
open: boolean;
}

export interface SetMultiRpcMigrationModalOpen
extends ReduxAction<ActionType.SET_MULTI_RPC_MIGRATION_MODAL_OPEN> {
open: boolean;
}

export interface SetDataCollectionForMarketing
extends ReduxAction<ActionType.SET_DATA_COLLECTION_FOR_MARKETING> {
enabled: boolean;
Expand All @@ -46,7 +52,8 @@ export type Action =
| UserSelectedAutomaticSecurityChecksOptions
| SetAutomaticSecurityChecksModalOpen
| SetDataCollectionForMarketing
| SetNftAutoDetectionModalOpen;
| SetNftAutoDetectionModalOpen
| SetMultiRpcMigrationModalOpen;

export const setAllowLoginWithRememberMe = (
enabled: boolean,
Expand Down Expand Up @@ -82,6 +89,13 @@ export const setNftAutoDetectionModalOpen = (
open,
});

export const setMultiRpcMigrationModalOpen = (
open: boolean,
): SetMultiRpcMigrationModalOpen => ({
type: ActionType.SET_MULTI_RPC_MIGRATION_MODAL_OPEN,
open,
});

export const setDataCollectionForMarketing = (enabled: boolean) => ({
type: ActionType.SET_DATA_COLLECTION_FOR_MARKETING,
enabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const CellSelectWithMenu = ({
tagLabel,
isSelected = false,
children,
withAvatar = true,
...props
}: CellSelectWithMenuProps) => {
const { styles } = useStyles(styleSheet, { style });
Expand All @@ -46,12 +47,15 @@ const CellSelectWithMenu = ({
>
<View style={styles.cellBase}>
{/* DEV Note: Account Avatar should be replaced with Avatar with Badge whenever available */}
<Avatar
style={styles.avatar}
testID={CellModalSelectorsIDs.BASE_AVATAR}
size={DEFAULT_CELLBASE_AVATAR_SIZE}
{...avatarProps}
/>
{withAvatar ? (
<Avatar
style={styles.avatar}
testID={CellModalSelectorsIDs.BASE_AVATAR}
size={DEFAULT_CELLBASE_AVATAR_SIZE}
{...avatarProps}
/>
) : null}

<View style={styles.cellBaseInfo}>
<Text
numberOfLines={1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ exports[`CellSelectWithMenu should render with default settings correctly 1`] =
"opacity": 1,
"padding": 16,
"position": "relative",
"width": "95%",
"width": "90%",
"zIndex": 1,
}
}
Expand Down Expand Up @@ -287,7 +287,13 @@ exports[`CellSelectWithMenu should render with default settings correctly 1`] =
</View>
</View>
</TouchableOpacity>
<View>
<View
style={
{
"paddingHorizontal": 20,
}
}
>
<TouchableOpacity
accessibilityRole="button"
accessible={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const styleSheet = (params: {
position: 'relative',
opacity: isDisabled ? 0.5 : 1,
padding: 16,
width: '95%',
width: '90%',
zIndex: 1,
} as ViewStyle,
style,
Expand Down Expand Up @@ -86,6 +86,9 @@ const styleSheet = (params: {
paddingLeft: 8,
paddingTop: 32,
},
buttonIcon: {
paddingHorizontal: 20,
},
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ describe('ListItemMultiSelectButton', () => {
const { getByRole } = render(
<ListItemMultiSelectButton
onPress={mockOnPress}
onButtonClick={mockOnPress}
buttonProps={{
onButtonClick: mockOnPress,
}}
>
<View />
</ListItemMultiSelectButton>,
Expand All @@ -64,7 +66,9 @@ describe('ListItemMultiSelectButton', () => {
const { getByTestId } = render(
<ListItemMultiSelectButton
buttonIcon={IconName.Check}
onButtonClick={mockOnButtonClick}
buttonProps={{
onButtonClick: mockOnButtonClick,
}}
>
<View />
</ListItemMultiSelectButton>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,22 @@ import {
IconColor,
IconName,
} from '../../../component-library/components/Icons/Icon';
import Button, {
ButtonSize,
ButtonVariants,
ButtonWidthTypes,
} from '../../../component-library/components/Buttons/Button';
import { TextVariant } from '../../../component-library/components/Texts/Text';

const ListItemMultiSelectButton: React.FC<ListItemMultiSelectButtonProps> = ({
style,
isSelected = false,
isDisabled = false,
children,
gap = DEFAULT_LISTITEMMULTISELECT_GAP,
showButtonIcon = true,
buttonIcon = IconName.MoreVertical,
buttonProps,
...props
}) => {
const { styles } = useStyles(styleSheet, {
Expand Down Expand Up @@ -55,15 +63,29 @@ const ListItemMultiSelectButton: React.FC<ListItemMultiSelectButtonProps> = ({
</View>
)}
</TouchableOpacity>
<View>
<ButtonIcon
iconName={buttonIcon}
iconColor={IconColor.Default}
testID={BUTTON_TEST_ID}
onPress={props.onButtonClick}
accessibilityRole="button"
/>
</View>
{showButtonIcon ? (
<View style={styles.buttonIcon}>
<ButtonIcon
iconName={buttonIcon}
iconColor={IconColor.Default}
testID={BUTTON_TEST_ID}
onPress={buttonProps?.onButtonClick}
accessibilityRole="button"
/>
</View>
) : null}
{buttonProps?.textButton ? (
<View>
<Button
variant={ButtonVariants.Link}
onPress={buttonProps?.onButtonClick as () => void}
labelTextVariant={TextVariant.BodyMD}
size={ButtonSize.Lg}
width={ButtonWidthTypes.Auto}
label={buttonProps?.textButton}
/>
</View>
) : null}
</View>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,30 @@ export interface ListItemMultiSelectButtonProps
buttonIcon?: IconName;

/**
* Optional button onClick function
* Optional button onClick rpc modal function
*/
onButtonClick?: ((event: GestureResponderEvent) => void) | undefined;
onTextClick?: (() => void) | undefined;

/**
* Optional button onClick rpc modal function
* Optional property to add avatar
*/
onTextClick?: (() => void) | undefined;
withAvatar?: boolean;

/**
* Optional property to show icon
*/
showButtonIcon?: boolean;

buttonProps?: {
/**
* Optional button onClick function
*/
onButtonClick?: ((event: GestureResponderEvent) => void) | undefined;
/**
* Optional property to show text button
*/
textButton?: string | null;
};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ exports[`ListItemMultiSelectButton should render correctly with default props 1`
"opacity": 1,
"padding": 16,
"position": "relative",
"width": "95%",
"width": "90%",
"zIndex": 1,
}
}
Expand Down Expand Up @@ -52,7 +52,13 @@ exports[`ListItemMultiSelectButton should render correctly with default props 1`
</View>
</View>
</TouchableOpacity>
<View>
<View
style={
{
"paddingHorizontal": 20,
}
}
>
<TouchableOpacity
accessibilityRole="button"
accessible={true}
Expand Down
10 changes: 8 additions & 2 deletions app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ import { SnapsExecutionWebView } from '../../../lib/snaps';
import OptionsSheet from '../../UI/SelectOptionSheet/OptionsSheet';
import FoxLoader from '../../../components/UI/FoxLoader';
import { AppStateEventProcessor } from '../../../core/AppStateEventListener';
import MultiRpcModal from '../../../components/Views/MultiRpcModal/MultiRpcModal';

const clearStackNavigatorOptions = {
headerShown: false,
Expand Down Expand Up @@ -396,7 +397,6 @@ const App = (props) => {
});
}, [handleDeeplink]);


useEffect(() => {
if (navigator) {
// Initialize deep link manager
Expand Down Expand Up @@ -687,11 +687,17 @@ const App = (props) => {
name={Routes.MODAL.NFT_AUTO_DETECTION_MODAL}
component={NFTAutoDetectionModal}
/>
{isNetworkUiRedesignEnabled() ? (
<Stack.Screen
name={Routes.MODAL.MULTI_RPC_MIGRATION_MODAL}
component={MultiRpcModal}
/>
) : null}

<Stack.Screen
name={Routes.SHEET.SHOW_TOKEN_ID}
component={ShowTokenIdSheet}
/>

<Stack.Screen
name={Routes.SHEET.ORIGIN_SPAM_MODAL}
component={OriginSpamModal}
Expand Down
39 changes: 39 additions & 0 deletions app/components/Nav/Main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { useMinimumVersions } from '../../hooks/MinimumVersions';
import navigateTermsOfUse from '../../../util/termsOfUse/termsOfUse';
import {
selectChainId,
selectNetworkConfigurations,
selectProviderConfig,
selectProviderType,
} from '../../../selectors/networkController';
Expand All @@ -80,6 +81,7 @@ import {
startIncomingTransactionPolling,
stopIncomingTransactionPolling,
} from '../../../util/transaction-controller';
import isNetworkUiRedesignEnabled from '../../../util/networks/isNetworkUiRedesignEnabled';

const Stack = createStackNavigator();

Expand Down Expand Up @@ -232,8 +234,10 @@ const Main = (props) => {
* Current network
*/
const providerConfig = useSelector(selectProviderConfig);
const networkConfigurations = useSelector(selectNetworkConfigurations);
const networkName = useSelector(selectNetworkName);
const previousProviderConfig = useRef(undefined);
const previousNetworkConfigurations = useRef(undefined);
const { toastRef } = useContext(ToastContext);
const networkImage = useSelector(selectNetworkImageSource);

Expand All @@ -259,6 +263,41 @@ const Main = (props) => {
previousProviderConfig.current = providerConfig;
}, [providerConfig, networkName, networkImage, toastRef]);

// Show add network confirmation.
useEffect(() => {
if (!isNetworkUiRedesignEnabled()) return;

// Memoized values to avoid recalculations
const currentNetworkValues = Object.values(networkConfigurations);
const previousNetworkValues = Object.values(
previousNetworkConfigurations.current ?? {},
);

if (
previousNetworkValues.length &&
currentNetworkValues.length !== previousNetworkValues.length
) {
// Find the newly added network
const newNetwork = currentNetworkValues.find(
(network) => !previousNetworkValues.includes(network),
);

toastRef?.current?.showToast({
variant: ToastVariants.Plain,
labelOptions: [
{
label: `${newNetwork?.name ?? strings('asset_details.network')} `,
isBold: true,
},
{ label: strings('toast.network_added') },
],
networkImageSource: networkImage,
});
}

previousNetworkConfigurations.current = networkConfigurations;
}, [networkConfigurations, networkName, networkImage, toastRef]);

useEffect(() => {
if (locale.current !== I18n.locale) {
locale.current = I18n.locale;
Expand Down
1 change: 1 addition & 0 deletions app/components/UI/AssetIcon/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const mockInitialState = {
selectedAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3',
useTokenDetection: true,
useNftDetection: false,
showMultiRpcModal: false,
displayNftMedia: true,
useSafeChainsListValidation: false,
isMultiAccountBalancesEnabled: true,
Expand Down
Loading

0 comments on commit ea3948c

Please sign in to comment.