NetworkSelector.tsx 7.66 KB
Newer Older
1
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
Alexey's avatar
Alexey committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity.  If not, see <http://www.gnu.org/licenses/>.

17
import React, { ReactElement, useState } from 'react';
18
import { BackHandler, FlatList, FlatListProps } from 'react-native';
19
import { useFocusEffect } from '@react-navigation/native';
Alexey's avatar
Alexey committed
20

21
22
23
import { NetworkCard } from 'components/AccountCard';
import { SafeAreaViewContainer } from 'components/SafeAreaContainer';
import ScreenHeading, { IdentityHeading } from 'components/ScreenHeading';
24
25
import {
	NETWORK_LIST,
Hanwen Cheng's avatar
Hanwen Cheng committed
26
	SubstrateNetworkKeys,
27
	UnknownNetworkKeys
28
29
30
} from 'constants/networkSpecs';
import testIDs from 'e2e/testIDs';
import colors from 'styles/colors';
31
import {
32
33
34
35
36
	isEthereumNetworkParams,
	isSubstrateNetworkParams,
	NetworkParams,
	SubstrateNetworkParams
} from 'types/networkSpecsTypes';
37
import { NavigationAccountIdentityProps } from 'types/props';
38
import { alertPathDerivationError } from 'utils/alertUtils';
39
import { getExistedNetworkKeys, getIdentityName } from 'utils/identitiesUtils';
40
import {
41
42
	navigateToPathDetails,
	navigateToPathsList,
43
44
	unlockSeedPhrase,
	useUnlockSeed
45
46
} from 'utils/navigationHelpers';
import { useSeedRef } from 'utils/seedRefHooks';
47
import QrScannerTab from 'components/QrScannerTab';
Alexey's avatar
Alexey committed
48

49
50
51
52
53
54
55
56
57
const excludedNetworks = [
	UnknownNetworkKeys.UNKNOWN,
	SubstrateNetworkKeys.KUSAMA_CC2
];
if (!__DEV__) {
	excludedNetworks.push(SubstrateNetworkKeys.SUBSTRATE_DEV);
	excludedNetworks.push(SubstrateNetworkKeys.KUSAMA_DEV);
}

58
export default function NetworkSelector({
59
	accounts,
60
	navigation,
61
	route
62
}: NavigationAccountIdentityProps<'Main'>): React.ReactElement {
63
	const isNew = route.params?.isNew ?? false;
Hanwen Cheng's avatar
Hanwen Cheng committed
64
	const [shouldShowMoreNetworks, setShouldShowMoreNetworks] = useState(false);
65
	const { identities, currentIdentity } = accounts.state;
66
	const seedRefHooks = useSeedRef(currentIdentity.encryptedSeed);
67
	const { unlockWithoutPassword } = useUnlockSeed(seedRefHooks.isSeedRefValid);
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
	// catch android back button and prevent exiting the app
	useFocusEffect(
		React.useCallback((): any => {
			const handleBackButton = (): boolean => {
				if (shouldShowMoreNetworks) {
					setShouldShowMoreNetworks(false);
					return true;
				} else {
					return false;
				}
			};
			const backHandler = BackHandler.addEventListener(
				'hardwareBackPress',
				handleBackButton
			);
			return (): void => backHandler.remove();
		}, [shouldShowMoreNetworks])
	);

87
88
89
90
91
92
	const onAddCustomPath = (): Promise<void> =>
		unlockWithoutPassword({
			name: 'PathDerivation',
			params: { parentPath: '' }
		});

93
94
95
96
97
98
99
100
101
102
103
104
	const sortNetworkKeys = (
		[, params1]: [any, NetworkParams],
		[, params2]: [any, NetworkParams]
	): number => {
		if (params1.order > params2.order) {
			return 1;
		} else if (params1.order < params2.order) {
			return -1;
		} else {
			return 0;
		}
	};
Hanwen Cheng's avatar
Hanwen Cheng committed
105

106
	const filterNetworkKeys = ([networkKey]: [string, any]): boolean => {
107
108
		const shouldExclude = excludedNetworks.includes(networkKey);
		if (isNew && !shouldExclude) return true;
109

Hanwen Cheng's avatar
Hanwen Cheng committed
110
		if (shouldShowMoreNetworks) {
111
			if (shouldExclude) return false;
Hanwen Cheng's avatar
Hanwen Cheng committed
112
113
114
115
116
			return !availableNetworks.includes(networkKey);
		}
		return availableNetworks.includes(networkKey);
	};

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
	const deriveSubstrateSeedRootPath = async (
		networkKey: string
	): Promise<void> => {
		await unlockSeedPhrase(navigation, seedRefHooks.isSeedRefValid);
		const fullPath = '';
		try {
			await accounts.deriveNewPath(
				fullPath,
				seedRefHooks.substrateAddress,
				networkKey,
				'Root',
				''
			);
			navigateToPathDetails(navigation, networkKey, fullPath);
		} catch (error) {
			alertPathDerivationError(error.message);
		}
	};

136
137
138
139
	const deriveSubstrateNetworkRootPath = async (
		networkKey: string,
		networkParams: SubstrateNetworkParams
	): Promise<void> => {
140
		const { pathId } = networkParams;
141
		await unlockSeedPhrase(navigation, seedRefHooks.isSeedRefValid);
142
		const fullPath = `//${pathId}`;
143
144
145
		try {
			await accounts.deriveNewPath(
				fullPath,
146
				seedRefHooks.substrateAddress,
147
148
149
150
				networkKey,
				`${networkParams.title} root`,
				''
			);
151
			navigateToPathDetails(navigation, networkKey, fullPath);
152
153
		} catch (error) {
			alertPathDerivationError(error.message);
154
		}
Hanwen Cheng's avatar
Hanwen Cheng committed
155
156
	};

157
	const deriveEthereumAccount = async (networkKey: string): Promise<void> => {
158
		await unlockSeedPhrase(navigation, seedRefHooks.isSeedRefValid);
159
		try {
160
161
162
163
			await accounts.deriveEthereumAccount(
				seedRefHooks.brainWalletAddress,
				networkKey
			);
164
			navigateToPathsList(navigation, networkKey);
165
166
		} catch (e) {
			alertPathDerivationError(e.message);
167
		}
Hanwen Cheng's avatar
Hanwen Cheng committed
168
	};
Alexey's avatar
Alexey committed
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183
	const getListOptions = (): Partial<FlatListProps<any>> => {
		if (isNew) return {};
		if (shouldShowMoreNetworks) {
			return {
				ListHeaderComponent: (
					<NetworkCard
						isAdd={true}
						onPress={onAddCustomPath}
						testID={testIDs.Main.addCustomNetworkButton}
						title="Create Custom Path"
						networkColor={colors.background.app}
					/>
				)
			};
184
		} else {
185
186
187
188
189
190
191
192
193
194
195
			return {
				ListFooterComponent: (
					<NetworkCard
						isAdd={true}
						onPress={(): void => setShouldShowMoreNetworks(true)}
						testID={testIDs.Main.addNewNetworkButton}
						title="Add Network Account"
						networkColor={colors.background.app}
					/>
				)
			};
Hanwen Cheng's avatar
Hanwen Cheng committed
196
197
		}
	};
198

199
	const renderScreenHeading = (): React.ReactElement => {
Hanwen Cheng's avatar
Hanwen Cheng committed
200
201
202
203
		if (isNew) {
			return <ScreenHeading title={'Create your first Keypair'} />;
		} else if (shouldShowMoreNetworks) {
			return (
204
				<IdentityHeading
Hanwen Cheng's avatar
Hanwen Cheng committed
205
					title={'Choose Network'}
206
					onPressBack={(): void => setShouldShowMoreNetworks(false)}
Hanwen Cheng's avatar
Hanwen Cheng committed
207
208
				/>
			);
209
		} else {
210
			const identityName = getIdentityName(currentIdentity, identities);
211
			return <IdentityHeading title={identityName} />;
212
		}
Hanwen Cheng's avatar
Hanwen Cheng committed
213
	};
214

215
216
217
218
	const onNetworkChosen = async (
		networkKey: string,
		networkParams: NetworkParams
	): Promise<void> => {
219
		if (isNew || shouldShowMoreNetworks) {
220
			if (isSubstrateNetworkParams(networkParams)) {
221
222
223
224
225
				if (isNew) {
					await deriveSubstrateSeedRootPath(networkKey);
				} else {
					await deriveSubstrateNetworkRootPath(networkKey, networkParams);
				}
Hanwen Cheng's avatar
Hanwen Cheng committed
226
227
228
229
230
231
232
233
			} else {
				await deriveEthereumAccount(networkKey);
			}
		} else {
			navigation.navigate('PathsList', { networkKey });
		}
	};

234
	const availableNetworks = getExistedNetworkKeys(currentIdentity);
235
	const networkList = Object.entries(NETWORK_LIST).filter(filterNetworkKeys);
Hanwen Cheng's avatar
Hanwen Cheng committed
236
237
	networkList.sort(sortNetworkKeys);

238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
	const renderNetwork = ({
		item
	}: {
		item: [string, NetworkParams];
	}): ReactElement => {
		const [networkKey, networkParams] = item;
		const networkIndexSuffix = isEthereumNetworkParams(networkParams)
			? networkParams.ethereumChainId
			: networkParams.pathId;
		return (
			<NetworkCard
				key={networkKey}
				testID={testIDs.Main.networkButton + networkIndexSuffix}
				networkKey={networkKey}
				onPress={(): Promise<void> =>
					onNetworkChosen(networkKey, networkParams)
				}
				title={networkParams.title}
			/>
		);
	};

Hanwen Cheng's avatar
Hanwen Cheng committed
260
	return (
261
		<SafeAreaViewContainer>
Hanwen Cheng's avatar
Hanwen Cheng committed
262
			{renderScreenHeading()}
263
264
265
266
267
268
			<FlatList
				bounces={false}
				data={networkList}
				keyExtractor={(item: [string, NetworkParams]): string => item[0]}
				renderItem={renderNetwork}
				testID={testIDs.Main.chooserScreen}
269
				{...getListOptions()}
270
			/>
271
			{!shouldShowMoreNetworks && !isNew && <QrScannerTab />}
272
		</SafeAreaViewContainer>
Hanwen Cheng's avatar
Hanwen Cheng committed
273
	);
Alexey's avatar
Alexey committed
274
}