Unverified Commit 71372268 authored by Hanwen Cheng's avatar Hanwen Cheng Committed by GitHub
Browse files

refactor: identity root account and network root account (#532)

* extract root as normal custom account

* extract network root account into list

* do not create root account when create new identity

* renaming new root account

* enable derive path in paths list

* remove unused comment
parent 7f4703f0
Pipeline #77556 failed with stages
in 2 minutes
......@@ -114,12 +114,6 @@ describe('Load test', async () => {
await testSetUpDefaultPath();
});
it('starts with a root account', async () => {
await testTap(PathsList.rootButton);
await expect(element(by.text('Root'))).toExist();
await tapBack();
});
it('is able to create custom path', async () => {
await tapBack();
await testTap(testIDs.AccountNetworkChooser.addNewNetworkButton);
......
......@@ -74,7 +74,6 @@ const testIDs = {
PathsList: {
deriveButton: 'path_list_derive_button',
pathCard: 'path_list_path_card',
rootButton: 'path_list_root_button',
scanButton: 'path_list_scan_button',
screen: 'path_list_screen'
},
......
......@@ -87,7 +87,7 @@ const raw = [
},
{
address: 'addressRoot',
expectName: '',
expectName: 'Identity root',
name: '',
path: ''
},
......@@ -162,19 +162,23 @@ describe('IdentitiesUtils', () => {
const groupResult = groupPaths(kusamaPaths);
expect(groupResult).toEqual([
{
paths: [paths[0]],
paths: ['//kusama'],
title: 'Kusama root'
},
{
paths: ['//kusama//default'],
title: '//default'
},
{
paths: [paths[2]],
paths: ['//kusama/softKey1'],
title: '/softKey1'
},
{
paths: [paths[4]],
paths: ['//kusama//staking/1'],
title: '//staking'
},
{
paths: [paths[1], paths[3]],
paths: ['//kusama//funding/1', '//kusama//funding/2'],
title: '//funding'
}
]);
......@@ -192,8 +196,8 @@ describe('IdentitiesUtils', () => {
const groupResult = groupPaths(unKnownPaths);
expect(groupResult).toEqual([
{
paths: ['//polkadot//default'],
title: '//polkadot'
paths: [''],
title: 'Identity root'
},
{
paths: ['//custom'],
......@@ -203,6 +207,10 @@ describe('IdentitiesUtils', () => {
paths: ['/polkadot/1'],
title: '/polkadot'
},
{
paths: ['//polkadot//default'],
title: '//polkadot'
},
{
paths: ['/kusama', '/kusama/1'],
title: '/kusama'
......
......@@ -20,8 +20,6 @@ import PropTypes from 'prop-types';
import AccountCard from './AccountCard';
import PathCard from './PathCard';
import React from 'react';
import { getIdentityName } from '../util/identitiesUtils';
import { defaultNetworkKey } from '../constants';
const CompatibleCard = ({ account, accountsStore, titlePrefix }) =>
account.isLegacy === true || account.isLegacy === undefined ? (
......@@ -30,12 +28,6 @@ const CompatibleCard = ({ account, accountsStore, titlePrefix }) =>
address={account.address}
networkKey={account.networkKey || ''}
/>
) : account.path === '' ? (
<AccountCard
title={getIdentityName(account, accountsStore.state.identities)}
address={account.address}
networkKey={defaultNetworkKey}
/>
) : (
<PathCard
identity={accountsStore.getIdentityByAccountId(account.accountId)}
......
......@@ -29,8 +29,6 @@ import { Icon } from 'react-native-elements';
import colors from '../colors';
import AccountIcon from './AccountIcon';
import { NETWORK_LIST } from '../constants';
import TouchableItem from './TouchableItem';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
const composeStyle = StyleSheet.compose;
......@@ -77,7 +75,7 @@ const renderBack = onPress => {
iconName="arrowleft"
iconType="antdesign"
onPress={onPress}
style={[baseStyles.icon, { left: 0, top: -8 }]}
style={[baseStyles.icon, { left: 0 }]}
iconBgStyle={{ backgroundColor: 'transparent' }}
/>
);
......@@ -91,12 +89,21 @@ const renderIcon = (iconName, iconType) => {
);
};
export function PathCardHeading({ title, networkKey }) {
export function LeftScreenHeading({
title,
subtitle,
hasSubtitleIcon,
networkKey
}) {
const titleStyle = composeStyle(
fontStyles.h2,
baseStyles.t_left,
baseStyles.t_normal
);
const titleStyleWithSubtitle = composeStyle(
baseStyles.text,
baseStyles.t_left
);
return (
<View style={baseStyles.bodyWithIcon}>
<AccountIcon
......@@ -105,61 +112,35 @@ export function PathCardHeading({ title, networkKey }) {
style={baseStyles.networkIcon}
/>
<View>
<Text style={titleStyle}>{title}</Text>
<Text style={subtitle ? titleStyleWithSubtitle : titleStyle}>
{title}
</Text>
{renderSubtitle(subtitle, hasSubtitleIcon, true, false, false)}
</View>
</View>
);
}
export function PathListHeading({
export function IdentityHeading({
title,
subtitle,
hasSubtitleIcon,
testID,
networkKey,
onPress
onPressBack
}) {
return (
<TouchableItem
style={baseStyles.bodyWithIcon}
onPress={onPress}
testID={testID}
>
<AccountIcon
address={''}
network={NETWORK_LIST[networkKey]}
style={baseStyles.networkIcon}
/>
<View>
<Text style={[baseStyles.text, baseStyles.t_left]}>{title}</Text>
{renderSubtitle(subtitle, hasSubtitleIcon, true, false, false)}
</View>
</TouchableItem>
);
}
export function IdentityHeading({ onPress, title, subtitle, hasSubtitleIcon }) {
return (
<TouchableItem style={baseStyles.bodyWithIdentity} onPress={onPress}>
<View style={baseStyles.touchable}>
<View style={baseStyles.identityName}>
<Text
style={[baseStyles.text, baseStyles.t_left]}
numberOfLines={1}
ellipsizeMode="middle"
>
{title}
</Text>
<FontAwesome
style={baseStyles.linkIcon}
name="external-link"
color={colors.bg_button}
size={18}
/>
</View>
{renderSubtitle(subtitle, hasSubtitleIcon, true, false, false)}
<View style={baseStyles.bodyWithIdentity}>
<View style={baseStyles.identityName}>
<Text
style={[baseStyles.text, baseStyles.t_left]}
numberOfLines={1}
ellipsizeMode="middle"
>
{title}
</Text>
</View>
</TouchableItem>
{onPressBack && renderBack(onPressBack)}
{renderSubtitle(subtitle, hasSubtitleIcon, true, false, false)}
</View>
);
}
......@@ -167,7 +148,7 @@ export default class ScreenHeading extends React.PureComponent {
static propTypes = {
onPress: PropTypes.func,
subtitle: PropTypes.string,
title: PropTypes.string
title: PropTypes.string.isRequired
};
render() {
const {
......@@ -176,7 +157,6 @@ export default class ScreenHeading extends React.PureComponent {
subtitleL,
hasSubtitleIcon,
error,
onPress,
iconName,
iconType
} = this.props;
......@@ -185,7 +165,6 @@ export default class ScreenHeading extends React.PureComponent {
<View style={baseStyles.body}>
<Text style={baseStyles.text}>{title}</Text>
{renderSubtitle(subtitle, hasSubtitleIcon, subtitleL, error, true)}
{renderBack(onPress)}
{renderIcon(iconName, iconType)}
</View>
);
......@@ -203,7 +182,9 @@ const baseStyles = StyleSheet.create({
marginBottom: 16
},
bodyWithIdentity: {
flexDirection: 'column',
height: 42,
justifyContent: 'center',
paddingLeft: 72,
paddingRight: 32
},
......@@ -241,10 +222,5 @@ const baseStyles = StyleSheet.create({
text: {
...fontStyles.h1,
textAlign: 'center'
},
touchable: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
}
});
......@@ -60,7 +60,7 @@ const unknownNetworkBase = {
prefix: 2,
protocol: NetworkProtocols.UNKNOWN,
secondaryColor: colors.card_bgSolid,
title: 'Unknown network'
title: 'Custom network'
}
};
......
......@@ -26,12 +26,10 @@ import {
NETWORK_LIST,
UnknownNetworkKeys,
SubstrateNetworkKeys,
NetworkProtocols,
defaultNetworkKey
NetworkProtocols
} from '../constants';
import {
navigateToPathsList,
navigateToRootPath,
navigateToSubstrateRoot,
unlockSeedPhrase
} from '../util/navigationHelpers';
......@@ -142,7 +140,7 @@ function AccountNetworkChooser({ navigation, accounts }) {
`//${pathId}`,
seedPhrase,
networkKey,
'Root'
`${networkParams.title} root`
);
onDerivationFinished(derivationSucceed, networkKey, true);
};
......@@ -192,41 +190,14 @@ function AccountNetworkChooser({ navigation, accounts }) {
return <ScreenHeading title={'Create your first Keypair'} />;
} else if (shouldShowMoreNetworks) {
return (
<ScreenHeading
<IdentityHeading
title={'Choose Network'}
onPress={() => setShouldShowMoreNetworks(false)}
onPressBack={() => setShouldShowMoreNetworks(false)}
/>
);
} else {
const identityName = getIdentityName(currentIdentity, identities);
const rootAccount = currentIdentity.meta.get('');
const rootAddress = rootAccount ? rootAccount.address : '';
const onRootKeyPress = async () => {
if (rootAccount == null) {
const seedPhrase = await unlockSeedPhrase(navigation);
const derivationSucceed = await accounts.deriveNewPath(
'',
seedPhrase,
defaultNetworkKey,
''
);
if (!derivationSucceed) {
return alertPathDerivationError();
} else {
navigateToRootPath(navigation);
}
} else {
navigation.navigate('PathDetails', { path: '' });
}
};
return (
<IdentityHeading
title={identityName}
subtitle={rootAddress}
onPress={onRootKeyPress}
hasSubtitleIcon={true}
/>
);
return <IdentityHeading title={identityName} />;
}
};
......
......@@ -22,12 +22,11 @@ import { withNavigation } from 'react-navigation';
import { ScrollView, StyleSheet, View } from 'react-native';
import PathCard from '../components/PathCard';
import PopupMenu from '../components/PopupMenu';
import { PathCardHeading } from '../components/ScreenHeading';
import { LeftScreenHeading } from '../components/ScreenHeading';
import colors from '../colors';
import QrView from '../components/QrView';
import {
getAddressWithPath,
getIdentityName,
getNetworkKeyByPath,
getPathName,
getPathsWithSubstrateNetwork,
......@@ -54,7 +53,6 @@ export function PathDetailsView({ accounts, navigation, path, networkKey }) {
});
const isUnknownNetwork = networkKey === UnknownNetworkKeys.UNKNOWN;
//TODO enable user to select networkKey.
const isRootPath = path === '';
const formattedNetworkKey = isUnknownNetwork ? defaultNetworkKey : networkKey;
const onOptionSelect = value => {
......@@ -67,7 +65,7 @@ export function PathDetailsView({ accounts, navigation, path, networkKey }) {
const listedPaths = getPathsWithSubstrateNetwork(paths, networkKey);
const hasOtherPaths = listedPaths.length > 0;
if (deleteSucceed) {
isSubstratePath(path) && !isRootPath && hasOtherPaths
isSubstratePath(path) && hasOtherPaths
? navigateToPathsList(navigation, networkKey)
: navigation.navigate('AccountNetworkChooser');
} else {
......@@ -86,7 +84,7 @@ export function PathDetailsView({ accounts, navigation, path, networkKey }) {
return (
<View style={styles.body} testID={testIDs.PathDetail.screen}>
<PathCardHeading
<LeftScreenHeading
title="Public Address"
networkKey={formattedNetworkKey}
/>
......@@ -96,7 +94,7 @@ export function PathDetailsView({ accounts, navigation, path, networkKey }) {
onSelect={onOptionSelect}
menuTriggerIconName={'more-vert'}
menuItems={[
{ hide: isUnknownNetwork, text: 'Edit', value: 'PathManagement' },
{ text: 'Edit', value: 'PathManagement' },
{ text: 'Derive Account', value: 'PathDerivation' },
{
testID: testIDs.PathDetail.deleteButton,
......@@ -111,11 +109,7 @@ export function PathDetailsView({ accounts, navigation, path, networkKey }) {
{isUnknownNetwork ? (
<>
<AccountCard
title={
isRootPath
? getIdentityName(currentIdentity, accounts.state.identities)
: getPathName(path, currentIdentity)
}
title={getPathName(path, currentIdentity)}
address={address}
networkKey={formattedNetworkKey}
/>
......
......@@ -28,7 +28,6 @@ import { withAccountStore } from '../util/HOC';
import { withNavigation } from 'react-navigation';
import {
getPathsWithSubstrateNetwork,
getRootPathMeta,
groupPaths,
removeSlash
} from '../util/identitiesUtils';
......@@ -40,15 +39,7 @@ import testIDs from '../../e2e/testIDs';
import Separator from '../components/Separator';
import fontStyles from '../fontStyles';
import colors from '../colors';
import { PathListHeading } from '../components/ScreenHeading';
import {
alertDeriveRootPath,
alertPathDerivationError
} from '../util/alertUtils';
import {
navigateToPathDetails,
unlockSeedPhrase
} from '../util/navigationHelpers';
import { LeftScreenHeading } from '../components/ScreenHeading';
function PathsList({ accounts, navigation }) {
const networkKey = navigation.getParam(
......@@ -83,29 +74,6 @@ function PathsList({ accounts, navigation }) {
const { navigate } = navigation;
const rootPath = `//${networkParams.pathId}`;
const onClickRootPath = () => {
if (isUnknownNetworkPath) return;
const rootPathMeta = getRootPathMeta(currentIdentity, networkKey);
if (rootPathMeta) {
navigate('PathDetails', { path: rootPath });
} else {
alertDeriveRootPath(async () => {
const seedPhrase = await unlockSeedPhrase(navigation);
const derivationSucceed = await accounts.deriveNewPath(
rootPath,
seedPhrase,
networkKey,
''
);
if (derivationSucceed) {
navigateToPathDetails(navigation, networkKey, rootPath);
} else {
alertPathDerivationError();
}
});
}
};
const renderSinglePath = pathsGroup => {
const path = pathsGroup.paths[0];
return (
......@@ -176,10 +144,8 @@ function PathsList({ accounts, navigation }) {
: `//${networkParams.pathId}`;
return (
<View style={styles.body} testID={testIDs.PathsList.screen}>
<PathListHeading
onPress={onClickRootPath}
<LeftScreenHeading
title={networkParams.title}
testID={testIDs.PathsList.rootButton}
subtitle={subtitle}
hasSubtitleIcon={true}
networkKey={networkKey}
......@@ -190,15 +156,15 @@ function PathsList({ accounts, navigation }) {
? renderSinglePath(pathsGroup)
: renderGroupPaths(pathsGroup)
)}
{!isUnknownNetworkPath && (
<ButtonNewDerivation
testID={testIDs.PathsList.deriveButton}
title="Create New Derivation"
onPress={() =>
navigation.navigate('PathDerivation', { parentPath: rootPath })
}
/>
)}
<ButtonNewDerivation
testID={testIDs.PathsList.deriveButton}
title="Create New Derivation"
onPress={() =>
navigation.navigate('PathDerivation', {
parentPath: isUnknownNetworkPath ? '' : rootPath
})
}
/>
</ScrollView>
</View>
);
......
......@@ -360,8 +360,6 @@ export default class AccountsStore extends Container<AccountsStoreState> {
//TODO encrypt seedPhrase with password in the future version,
// current encryption with only seedPhrase is compatible.
updatedIdentity.encryptedSeed = await encryptData(seedPhrase, pin);
//TODO now hard coded to polkadot canary prefix which is 2, future enable user to change that.
await this._addPathToIdentity('', seedPhrase, updatedIdentity, 'Root', 2);
const newIdentities = this.state.identities.concat(updatedIdentity);
this.setState({
currentIdentity: updatedIdentity,
......
......@@ -81,13 +81,6 @@ This identity can only be recovered with its associated recovery phrase.`,
);
};
export const alertDeriveRootPath = onDeriveRootPath =>
Alert.alert(
'Create Root Path',
'By confirm you are about to create the root account for this network',
buildAlertButtons(onDeriveRootPath, 'Confirm')
);
export const alertCopyBackupPhrase = seedPhrase =>
Alert.alert(
'Write this recovery phrase on paper',
......
......@@ -149,15 +149,6 @@ export const getAddressWithPath = (path, identity) => {
: address;
};
export const getRootPathMeta = (identity, networkKey) => {
const rootPathId = `//${NETWORK_LIST[networkKey].pathId}`;
if (identity.meta.has(rootPathId)) {
return identity.meta.get(rootPathId);
} else {
return null;
}
};
export const unlockIdentitySeed = async (pin, identity) => {
const { encryptedSeed } = identity;
const seed = await decryptData(encryptedSeed, pin);
......@@ -168,7 +159,6 @@ export const unlockIdentitySeed = async (pin, identity) => {
export const getExistedNetworkKeys = identity => {
const pathsList = Array.from(identity.addresses.values());
const networkKeysSet = pathsList.reduce((networksSet, path) => {
if (path === '') return networksSet;
let networkKey;
if (isSubstratePath(path)) {
networkKey = getNetworkKeyByPath(path);
......@@ -199,9 +189,8 @@ export const getPathName = (path, lookUpIdentity) => {
) {
return lookUpIdentity.meta.get(path).name;
}
if (!isSubstratePath(path)) {
return 'No name';
}
if (!isSubstratePath(path)) return 'No name';
if (path === '') return 'Identity root';
return extractSubPathName(path);
};
......@@ -226,25 +215,35 @@ export const groupPaths = paths => {
const groupedPaths = paths.reduce((groupedPath, path) => {
if (path === '') {
groupedPath.push({ paths: [''], title: 'Identity root' });
return groupedPath;
}
const rootPath = path.match(pathsRegex.firstPath)[0];
const isUnknownRootPath = Object.values(NETWORK_LIST).every(
v => `//${v.pathId}` !== rootPath
const networkParams = Object.values(NETWORK_LIST).find(
v => `//${v.pathId}` === rootPath
);
if (isUnknownRootPath) {
if (networkParams === undefined) {
insertPathIntoGroup(path, path, groupedPath);
return groupedPath;
}
const isRootPath = path === rootPath;
if (isRootPath) return groupedPath;
if (isRootPath) {
groupedPath.push({ paths: [path], title: `${networkParams.title} root` });
return groupedPath;
}
const subPath = path.slice(rootPath.length);
insertPathIntoGroup(subPath, path, groupedPath);
return groupedPath;
}, []);
return groupedPaths.sort((a, b) => a.paths.length - b.paths.length);
return groupedPaths.sort((a, b) => {
if (a.paths.length === 1 && b.paths.length === 1) {
return a.paths[0].length - b.paths[0].length;
}
return a.paths.length - b.paths.length;
});
};
......@@ -142,9 +142,6 @@ export const navigateToSignedMessage = navigation =>
export const navigateToSignedTx = navigation =>
resetNavigationWithNetworkChooser(navigation, 'SignedTx', { isNew: true });
export const navigateToRootPath = navigation =>
resetNavigationWithNetworkChooser(navigation, 'PathDetails', { path: '' });