Skip to content

Commit 6ba5ae4

Browse files
salimtbgambinish
andauthored
feat: multichain manual import (#14400)
<!-- 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** the goal of this PR is to make the manual import token multichain design: https://www.figma.com/design/CvBEDNwQqfW4Wbil40Bhr1/Substitute-Global-Network-selector?t=7VlJYKwbd0ONMpnl-0 <!-- 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. Go to import token 2. Import token using search 3. Import token using contract address ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/1e5f9b29-b0a3-4730-a005-552c1fea3aae https://github.com/user-attachments/assets/2d18b0e3-71b2-45b6-9ffc-1bbf49587a13 <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. --------- Co-authored-by: Nick Gambino <[email protected]> Co-authored-by: Nicholas Gambino <[email protected]>
1 parent a7a912c commit 6ba5ae4

32 files changed

+2855
-81
lines changed

app/components/UI/AddCustomToken/__snapshots__/index.test.tsx.snap

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`AddCustomToken render matches previous snapshot 1`] = `
3+
exports[`AddCustomToken render correctly 1`] = `
44
<View
55
style={
66
{
@@ -141,6 +141,80 @@ exports[`AddCustomToken render matches previous snapshot 1`] = `
141141
}
142142
}
143143
>
144+
<TouchableOpacity
145+
onLongPress={[Function]}
146+
onPress={[Function]}
147+
style={
148+
{
149+
"alignItems": "center",
150+
"borderColor": "#b7bbc8",
151+
"borderRadius": 2,
152+
"borderWidth": 1,
153+
"flexDirection": "row",
154+
"marginBottom": 16,
155+
"marginTop": 4,
156+
"padding": 16,
157+
}
158+
}
159+
>
160+
<Text
161+
style={
162+
{
163+
"color": "#121314",
164+
"fontFamily": "CentraNo1-Book",
165+
"fontSize": 16,
166+
"fontWeight": "400",
167+
}
168+
}
169+
>
170+
Select a network
171+
</Text>
172+
<View
173+
style={
174+
{
175+
"alignItems": "center",
176+
"flexDirection": "row",
177+
"paddingHorizontal": 16,
178+
"position": "absolute",
179+
"right": 0,
180+
}
181+
}
182+
>
183+
<TouchableOpacity
184+
accessibilityRole="button"
185+
accessible={true}
186+
activeOpacity={1}
187+
disabled={false}
188+
onPress={[Function]}
189+
onPressIn={[Function]}
190+
onPressOut={[Function]}
191+
style={
192+
{
193+
"alignItems": "center",
194+
"borderRadius": 8,
195+
"height": 24,
196+
"justifyContent": "center",
197+
"opacity": 1,
198+
"width": 24,
199+
}
200+
}
201+
testID="select-network-button"
202+
>
203+
<SvgMock
204+
color="#121314"
205+
height={16}
206+
name="ArrowDown"
207+
style={
208+
{
209+
"height": 16,
210+
"width": 16,
211+
}
212+
}
213+
width={16}
214+
/>
215+
</TouchableOpacity>
216+
</View>
217+
</TouchableOpacity>
144218
<Text
145219
style={
146220
{

app/components/UI/AddCustomToken/index.js

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
View,
66
StyleSheet,
77
InteractionManager,
8-
Platform,
98
ScrollView,
9+
TouchableOpacity,
1010
} from 'react-native';
1111
import { fontStyles } from '../../../styles/common';
1212
import Engine from '../../../core/Engine';
@@ -26,6 +26,7 @@ import { regex } from '../../../../app/util/regex';
2626
import {
2727
getBlockExplorerAddressUrl,
2828
getDecimalChainId,
29+
getNetworkImageSource,
2930
} from '../../../util/networks';
3031
import { withMetricsAwareness } from '../../../components/hooks/useMetrics';
3132
import { formatIconUrlWithProxy } from '@metamask/assets-controllers';
@@ -34,6 +35,7 @@ import Button, {
3435
ButtonVariants,
3536
} from '../../../component-library/components/Buttons/Button';
3637
import Icon, {
38+
IconColor,
3739
IconName,
3840
IconSize,
3941
} from '../../../component-library/components/Icons/Icon';
@@ -43,13 +45,25 @@ import Banner, {
4345
} from '../../../component-library/components/Banners/Banner';
4446
import CLText from '../../../component-library/components/Texts/Text/Text';
4547
import Logger from '../../../util/Logger';
48+
import Avatar, {
49+
AvatarSize,
50+
AvatarVariant,
51+
} from '../../../component-library/components/Avatars/Avatar';
52+
import ButtonIcon from '../../../component-library/components/Buttons/ButtonIcon';
4653

4754
const createStyles = (colors) =>
4855
StyleSheet.create({
4956
wrapper: {
5057
backgroundColor: colors.background.default,
5158
flex: 1,
5259
},
60+
overlappingAvatarsContainer: {
61+
flexDirection: 'row',
62+
alignItems: 'center',
63+
position: 'absolute',
64+
paddingHorizontal: 16,
65+
right: 0,
66+
},
5367
addressWrapper: {
5468
paddingHorizontal: 16,
5569
paddingTop: 16,
@@ -116,6 +130,21 @@ const createStyles = (colors) =>
116130
textWrapper: {
117131
padding: 0,
118132
},
133+
networkSelectorContainer: {
134+
borderWidth: 1,
135+
marginBottom: 16,
136+
marginTop: 4,
137+
borderColor: colors.border.default,
138+
borderRadius: 2,
139+
flexDirection: 'row',
140+
alignItems: 'center',
141+
padding: 16,
142+
},
143+
networkSelectorText: {
144+
...fontStyles.normal,
145+
color: colors.text.default,
146+
fontSize: 16,
147+
},
119148
});
120149

121150
/**
@@ -164,6 +193,21 @@ class AddCustomToken extends PureComponent {
164193
* Metrics injected by withMetricsAwareness HOC
165194
*/
166195
metrics: PropTypes.object,
196+
197+
/**
198+
* Function to set the open network selector
199+
*/
200+
setOpenNetworkSelector: PropTypes.func,
201+
202+
/**
203+
* The selected network
204+
*/
205+
selectedNetwork: PropTypes.string,
206+
207+
/**
208+
* The network client ID
209+
*/
210+
networkClientId: PropTypes.string,
167211
};
168212

169213
getTokenAddedAnalyticsParams = () => {
@@ -186,7 +230,16 @@ class AddCustomToken extends PureComponent {
186230
if (!(await this.validateCustomToken())) return;
187231
const { TokensController } = Engine.context;
188232
const { address, symbol, decimals, name } = this.state;
189-
await TokensController.addToken({ address, symbol, decimals, name });
233+
const { chainId } = this.props;
234+
const networkClientId = this.props.networkClientId;
235+
await TokensController.addToken({
236+
address,
237+
symbol,
238+
decimals,
239+
name,
240+
chainId,
241+
networkClientId,
242+
});
190243

191244
const analyticsParams = this.getTokenAddedAnalyticsParams();
192245

@@ -228,6 +281,18 @@ class AddCustomToken extends PureComponent {
228281
this.props.navigation.goBack();
229282
};
230283

284+
componentDidUpdate(prevProps) {
285+
if (prevProps.networkClientId !== this.props.networkClientId) {
286+
this.setState({
287+
address: '',
288+
symbol: '',
289+
decimals: '',
290+
name: '',
291+
warningAddress: '',
292+
});
293+
}
294+
}
295+
231296
onAddressChange = async (address) => {
232297
this.setState({ address });
233298
if (address.length === 42) {
@@ -239,9 +304,18 @@ class AddCustomToken extends PureComponent {
239304
if (validated) {
240305
const { AssetsContractController } = Engine.context;
241306
const [decimals, symbol, name] = await Promise.all([
242-
AssetsContractController.getERC20TokenDecimals(address),
243-
AssetsContractController.getERC721AssetSymbol(address),
244-
AssetsContractController.getERC20TokenName(address),
307+
AssetsContractController.getERC20TokenDecimals(
308+
address,
309+
this.props.networkClientId,
310+
),
311+
AssetsContractController.getERC721AssetSymbol(
312+
address,
313+
this.props.networkClientId,
314+
),
315+
AssetsContractController.getERC20TokenName(
316+
address,
317+
this.props.networkClientId,
318+
),
245319
]);
246320

247321
this.setState({
@@ -463,6 +537,7 @@ class AddCustomToken extends PureComponent {
463537
}),
464538
name,
465539
decimals,
540+
chainId,
466541
},
467542
];
468543

@@ -494,7 +569,7 @@ class AddCustomToken extends PureComponent {
494569
const colors = this.context.colors || mockTheme.colors;
495570
const themeAppearance = this.context.themeAppearance || 'light';
496571
const styles = createStyles(colors);
497-
const isDisabled = !symbol || !decimals;
572+
const isDisabled = !symbol || !decimals || !this.props.selectedNetwork;
498573

499574
const addressInputStyle = onFocusAddress
500575
? { ...styles.textInput, ...styles.textInputFocus }
@@ -524,6 +599,39 @@ class AddCustomToken extends PureComponent {
524599
<ScrollView>
525600
{this.renderBanner()}
526601
<View style={styles.addressWrapper}>
602+
<TouchableOpacity
603+
style={styles.networkSelectorContainer}
604+
onPress={() => this.props.setOpenNetworkSelector(true)}
605+
onLongPress={() => this.props.setOpenNetworkSelector(true)}
606+
>
607+
<Text style={styles.networkSelectorText}>
608+
{this.props.selectedNetwork ||
609+
strings('networks.select_network')}
610+
</Text>
611+
<View style={styles.overlappingAvatarsContainer}>
612+
{this.props.selectedNetwork ? (
613+
<Avatar
614+
variant={AvatarVariant.Network}
615+
size={AvatarSize.Sm}
616+
name={this.props.selectedNetwork}
617+
imageSource={getNetworkImageSource({
618+
networkType: 'evm',
619+
chainId: this.props.chainId,
620+
})}
621+
testID={ImportTokenViewSelectorsIDs.SELECT_NETWORK_BUTTON}
622+
/>
623+
) : null}
624+
625+
<ButtonIcon
626+
iconName={IconName.ArrowDown}
627+
iconColor={IconColor.Default}
628+
testID={ImportTokenViewSelectorsIDs.SELECT_NETWORK_BUTTON}
629+
onPress={() => this.props.setOpenNetworkSelector(true)}
630+
accessibilityRole="button"
631+
style={styles.buttonIcon}
632+
/>
633+
</View>
634+
</TouchableOpacity>
527635
<Text style={styles.inputLabel}>
528636
{strings('asset_details.address')}
529637
</Text>

0 commit comments

Comments
 (0)