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

feat: instant derivation on PathList screen (#648)

* create basic functional screen

* remove rust related files

* use npm libraries

* Update native.ts

* remove redundant files

* use npm package

* fix android building

* fix unit test

* Update actions.yml

* update readme

* make the state change response to ui changes

* update pod

* complete path group and redesign derivation button

* add e2e test for quick derivation

* lint fixes

* update project settings ios
parent af4eacd4
Pipeline #100299 failed with stages
in 4 minutes and 25 seconds
...@@ -25,9 +25,6 @@ Any data transfer from or to the app happens using QR code. By doing so, the mos ...@@ -25,9 +25,6 @@ Any data transfer from or to the app happens using QR code. By doing so, the mos
- [Troubleshooting](https://github.com/paritytech/parity-signer/wiki/Troubleshooting) - [Troubleshooting](https://github.com/paritytech/parity-signer/wiki/Troubleshooting)
- [Publishing](https://github.com/paritytech/parity-signer/wiki/Publishing) - [Publishing](https://github.com/paritytech/parity-signer/wiki/Publishing)
## Changes from 4.3.1
From [4.3.1](https://github.com/paritytech/parity-signer/commit/ea5786c85661d9b176795b9386af640b3e73aff3) we use the latest prebuild NDK (r21) toolchains for building rust libraries for android, so that we do not need to build the standalone NDK toolchains manually. If you have built or develop Parity Signer before 4.3.1, please download the NDK r19 or newer[here](https://developer.android.com/ndk/downloads) and point the `NKD_HOME` environment variable to it with e.g. `export NDK_HOME=/path/to/latest/ndk`
## License ## License
Parity-Signer is [GPL 3.0 licensed](LICENSE). Parity-Signer is [GPL 3.0 licensed](LICENSE).
This diff is collapsed.
...@@ -247,7 +247,7 @@ PODS: ...@@ -247,7 +247,7 @@ PODS:
- React - React
- react-native-safe-area-context (0.7.3): - react-native-safe-area-context (0.7.3):
- React - React
- react-native-substrate-sign (1.0.2): - react-native-substrate-sign (1.0.3):
- React - React
- React-RCTActionSheet (0.62.2): - React-RCTActionSheet (0.62.2):
- React-Core/RCTActionSheetHeaders (= 0.62.2) - React-Core/RCTActionSheetHeaders (= 0.62.2)
...@@ -503,7 +503,7 @@ SPEC CHECKSUMS: ...@@ -503,7 +503,7 @@ SPEC CHECKSUMS:
react-native-netinfo: 1b8691be19549f020d0757b40aca4f355cafeeec react-native-netinfo: 1b8691be19549f020d0757b40aca4f355cafeeec
react-native-randombytes: 3638d24759d67c68f6ccba60c52a7a8a8faa6a23 react-native-randombytes: 3638d24759d67c68f6ccba60c52a7a8a8faa6a23
react-native-safe-area-context: e200d4433aba6b7e60b52da5f37af11f7a0b0392 react-native-safe-area-context: e200d4433aba6b7e60b52da5f37af11f7a0b0392
react-native-substrate-sign: a56bdcb26819beeb7588c1175b6def28c03bce09 react-native-substrate-sign: 646f9915990e3930829d34dafaeb8d00825b02a2
React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c
React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0 React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0
React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71 React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71
......
...@@ -34,7 +34,6 @@ export default class Button extends React.PureComponent<{ ...@@ -34,7 +34,6 @@ export default class Button extends React.PureComponent<{
title: string; title: string;
onPress: ButtonListener; onPress: ButtonListener;
textStyles?: TextStyle; textStyles?: TextStyle;
buttonStyles?: ViewStyle;
aboveKeyboard?: boolean; aboveKeyboard?: boolean;
disabled?: boolean; disabled?: boolean;
small?: boolean; small?: boolean;
...@@ -51,13 +50,12 @@ export default class Button extends React.PureComponent<{ ...@@ -51,13 +50,12 @@ export default class Button extends React.PureComponent<{
small, small,
textStyles, textStyles,
onlyText, onlyText,
buttonStyles,
testID, testID,
style style
} = this.props; } = this.props;
const finalTextStyles = [styles.buttonText, {}]; const finalTextStyles = [styles.buttonText, {}];
const finalButtonStyles = [styles.button, buttonStyles]; const finalButtonStyles = [styles.button, {}];
if (small) { if (small) {
finalTextStyles.push({ fontSize: 14 }); finalTextStyles.push({ fontSize: 14 });
......
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// 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/>.
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import testIDs from '../../test/e2e/testIDs';
import PathCard from './PathCard';
import Separator from './Separator';
import colors from 'styles/colors';
import TouchableItem from 'components/TouchableItem';
import fontStyles from 'styles/fontStyles';
import {
AccountsStoreStateWithIdentity,
Identity,
PathGroup
} from 'types/identityTypes';
import {
isSubstrateNetworkParams,
SubstrateNetworkParams,
UnknownNetworkParams
} from 'types/networkSpecsTypes';
import { removeSlash } from 'utils/identitiesUtils';
import { useSeedRef } from 'utils/seedRefHooks';
import { unlockSeedPhrase } from 'utils/navigationHelpers';
import { alertPathDerivationError } from 'utils/alertUtils';
import { RootStackParamList } from 'types/routes';
type Props = {
accounts: AccountsStoreStateWithIdentity;
currentIdentity: Identity;
pathGroup: PathGroup;
networkParams: SubstrateNetworkParams | UnknownNetworkParams;
};
export default function PathGroupCard({
currentIdentity,
pathGroup,
networkParams,
accounts
}: Props): React.ReactElement {
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
const paths = pathGroup.paths;
const { isSeedRefValid, substrateAddress } = useSeedRef(
currentIdentity.encryptedSeed
);
const _getFullPath = (index: number, isHardDerivation: boolean): string =>
`//${networkParams.pathId}${pathGroup.title}${
isHardDerivation ? '//' : '/'
}${index}`;
const _getNextIndex = (isHardDerivation: boolean): number => {
let index = 0;
while (paths.includes(_getFullPath(index, isHardDerivation))) {
index++;
}
return index;
};
const addDerivationPath = async (
isHardDerivation: boolean
): Promise<void> => {
if (!isSeedRefValid) {
await unlockSeedPhrase(navigation, isSeedRefValid);
navigation.goBack();
}
const nextIndex = _getNextIndex(isHardDerivation);
const nextPath = _getFullPath(nextIndex, isHardDerivation);
const name = removeSlash(`${pathGroup.title}${nextIndex}`);
try {
await accounts.deriveNewPath(
nextPath,
substrateAddress,
(networkParams as SubstrateNetworkParams).genesisHash,
name,
''
);
} catch (error) {
alertPathDerivationError(error.message);
}
};
const _deletePath = async (): Promise<void> => {
const targetPath = paths[paths.length - 1];
await accounts.deletePath(targetPath);
};
const headerTitle = removeSlash(pathGroup.title);
const headerCode = `//${networkParams.pathId}${pathGroup.title}`;
return (
<View key={`group${pathGroup.title}`} style={{ marginTop: 24 }}>
<Separator shadow={true} style={styles.separator} />
<View style={styles.header}>
<View style={styles.headerText}>
<View>
<Text style={fontStyles.t_prefix}>{headerTitle}</Text>
<Text style={fontStyles.t_codeS}>{headerCode}</Text>
</View>
</View>
{isSubstrateNetworkParams(networkParams) && (
<TouchableItem
onPress={(): any => addDerivationPath(true)}
style={styles.derivationButton}
testID={`${testIDs.PathsList.pathsGroup}${pathGroup.title}`}
>
<Text style={styles.derivationIcon}>+</Text>
<Text style={styles.derivationTextLabel}>{'new derivation'}</Text>
</TouchableItem>
)}
</View>
{paths.map(path => (
<PathCard
key={path}
testID={testIDs.PathsList.pathCard + path}
identity={currentIdentity}
path={path}
onPress={(): void => navigation.navigate('PathDetails', { path })}
/>
))}
</View>
);
}
const styles = StyleSheet.create({
derivationButton: {
alignItems: 'center',
backgroundColor: 'black',
height: 63,
justifyContent: 'center',
marginHorizontal: 0,
marginVertical: 0,
paddingHorizontal: 10
},
derivationIcon: {
...fontStyles.i_medium,
color: colors.text.main,
fontWeight: 'bold'
},
derivationTextLabel: {
...fontStyles.a_text,
color: colors.text.main
},
header: {
flexDirection: 'row',
height: 63,
paddingLeft: 16,
paddingRight: 0
},
headerText: {
flexGrow: 1,
marginVertical: 16
},
separator: {
height: 0,
marginVertical: 0
}
});
...@@ -48,7 +48,7 @@ export default class QRScannerAndDerivationTab extends React.PureComponent<{ ...@@ -48,7 +48,7 @@ export default class QRScannerAndDerivationTab extends React.PureComponent<{
style={styles.derivationButton} style={styles.derivationButton}
testID={derivationTestID} testID={derivationTestID}
> >
<Text style={styles.icon}>//</Text> <Text style={styles.icon}>+</Text>
<Text style={styles.textLabel}>{title}</Text> <Text style={styles.textLabel}>{title}</Text>
</TouchableItem> </TouchableItem>
</View> </View>
...@@ -67,6 +67,7 @@ const styles = StyleSheet.create({ ...@@ -67,6 +67,7 @@ const styles = StyleSheet.create({
icon: { icon: {
...fontStyles.i_large, ...fontStyles.i_large,
color: colors.signal.main, color: colors.signal.main,
fontWeight: 'bold',
marginTop: 8 marginTop: 8
}, },
tab: { tab: {
......
...@@ -171,7 +171,7 @@ const substrateNetworkBase: { ...@@ -171,7 +171,7 @@ const substrateNetworkBase: {
[SubstrateNetworkKeys.POLKADOT]: { [SubstrateNetworkKeys.POLKADOT]: {
color: '#E6027A', color: '#E6027A',
decimals: 12, decimals: 12,
genesisHash: null, genesisHash: SubstrateNetworkKeys.POLKADOT,
logo: require('res/img/logos/Polkadot.png'), logo: require('res/img/logos/Polkadot.png'),
order: 1, order: 1,
pathId: 'polkadot', pathId: 'polkadot',
......
...@@ -125,7 +125,11 @@ export async function processBarCode( ...@@ -125,7 +125,11 @@ export async function processBarCode(
await scannerStore.setData(accounts); await scannerStore.setData(accounts);
scannerStore.clearMultipartProgress(); scannerStore.clearMultipartProgress();
const sender = scannerStore.getSender(); const sender = scannerStore.getSender();
if (!sender) throw new Error(strings.ERROR_NO_SENDER_FOUND); if (!sender)
return showErrorMessage(
strings.ERROR_TITLE,
strings.ERROR_NO_SENDER_FOUND
);
if (sender.isLegacy) { if (sender.isLegacy) {
if (scannerStore.getType() === 'transaction') { if (scannerStore.getType() === 'transaction') {
return navigation.navigate('AccountUnlockAndSign', { return navigation.navigate('AccountUnlockAndSign', {
......
...@@ -15,16 +15,17 @@ ...@@ -15,16 +15,17 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { ScrollView, Text, View } from 'react-native'; import { ScrollView } from 'react-native';
import { PathDetailsView } from './PathDetails'; import { PathDetailsView } from './PathDetails';
import { PathGroup } from 'types/identityTypes';
import PathGroupCard from 'components/PathGroupCard';
import { useUnlockSeed } from 'utils/navigationHelpers'; import { useUnlockSeed } from 'utils/navigationHelpers';
import { useSeedRef } from 'utils/seedRefHooks'; import { useSeedRef } from 'utils/seedRefHooks';
import { SafeAreaViewContainer } from 'components/SafeAreaContainer'; import { SafeAreaViewContainer } from 'components/SafeAreaContainer';
import { NETWORK_LIST, UnknownNetworkKeys } from 'constants/networkSpecs'; import { NETWORK_LIST, UnknownNetworkKeys } from 'constants/networkSpecs';
import testIDs from 'e2e/testIDs'; import testIDs from 'e2e/testIDs';
import { PathGroup } from 'types/identityTypes';
import { import {
isEthereumNetworkParams, isEthereumNetworkParams,
isUnknownNetworkParams isUnknownNetworkParams
...@@ -33,13 +34,11 @@ import { NavigationAccountIdentityProps } from 'types/props'; ...@@ -33,13 +34,11 @@ import { NavigationAccountIdentityProps } from 'types/props';
import { withAccountStore, withCurrentIdentity } from 'utils/HOC'; import { withAccountStore, withCurrentIdentity } from 'utils/HOC';
import { import {
getPathsWithSubstrateNetworkKey, getPathsWithSubstrateNetworkKey,
groupPaths, groupPaths
removeSlash
} from 'utils/identitiesUtils'; } from 'utils/identitiesUtils';
import QRScannerAndDerivationTab from 'components/QRScannerAndDerivationTab'; import QRScannerAndDerivationTab from 'components/QRScannerAndDerivationTab';
import PathCard from 'components/PathCard'; import PathCard from 'components/PathCard';
import Separator from 'components/Separator'; import Separator from 'components/Separator';
import fontStyles from 'styles/fontStyles';
import { LeftScreenHeading } from 'components/ScreenHeading'; import { LeftScreenHeading } from 'components/ScreenHeading';
function PathsList({ function PathsList({
...@@ -97,39 +96,6 @@ function PathsList({ ...@@ -97,39 +96,6 @@ function PathsList({
); );
}; };
const renderGroupPaths = (pathsGroup: PathGroup): React.ReactElement => (
<View key={`group${pathsGroup.title}`} style={{ marginTop: 24 }}>
<Separator
shadow={true}
style={{
height: 0,
marginVertical: 0
}}
/>
<View
style={{
marginVertical: 16,
paddingHorizontal: 16
}}
>
<Text style={fontStyles.t_prefix}>{removeSlash(pathsGroup.title)}</Text>
<Text style={fontStyles.t_codeS}>
{networkParams.pathId}
{pathsGroup.title}
</Text>
</View>
{pathsGroup.paths.map(path => (
<PathCard
key={path}
testID={testIDs.PathsList.pathCard + path}
identity={currentIdentity}
path={path}
onPress={(): void => navigate('PathDetails', { path })}
/>
))}
</View>
);
return ( return (
<SafeAreaViewContainer> <SafeAreaViewContainer>
<ScrollView testID={testIDs.PathsList.screen}> <ScrollView testID={testIDs.PathsList.screen}>
...@@ -139,9 +105,17 @@ function PathsList({ ...@@ -139,9 +105,17 @@ function PathsList({
networkKey={networkKey} networkKey={networkKey}
/> />
{(pathsGroups as PathGroup[]).map(pathsGroup => {(pathsGroups as PathGroup[]).map(pathsGroup =>
pathsGroup.paths.length === 1 pathsGroup.paths.length === 1 ? (
? renderSinglePath(pathsGroup) renderSinglePath(pathsGroup)
: renderGroupPaths(pathsGroup) ) : (
<PathGroupCard
currentIdentity={currentIdentity}
pathGroup={pathsGroup}
networkParams={networkParams}
accounts={accounts}
key={pathsGroup.title}
/>
)
)} )}
<Separator style={{ backgroundColor: 'transparent' }} /> <Separator style={{ backgroundColor: 'transparent' }} />
</ScrollView> </ScrollView>
......
...@@ -10,7 +10,7 @@ export type NetworkParams = ...@@ -10,7 +10,7 @@ export type NetworkParams =
export type SubstrateNetworkParams = { export type SubstrateNetworkParams = {
color: string; color: string;
decimals: number; decimals: number;
genesisHash: string | null; genesisHash: string;
logo: number; logo: number;
order: number; order: number;
pathId: string; pathId: string;
......
...@@ -81,10 +81,17 @@ describe('Load test', () => { ...@@ -81,10 +81,17 @@ describe('Load test', () => {
await testExist(PathsList.pathCard + `//kusama${defaultPath}${childPath}`); await testExist(PathsList.pathCard + `//kusama${defaultPath}${childPath}`);
}); });
it('derive new account with quick derivation button', async () => {
await tapBack();
const deriveButtonId = `${PathsList.pathsGroup}${defaultPath}`;
await testExist(deriveButtonId);
await testTap(deriveButtonId);
await testExist(PathsList.pathCard + `//kusama${defaultPath}//0`);
});
it('need pin after application go to the background', async () => { it('need pin after application go to the background', async () => {
await device.sendToHome(); await device.sendToHome();
await device.launchApp({ newInstance: false }); await device.launchApp({ newInstance: false });
await tapBack();
await testTap(PathsList.deriveButton); await testTap(PathsList.deriveButton);
await testUnlockPin(pinCode); await testUnlockPin(pinCode);
await testInput(PathDerivation.pathInput, secondPath); await testInput(PathDerivation.pathInput, secondPath);
......
...@@ -83,6 +83,7 @@ const testIDs = { ...@@ -83,6 +83,7 @@ const testIDs = {
PathsList: { PathsList: {
deriveButton: 'path_list_derive_button', deriveButton: 'path_list_derive_button',
pathCard: 'path_list_path_card', pathCard: 'path_list_path_card',
pathsGroup: 'path_list_paths_group',
scanButton: 'path_list_scan_button', scanButton: 'path_list_scan_button',
screen: 'path_list_screen' screen: 'path_list_screen'
}, },
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment