diff --git a/.eslintrc.js b/.eslintrc.js index aa2b7b50ef..f353b562b6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,7 @@ module.exports = { extends: ["@react-native-community", "plugin:prettier/recommended"], overrides: [ { - "files": ["e2e/*.spec.js", "e2e/init.js"], + "files": ["e2e/*.spec.js", "e2e/init.js", "e2e/e2eUtils.js"], "rules": { "no-undef": "off" } @@ -22,6 +22,7 @@ module.exports = { }, }, rules: { + "no-bitwise": "off", "comma-dangle": ["error", "never"], "object-curly-spacing": ["error", "always"], "quotes": ["error", "single", { "avoidEscape": true }], diff --git a/android/app/src/main/assets/fonts/RobotoMono-Regular.ttf b/android/app/src/main/assets/fonts/RobotoMono-Regular.ttf new file mode 100644 index 0000000000..5919b5d1bf Binary files /dev/null and b/android/app/src/main/assets/fonts/RobotoMono-Regular.ttf differ diff --git a/e2e/e2eUtils.js b/e2e/e2eUtils.js new file mode 100644 index 0000000000..22f3f32500 --- /dev/null +++ b/e2e/e2eUtils.js @@ -0,0 +1,57 @@ +// Copyright 2015-2019 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 . + +'use strict'; + +import testIDs from './testIDs'; +const { IdentityPin } = testIDs; + +export const testTap = async buttonId => await element(by.id(buttonId)).tap(); + +export const testVisible = async componentId => + await expect(element(by.id(componentId))).toBeVisible(); + +export const testExist = async componentId => + await expect(element(by.id(componentId))).toExist(); + +export const testNotExist = async componentId => + await expect(element(by.id(componentId))).toNotExist(); + +export const testNotVisible = async componentId => + await expect(element(by.id(componentId))).toBeNotVisible(); + +export const tapBack = async () => + await element(by.id(testIDs.Header.headerBackButton)) + .atIndex(0) + .tap(); + +export const testInput = async (inputId, inputText) => { + await element(by.id(inputId)).typeText(inputText); + await element(by.id(inputId)).tapReturnKey(); +}; + +export const testScrollAndTap = async (buttonId, screenId) => { + await waitFor(element(by.id(buttonId))) + .toBeVisible() + .whileElement(by.id(screenId)) + .scroll(100, 'down'); + await testTap(buttonId); +}; + +export const testUnlockPin = async pinCode => { + await testInput(IdentityPin.unlockPinInput, pinCode); + await testTap(IdentityPin.unlockPinButton); +}; diff --git a/e2e/firstTest.spec.js b/e2e/firstTest.spec.js deleted file mode 100644 index 8ac8de3f36..0000000000 --- a/e2e/firstTest.spec.js +++ /dev/null @@ -1,13 +0,0 @@ -import testIDs from './testIDs'; - -describe('Load test', () => { - it('should have account list screen', async () => { - await expect(element(by.id(testIDs.TacScreen.tacView))).toBeVisible(); - await element(by.id(testIDs.TacScreen.agreePrivacyButton)).tap(); - await element(by.id(testIDs.TacScreen.agreeTacButton)).tap(); - await element(by.id(testIDs.TacScreen.nextButton)).tap(); - await expect( - element(by.id(testIDs.AccountListScreen.accountList)) - ).toBeVisible(); - }); -}); diff --git a/e2e/identityManipulation.spec.js b/e2e/identityManipulation.spec.js new file mode 100644 index 0000000000..4d9a4687d5 --- /dev/null +++ b/e2e/identityManipulation.spec.js @@ -0,0 +1,143 @@ +// Copyright 2015-2019 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 . + +'use strict'; + +import testIDs from './testIDs'; +import { + tapBack, + testExist, + testInput, + testNotExist, + testNotVisible, + testScrollAndTap, + testTap, + testUnlockPin, + testVisible +} from './e2eUtils'; + +const { + TacScreen, + AccountNetworkChooser, + IdentitiesSwitch, + IdentityManagement, + IdentityNew, + IdentityBackup, + IdentityPin, + PathDerivation, + PathDetail, + PathsList, + SignedTx, + TxDetails +} = testIDs; + +const pinCode = '123456'; +const mockIdentityName = 'mockIdentity'; +const substrateNetworkButtonIndex = AccountNetworkChooser.networkButton + '2'; //Need change if network list changes +const fundingPath = '//funding/0'; +const mockSeedPhrase = + 'split cradle example drum veteran swear cruel pizza guilt surface mansion film grant benefit educate marble cargo ignore bind include advance grunt exile grow'; + +const testSetUpDefaultPath = async () => { + await testInput(IdentityPin.setPin, pinCode); + await testInput(IdentityPin.confirmPin, pinCode); + await testTap(IdentityPin.submitButton); + await testVisible(AccountNetworkChooser.chooserScreen); + await testScrollAndTap( + substrateNetworkButtonIndex, + testIDs.AccountNetworkChooser.chooserScreen + ); + await testUnlockPin(pinCode); + await testExist(PathsList.pathCard + '//kusama_CC2//default'); +}; + +describe('Load test', async () => { + beforeAll(async () => { + if (device.getPlatform() === 'ios') { + await device.clearKeychain(); + } + await device.launchApp({ permissions: { camera: 'YES' } }); + }); + + it('should have account list screen', async () => { + await testVisible(TacScreen.tacView); + await testTap(TacScreen.agreePrivacyButton); + await testTap(TacScreen.agreeTacButton); + await testTap(TacScreen.nextButton); + await testVisible(AccountNetworkChooser.noAccountScreen); + }); + + it('recover a identity with seed phrase', async () => { + await testTap(AccountNetworkChooser.recoverButton); + await testVisible(IdentityNew.seedInput); + await element(by.id(IdentityNew.seedInput)).typeText(mockSeedPhrase); + await testInput(IdentityNew.nameInput, mockIdentityName); + await testTap(IdentityNew.recoverButton); + await testSetUpDefaultPath(); + }); + + it('create a new identity with default substrate account', async () => { + await tapBack(); + await testTap(IdentitiesSwitch.toggleButton); + await testTap(IdentitiesSwitch.addIdentityButton); + await testNotVisible(IdentityNew.seedInput); + await testTap(IdentityNew.createButton); + await testVisible(IdentityBackup.seedText); + await testTap(IdentityBackup.nextButton); + await element(by.text('Proceed')).tap(); + await testSetUpDefaultPath(); + }); + + it('derive a new key', async () => { + await testTap(PathsList.deriveButton); + await testInput(PathDerivation.nameInput, 'first one'); + await testInput(PathDerivation.pathInput, fundingPath); + await testTap(PathDerivation.deriveButton); + await testUnlockPin(pinCode); + await testExist(PathsList.pathCard + `//kusama_CC2${fundingPath}`); + }); + + it('delete a path', async () => { + await tapBack(); + await testTap(AccountNetworkChooser.networkButton + '0'); + await testTap(PathsList.pathCard + `//kusama_CC2${fundingPath}`); + await testTap(PathDetail.popupMenuButton); + await testTap(PathDetail.deleteButton); + await element(by.text('Delete')).tap(); + await testUnlockPin(pinCode); + await testNotExist(PathsList.pathCard + `//kusama_CC2${fundingPath}`); + }); + + it('should sign the transaction', async () => { + await tapBack(); + await testTap(AccountNetworkChooser.scanButton); + await testScrollAndTap(TxDetails.signButton, TxDetails.scrollScreen); + await testUnlockPin(pinCode); + await testVisible(SignedTx.qrView); + }); + + it('delete identity', async () => { + await element(by.id(IdentitiesSwitch.toggleButton)) + .atIndex(0) + .tap(); + await testTap(IdentitiesSwitch.manageIdentityButton); + await testTap(IdentityManagement.popupMenuButton); + await testTap(IdentityManagement.deleteButton); + await element(by.text('Delete')).tap(); + await testUnlockPin(pinCode); + await testVisible(IdentitiesSwitch.modal); + }); +}); diff --git a/e2e/init.js b/e2e/init.js index 7a5ed06347..5d04e50487 100644 --- a/e2e/init.js +++ b/e2e/init.js @@ -1,3 +1,21 @@ +// Copyright 2015-2019 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 . + +'use strict'; + const detox = require('detox'); const config = require('../package.json').detox; const adapter = require('detox/runners/jest/adapter'); @@ -12,7 +30,7 @@ jasmine.getEnv().addReporter(adapter); jasmine.getEnv().addReporter(specReporter); beforeAll(async () => { - await detox.init(config); + await detox.init(config, { launchApp: false }); }); beforeEach(async () => { diff --git a/e2e/mock.js b/e2e/mock.js new file mode 100644 index 0000000000..49ca453424 --- /dev/null +++ b/e2e/mock.js @@ -0,0 +1,36 @@ +// Copyright 2015-2019 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 . + +'use strict'; + +import { NETWORK_LIST, SubstrateNetworkKeys } from '../src/constants'; + +export const signingTestIdentityPath = `//${NETWORK_LIST[SubstrateNetworkKeys.KUSAMA].pathID}//default`; + +const setRemarkExtrinsicKusama = + '47900000100005301023c36776005aec2f32a34c109dc791a82edef980eec3be80da938ac9bcc68217220170000010c11111165030000fa030000e3777fa922cafbff200cadeaea1a76bd7898ad5b89f7848999058b50e715f636dbb5aefb451e26bd64faf476301f980437d87c0d88dec1a8c7a3eb3cc82e9bbb0ec'; + +export const createMockSignRequest = () => ({ + bounds: { + height: 1440, + origin: [], + width: 1920 + }, + data: '', + rawData: setRemarkExtrinsicKusama, + target: 319, + type: 'QR_CODE' +}); diff --git a/e2e/testIDs.js b/e2e/testIDs.js index 48d607c51d..c04bcb30a7 100644 --- a/e2e/testIDs.js +++ b/e2e/testIDs.js @@ -1,12 +1,93 @@ +// Copyright 2015-2019 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 . + +'use strict'; + const testIDs = { AccountListScreen: { accountList: 'accountList' }, + AccountNetworkChooser: { + addNewNetworkButton: 'anc_add_button', + chooserScreen: 'anc_chooser_scree', + createButton: 'anc_create_button', + networkButton: 'anc_network_button', + noAccountScreen: 'anc_no_account_screen', + recoverButton: 'anc_recover_button', + scanButton: 'anc_scan_button', + showExistedButton: 'anc_show_existed' + }, + Header: { + headerBackButton: 'header_back_button' + }, + IdentitiesSwitch: { + addIdentityButton: 'identities_switch_add_identity', + manageIdentityButton: 'identities_switch_manager_button', + modal: 'identity_switch_modal', + toggleButton: 'identities_switch_toggle_button' + }, + IdentityBackup: { + nextButton: 'identity_backup_next', + seedText: 'identity_backup_seed' + }, + IdentityManagement: { + deleteButton: 'identity_management_delete_button', + popupMenuButton: 'identity_management_popup_menu' + }, + IdentityNew: { + createButton: 'identity_new_create_button', + nameInput: 'identity_new_name_input', + recoverButton: 'identity_new_recover_button', + seedInput: 'identity_new_seed_input' + }, + IdentityPin: { + confirmPin: 'identity_pin_confirm', + scrollScreen: 'identity_pin_scroll', + setPin: 'identity_pin_set', + submitButton: 'identity_submit_button', + unlockPinButton: 'identity_unlock_pin_button', + unlockPinInput: 'identity_unlock_pin_input' + }, + PathDerivation: { + deriveButton: 'path_derivation_derive_button', + nameInput: 'path_derivation_name_input', + pathInput: 'path_derivation_path_input' + }, + PathDetail: { + deleteButton: 'path_detail_delete_button', + popupMenuButton: 'path_detail_popup_menu_button' + }, + PathsList: { + deriveButton: 'path_list_derive_button', + pathCard: 'path_list_path_card', + scanButton: 'path_list_scan_button' + }, + SignedMessage: {}, + SignedTx: { + qrView: 'signed_tx_qr_view' + }, TacScreen: { agreePrivacyButton: 'tac_privacy', agreeTacButton: 'tac_agree', nextButton: 'tac_next', tacView: 'tac_view' + }, + TxDetails: { + scrollScreen: 'tx_details_scroll', + signButton: 'tx_details_sign_button' } }; diff --git a/ios/NativeSigner.xcodeproj/project.pbxproj b/ios/NativeSigner.xcodeproj/project.pbxproj index 369899772b..39498aa6a3 100644 --- a/ios/NativeSigner.xcodeproj/project.pbxproj +++ b/ios/NativeSigner.xcodeproj/project.pbxproj @@ -5,7 +5,6 @@ }; objectVersion = 46; objects = { - /* Begin PBXBuildFile section */ 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; @@ -45,6 +44,7 @@ 5DC40D98E51D492C8FF692B5 /* Manifold-CF-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = 31AAF2CB51C04377BFC79634 /* Manifold-CF-Light.otf */; }; 6686E5A219254E3D8880285E /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F2FB2DDD90964B3BB1FC813C /* Octicons.ttf */; }; 6701864923270B1100A14061 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 6701864823270B1100A14061 /* assets */; }; + 67E3BED3237C17A6007882FA /* RobotoMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 67E3BEA2237C17A6007882FA /* RobotoMono-Regular.ttf */; }; 6FBA1E2F42104B26A94F3EE0 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 84D1117E71B04C839F00F619 /* Ionicons.ttf */; }; 740B033F930D4E6FB906D8C2 /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CB5C4A7F0E8B4E4E98E06EFD /* MaterialCommunityIcons.ttf */; }; 768CDFD4C47549429C87DEDD /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6A7AB3BD3C1A4E0EB3C4B3B6 /* Entypo.ttf */; }; @@ -477,6 +477,7 @@ 5F744F56289845F0A1085BBB /* Roboto-MediumItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-MediumItalic.ttf"; path = "../res/fonts/Roboto-MediumItalic.ttf"; sourceTree = ""; }; 624E6C0FF4A64A97A7D51BEF /* Manifold-CF-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Manifold-CF-Bold.otf"; path = "../res/fonts/Manifold-CF-Bold.otf"; sourceTree = ""; }; 6701864823270B1100A14061 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = ""; }; + 67E3BEA2237C17A6007882FA /* RobotoMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "RobotoMono-Regular.ttf"; sourceTree = ""; }; 6A7AB3BD3C1A4E0EB3C4B3B6 /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = ""; }; 6C53A63D96B24FDD95AF1C97 /* Fontisto.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Fontisto.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf"; sourceTree = ""; }; 7733AB637FF54DE5B174F42C /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; }; @@ -765,6 +766,7 @@ 53880E8FF84C419EB11ACA5C /* Roboto-Light.ttf */, 8C99AB759A004CEB88AC4455 /* Roboto-LightItalic.ttf */, E3DA81F74A0847378E71E280 /* Roboto-Medium.ttf */, + 67E3BEA2237C17A6007882FA /* RobotoMono-Regular.ttf */, 5F744F56289845F0A1085BBB /* Roboto-MediumItalic.ttf */, 5CD421FF72D947B0AA9EBABB /* Roboto-Regular.ttf */, 020454D0D3C74A398EC7F440 /* Roboto-Thin.ttf */, @@ -1415,6 +1417,7 @@ 7A7137D6EEDE40C184F30C15 /* Roboto-Regular.ttf in Resources */, 16614F66487241CE933918B8 /* Roboto-Thin.ttf in Resources */, 0CFC8839F5B54F888E6C01DE /* Roboto-ThinItalic.ttf in Resources */, + 67E3BED3237C17A6007882FA /* RobotoMono-Regular.ttf in Resources */, 3C31DDCB4CD0465084344D5F /* Manifold-CF-Bold.otf in Resources */, AD0B6F7EACB74BA7A42D2A2E /* Manifold-CF-Demi-Bold.otf in Resources */, F1569210427145DEBBB5B898 /* Manifold-CF-Extra-Bold.otf in Resources */, diff --git a/ios/NativeSigner/Info.plist b/ios/NativeSigner/Info.plist index 0da84f6d22..a93fa124c6 100644 --- a/ios/NativeSigner/Info.plist +++ b/ios/NativeSigner/Info.plist @@ -43,6 +43,7 @@ LaunchScreen UIAppFonts + RobotoMono-Regular.ttf Roboto-Black.ttf Roboto-BlackItalic.ttf Roboto-Bold.ttf diff --git a/ios/RobotoMono-Regular.ttf b/ios/RobotoMono-Regular.ttf new file mode 100644 index 0000000000..5919b5d1bf Binary files /dev/null and b/ios/RobotoMono-Regular.ttf differ diff --git a/jest.config.js b/jest.config.js index 4c729261f3..8cbfc1cafc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,7 +19,7 @@ const { defaults } = require('jest-config'); module.exports = { moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], preset: 'react-native', - roots: ['/src'], + roots: ['/specs'], setupFiles: ['/jest-setup.js'], testEnvironment: 'node', testPathIgnorePatterns: ['/node_modules/'], diff --git a/package.json b/package.json index c13b68100b..369a46746b 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,11 @@ "start": "NODE_OPTIONS=--max_old_space_size=8192 react-native start", "test": "jest", "test-rust": "cd ./rust/signer && cargo test && cd ../..", - "build-e2e:android": "detox build -c android.emu.debug", - "test-e2e:android": "detox test -c android.emu.debug", + "build-e2e:android": "detox build -c android.emu.debug -l info", + "test-e2e:android": "detox test -c android.emu.debug -l info --noStackTrace", "e2e:android": "yarn run build-e2e:android && yarn run test-e2e:android", - "build-e2e:ios": "detox build -c ios.sim.debug", - "test-e2e:ios": "detox test -c ios.sim.debug", + "build-e2e:ios": "detox build -c ios.sim.debug -l info", + "test-e2e:ios": "detox test -c ios.sim.debug -l info --noStackTrace", "e2e:ios": "yarn run build-e2e:ios && yarn run test-e2e:ios" }, "husky": { @@ -51,6 +51,7 @@ "react": "^16.9.0", "react-native": "0.60.5", "react-native-camera": "^3.4.0", + "react-native-elements": "^1.2.6", "react-native-gesture-handler": "^1.4.1", "react-native-keyboard-aware-scroll-view": "^0.9.1", "react-native-markdown-renderer": "^3.2.8", @@ -75,7 +76,7 @@ "babel-eslint": "10.0.3", "babel-jest": "^24.9.0", "babel-plugin-rewrite-require": "^1.14.5", - "detox": "^14.5.0", + "detox": "^14.7.0", "eslint": "^6.3.0", "eslint-config-prettier": "^6.2.0", "eslint-plugin-prettier": "^3.1.0", diff --git a/res/fonts/RobotoMono-Regular.ttf b/res/fonts/RobotoMono-Regular.ttf new file mode 100755 index 0000000000..5919b5d1bf Binary files /dev/null and b/res/fonts/RobotoMono-Regular.ttf differ diff --git a/res/img/card_shadow-1.png b/res/img/card_shadow-1.png new file mode 100644 index 0000000000..a6a031fb25 Binary files /dev/null and b/res/img/card_shadow-1.png differ diff --git a/res/img/card_shadow.png b/res/img/card_shadow.png new file mode 100644 index 0000000000..ad2454ea2d Binary files /dev/null and b/res/img/card_shadow.png differ diff --git a/res/img/card_shadow@2x.png b/res/img/card_shadow@2x.png new file mode 100644 index 0000000000..5bcac89be7 Binary files /dev/null and b/res/img/card_shadow@2x.png differ diff --git a/icon.png b/res/img/icon.png similarity index 100% rename from icon.png rename to res/img/icon.png diff --git a/res/img/logos/eth-classic.png b/res/img/logos/eth-classic.png new file mode 100644 index 0000000000..7d38a69f2e Binary files /dev/null and b/res/img/logos/eth-classic.png differ diff --git a/res/img/logos/eth-classic@2x.png b/res/img/logos/eth-classic@2x.png new file mode 100644 index 0000000000..b8f66d0d0d Binary files /dev/null and b/res/img/logos/eth-classic@2x.png differ diff --git a/res/img/logos/eth-classic@3x.png b/res/img/logos/eth-classic@3x.png new file mode 100644 index 0000000000..e5919749d5 Binary files /dev/null and b/res/img/logos/eth-classic@3x.png differ diff --git a/res/img/logos/eth.png b/res/img/logos/eth.png new file mode 100644 index 0000000000..2c063d2d2c Binary files /dev/null and b/res/img/logos/eth.png differ diff --git a/res/img/logos/eth@2x.png b/res/img/logos/eth@2x.png new file mode 100644 index 0000000000..a20e4f6d47 Binary files /dev/null and b/res/img/logos/eth@2x.png differ diff --git a/res/img/logos/eth@3x.png b/res/img/logos/eth@3x.png new file mode 100644 index 0000000000..8c31019dd1 Binary files /dev/null and b/res/img/logos/eth@3x.png differ diff --git a/res/img/logos/kusama.png b/res/img/logos/kusama.png new file mode 100644 index 0000000000..c66cff1180 Binary files /dev/null and b/res/img/logos/kusama.png differ diff --git a/res/img/logos/kusama@2x.png b/res/img/logos/kusama@2x.png new file mode 100644 index 0000000000..bebf413075 Binary files /dev/null and b/res/img/logos/kusama@2x.png differ diff --git a/res/img/logos/kusama@3x.png b/res/img/logos/kusama@3x.png new file mode 100644 index 0000000000..b8c8e5276e Binary files /dev/null and b/res/img/logos/kusama@3x.png differ diff --git a/res/img/logos/substrate-dev.png b/res/img/logos/substrate-dev.png new file mode 100644 index 0000000000..e7ae85095d Binary files /dev/null and b/res/img/logos/substrate-dev.png differ diff --git a/res/img/logos/substrate-dev@2x.png b/res/img/logos/substrate-dev@2x.png new file mode 100644 index 0000000000..fa8b637a39 Binary files /dev/null and b/res/img/logos/substrate-dev@2x.png differ diff --git a/res/img/logos/substrate-dev@3x.png b/res/img/logos/substrate-dev@3x.png new file mode 100644 index 0000000000..c088856522 Binary files /dev/null and b/res/img/logos/substrate-dev@3x.png differ diff --git a/src/util/checksum.spec.js b/specs/util/checksum.spec.js similarity index 94% rename from src/util/checksum.spec.js rename to specs/util/checksum.spec.js index 001f404ad2..ba6b7d287f 100644 --- a/src/util/checksum.spec.js +++ b/specs/util/checksum.spec.js @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { checksummedAddress } from './checksum'; +'use strict'; + +import { checksummedAddress } from '../../src/util/checksum'; describe('checksum', () => { it('create a proper checksum of an address', () => { diff --git a/src/util/decoders.spec.js b/specs/util/decoders.spec.js similarity index 96% rename from src/util/decoders.spec.js rename to specs/util/decoders.spec.js index fb40f8cfd7..0e0de67e11 100644 --- a/src/util/decoders.spec.js +++ b/specs/util/decoders.spec.js @@ -27,7 +27,10 @@ import Call from '@polkadot/types/primitive/Generic/Call'; import { u8aConcat } from '@polkadot/util'; import { checkAddress, decodeAddress } from '@polkadot/util-crypto'; -import { SUBSTRATE_NETWORK_LIST, SubstrateNetworkKeys } from '../constants'; +import { + SUBSTRATE_NETWORK_LIST, + SubstrateNetworkKeys +} from '../../src/constants'; import { constructDataFromBytes, rawDataToU8A, @@ -35,9 +38,9 @@ import { hexToAscii, decodeToString, isJsonString -} from './decoders'; -import { isAscii } from './strings'; -import kusamaData from './static-kusama'; +} from '../../src/util/decoders'; +import { isAscii } from '../../src/util/strings'; +import kusamaData from '../../src/util/static-kusama'; const SUBSTRATE_ID = new Uint8Array([0x53]); const CRYPTO_SR25519 = new Uint8Array([0x01]); @@ -97,6 +100,7 @@ const SIGN_TX_TEST = u8aConcat( new GenericExtrinsicPayload(SIGNER_PAYLOAD_TEST, { version: 4 }).toU8a() ); +/* eslint-disable-next-line jest/no-disabled-tests */ describe.skip('sanity check', () => { it('sanity check address is kusama', () => { expect(checkAddress(KUSAMA_ADDRESS, 2)).toEqual([true, null]); diff --git a/specs/util/identitiesUtils.spec.js b/specs/util/identitiesUtils.spec.js new file mode 100644 index 0000000000..ffebfb98dd --- /dev/null +++ b/specs/util/identitiesUtils.spec.js @@ -0,0 +1,179 @@ +// Copyright 2015-2019 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 . + +'use strict'; + +import { + deserializeIdentities, + getAvailableNetworkKeys, + getPathName, + groupPaths, + serializeIdentities +} from '../../src/util/identitiesUtils'; +import { + EthereumNetworkKeys, + SubstrateNetworkKeys, + UnknownNetworkKeys +} from '../../src/constants'; + +const accountIdFunding1 = 'address1', + accountIdFunding2 = 'address2', + accountIdSoft = 'address3', + accountIdPolkadot = 'address5', + accountIdStaking = 'address4', + accountIdEthereum = 'address6', + accountIdDefault = 'addressDefault', + paths = [ + '//kusama_CC2//default', + '//kusama_CC2//funding/1', + '//kusama_CC2/softKey1', + '//kusama_CC2//funding/2', + '//kusama_CC2//staking/1', + '//polkadot//default', + '1' + ], + metaDefault = { + accountId: accountIdDefault, + createdAt: 1571068850409, + name: '', + updatedAt: 1571078850509 + }, + metaFunding1 = { + accountId: accountIdFunding1, + createdAt: 1571068850409, + name: 'funding account1', + updatedAt: 1571078850509 + }, + metaFunding2 = { + accountId: accountIdFunding2, + createdAt: 1571068850409, + name: '', + updatedAt: 1571078850509 + }, + metaStaking = { + accountId: accountIdStaking, + createdAt: 1571068850409, + name: '', + updatedAt: 1571078850509 + }, + metaPolkadot = { + accountId: accountIdPolkadot, + createdAt: 1573142786972, + name: 'PolkadotFirst', + updatedAt: 1573142786972 + }, + metaEthereum = { + accountId: accountIdEthereum, + createdAt: 1573142786972, + name: 'Eth account', + updatedAt: 1573142786972 + }, + metaSoftKey = { + accountId: accountIdSoft, + createdAt: 1573142786972, + name: '', + updatedAt: 1573142786972 + }; +const accountIdsMap = new Map([ + [accountIdDefault, paths[0]], + [accountIdFunding1, paths[1]], + [accountIdSoft, paths[2]], + [accountIdFunding2, paths[3]], + [accountIdStaking, paths[4]], + [accountIdPolkadot, paths[5]], + [accountIdEthereum, paths[6]] +]); +const metaMap = new Map([ + [paths[0], metaDefault], + [paths[1], metaFunding1], + [paths[2], metaSoftKey], + [paths[3], metaStaking], + [paths[4], metaFunding2], + [paths[5], metaPolkadot], + [paths[6], metaEthereum] +]); +const testIdentities = [ + { + accountIds: accountIdsMap, + derivationPassword: '', + encryptedSeedPhrase: 'yyyy', + meta: metaMap, + name: 'identity1' + }, + { + accountIds: accountIdsMap, + derivationPassword: '', + encryptedSeedPhrase: 'xxxx', + meta: metaMap, + name: 'identity2' + } +]; + +describe('IdentitiesUtils', () => { + it('works with serialize and deserialize', () => { + const serializedJson = serializeIdentities(testIdentities); + const originItem = deserializeIdentities(serializedJson); + expect(originItem).toEqual(testIdentities); + }); + + it('regroup the paths', () => { + const kusamaPaths = paths.slice(0, 5); + const groupResult = groupPaths(kusamaPaths); + expect(groupResult).toEqual([ + { + paths: [paths[0]], + title: '//default' + }, + { + paths: [paths[2]], + title: '/softKey1' + }, + { + paths: [paths[4]], + title: '//staking' + }, + { + paths: [paths[1], paths[3]], + title: '//funding' + } + ]); + }); + + it('get the path name', () => { + const expectNames = [ + 'default', + 'funding account1', + 'softKey1', + 'funding2', + 'staking1', + 'PolkadotFirst', + 'Eth account' + ]; + paths.forEach((path, index) => { + const name = getPathName(path, testIdentities[0]); + expect(name).toEqual(expectNames[index]); + }); + }); + + it('get the correspond networkKeys', () => { + const networkKeys = getAvailableNetworkKeys(testIdentities[0]); + expect(networkKeys).toEqual([ + EthereumNetworkKeys.FRONTIER, + SubstrateNetworkKeys.KUSAMA, + UnknownNetworkKeys.UNKNOWN + ]); + }); +}); diff --git a/src/util/suri.spec.js b/specs/util/suri.spec.js similarity index 97% rename from src/util/suri.spec.js rename to specs/util/suri.spec.js index 2e7f416176..1f1502b5bf 100644 --- a/src/util/suri.spec.js +++ b/specs/util/suri.spec.js @@ -16,7 +16,11 @@ 'use strict'; -import { constructSURI, parseDerivationPath, parseSURI } from './suri'; +import { + constructSURI, + parseDerivationPath, + parseSURI +} from '../../src/util/suri'; describe('derivation path', () => { describe('parsing', () => { diff --git a/src/util/units.spec.js b/specs/util/units.spec.js similarity index 97% rename from src/util/units.spec.js rename to specs/util/units.spec.js index ae543ffca1..04c2cf1b50 100644 --- a/src/util/units.spec.js +++ b/specs/util/units.spec.js @@ -22,8 +22,8 @@ import { GenericCall, Metadata } from '@polkadot/types'; import Call from '@polkadot/types/primitive/Generic/Call'; import { formatBalance } from '@polkadot/util'; -import kusamaData from './static-kusama'; -import { fromWei } from './units'; +import kusamaData from '../../src/util/static-kusama'; +import { fromWei } from '../../src/util/units'; describe('units', () => { describe('ethereum', () => { diff --git a/src/App.js b/src/App.js index cecde3cfa2..cc9390e02e 100644 --- a/src/App.js +++ b/src/App.js @@ -20,11 +20,12 @@ import '../shim'; import '@polkadot/types/injector'; -import React, { Component } from 'react'; -import { Platform, StatusBar, YellowBox } from 'react-native'; +import React, { Component, PureComponent } from 'react'; +import { Platform, StatusBar, View, YellowBox } from 'react-native'; import { createAppContainer, createStackNavigator, + createSwitchNavigator, HeaderBackButton, withNavigation } from 'react-navigation'; @@ -34,22 +35,29 @@ import { MenuProvider } from 'react-native-popup-menu'; import '../shim'; import Background from './components/Background'; import colors from './colors'; -import fonts from './fonts'; import HeaderLeftHome from './components/HeaderLeftHome'; import SecurityHeader from './components/SecurityHeader'; import '../ReactotronConfig'; import About from './screens/About'; -import AccountBackup from './screens/AccountBackup'; +import LegacyAccountBackup from './screens/LegacyAccountBackup'; import AccountDetails from './screens/AccountDetails'; import AccountEdit from './screens/AccountEdit'; -import AccountList from './screens/AccountList'; import AccountNetworkChooser from './screens/AccountNetworkChooser'; import AccountNew from './screens/AccountNew'; import AccountPin from './screens/AccountPin'; import AccountRecover from './screens/AccountRecover'; import { AccountUnlock, AccountUnlockAndSign } from './screens/AccountUnlock'; +import LegacyAccountList from './screens/LegacyAccountList'; import Loading from './screens/Loading'; +import IdentityBackup from './screens/IdentityBackup'; +import IdentityManagement from './screens/IdentityManagement'; +import IdentityNew from './screens/IdentityNew'; +import IdentityPin from './screens/IdentityPin'; import MessageDetails from './screens/MessageDetails'; +import PathDerivation from './screens/PathDerivation'; +import PathDetails from './screens/PathDetails'; +import PathsList from './screens/PathsList'; +import PathManagement from './screens/PathManagement'; import PrivacyPolicy from './screens/PrivacyPolicy'; import QrScanner from './screens/QrScanner'; import Security from './screens/Security'; @@ -57,15 +65,17 @@ import SignedMessage from './screens/SignedMessage'; import SignedTx from './screens/SignedTx'; import TermsAndConditions from './screens/TermsAndConditions'; import TxDetails from './screens/TxDetails'; +import LegacyNetworkChooser from './screens/LegacyNetworkChooser'; +import testIDs from '../e2e/testIDs'; const getLaunchArgs = props => { if (Platform.OS === 'ios') { if (props.launchArgs && props.launchArgs.includes('-detoxServer')) { - global.inTest = true; + return (global.inTest = true); } } else { if (props.launchArgs && props.launchArgs.hasOwnProperty('detoxServer')) { - global.inTest = true; + return (global.inTest = true); } } global.inTest = false; @@ -88,7 +98,7 @@ export default class App extends Component { return ( - + @@ -97,130 +107,145 @@ export default class App extends Component { } } -const globalStackNavigationOptions = { - headerBackTitleStyle: { - fontFamily: fonts.semiBold, - fontSize: 20 - }, - headerRight: , - headerStyle: { - backgroundColor: colors.bg, - borderBottomColor: colors.bg_text_sec, - borderBottomWidth: 0.5, - height: 60, - paddingBottom: 0, - paddingTop: 0 - }, - headerTintColor: colors.card_bg, - headerTitleStyle: { - display: 'none' - } +const globalStackNavigationOptions = ({ navigation }) => { + const isFirstScreen = navigation.dangerouslyGetParent().state.index === 0; + return { + headerBackTitleStyle: { + color: colors.bg_text_sec + }, + headerLeft: isFirstScreen ? : , + headerRight: , + headerStyle: { + backgroundColor: colors.bg, + borderBottomColor: colors.bg, + borderBottomWidth: 0, + elevation: 0, + height: 60, + paddingBottom: 0, + paddingTop: 0 + }, + headerTintColor: colors.bg_text_sec, + headerTitleStyle: { + display: 'none' + } + }; }; -// A workaround for https://github.com/react-navigation/react-navigation/issues/88 -const SecurityHeaderBackButton = withNavigation( - class _HeaderBackButton extends Component { +const HeaderLeftWithBack = withNavigation( + class _HeaderBackButton extends PureComponent { render() { const { navigation } = this.props; return ( - navigation.goBack(null)} - /> + + navigation.goBack()} + /> + ); } } ); /* eslint-disable sort-keys */ -const Screens = createStackNavigator( +const tocAndPrivacyPolicyScreens = { + TermsAndConditions: { + navigationOptions: { + headerRight: null + }, + screen: TermsAndConditions + }, + PrivacyPolicy: { + navigationOptions: { + headerRight: null + }, + screen: PrivacyPolicy + } +}; + +const Screens = createSwitchNavigator( { Loading: { screen: Loading }, - Security: { - screen: createStackNavigator( - { - Security: { - navigationOptions: { - headerLeft: , - headerRight: null - }, - screen: Security - } - }, - { - defaultNavigationOptions: globalStackNavigationOptions, - headerMode: 'screen' - } - ) - }, - TocAndPrivacyPolicy: { - screen: createStackNavigator( - { - TermsAndConditions: { - navigationOptions: { - headerLeft: - }, - screen: TermsAndConditions - }, - PrivacyPolicy: { - screen: PrivacyPolicy - } - }, - { - defaultNavigationOptions: globalStackNavigationOptions, - initialRouteParams: { - isWelcome: true - } - } - ) - }, + TocAndPrivacyPolicy: createStackNavigator(tocAndPrivacyPolicyScreens, { + defaultNavigationOptions: globalStackNavigationOptions + }), Welcome: { screen: createStackNavigator( { - AccountList: { - navigationOptions: { - headerLeft: - }, - screen: AccountList + AccountNetworkChooser: { + screen: AccountNetworkChooser + }, + AccountPin: { + screen: AccountPin + }, + AccountUnlock: { + screen: AccountUnlock }, About: { screen: About }, - AccountBackup: { - screen: AccountBackup - }, AccountDetails: { screen: AccountDetails }, AccountEdit: { screen: AccountEdit }, - AccountNetworkChooser: { - screen: AccountNetworkChooser - }, AccountNew: { screen: AccountNew }, - AccountPin: { - screen: AccountPin - }, AccountRecover: { screen: AccountRecover }, - AccountUnlock: { - screen: AccountUnlock - }, AccountUnlockAndSign: { screen: AccountUnlockAndSign }, + LegacyAccountBackup: { + screen: LegacyAccountBackup + }, + LegacyAccountList: { + screen: LegacyAccountList + }, + LegacyNetworkChooser: { + screen: LegacyNetworkChooser + }, + IdentityBackup: { + screen: IdentityBackup + }, + IdentityManagement: { + screen: IdentityManagement + }, + IdentityNew: { + screen: IdentityNew + }, + IdentityPin: { + screen: IdentityPin + }, MessageDetails: { screen: MessageDetails }, + PathDerivation: { + screen: PathDerivation + }, + PathDetails: { + screen: PathDetails + }, + PathsList: { + screen: PathsList + }, + PathManagement: { + screen: PathManagement + }, QrScanner: { screen: QrScanner }, @@ -232,13 +257,17 @@ const Screens = createStackNavigator( }, TxDetails: { screen: TxDetails - } + }, + Security: { + navigationOptions: { + headerRight: null + }, + screen: Security + }, + ...tocAndPrivacyPolicyScreens }, { - defaultNavigationOptions: globalStackNavigationOptions, - initialRouteParams: { - isWelcome: true - } + defaultNavigationOptions: globalStackNavigationOptions } ) } diff --git a/src/colors.js b/src/colors.js index 912cef597a..379d6621ae 100644 --- a/src/colors.js +++ b/src/colors.js @@ -14,14 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +'use strict'; + export default { - bg: '#1D1D1D', - bg_alert: '#ED332B', - bg_button: '#48acf0', - bg_text: '#FFFFFF', - bg_text_sec: '#B4B5B0', + bg: '#1B1B1B', + bg_alert: '#EB5757', + bg_button: '#48A5C2', + bg_text: '#EEE', + bg_text_sec: '#AAA', bg_warning: '#FAE265', - card_bg: '#F9F9F9', - card_bg_text_sec: '#B4B5B0', - card_text: '#1A1A1A' + card_bg: 'rgba(255, 255, 255, 0.15)', + card_bgSolid: '#323232', + card_bg_text_sec: '#999', + card_text: '#1A1A1A', + label_text: '#48A5C2', + label_text_sec: '#A8CDD9' }; diff --git a/src/components/AccountCard.js b/src/components/AccountCard.js index fe15e75737..b9c18d541c 100644 --- a/src/components/AccountCard.js +++ b/src/components/AccountCard.js @@ -16,89 +16,116 @@ // @flow +'use strict'; + import PropTypes from 'prop-types'; import React from 'react'; import { StyleSheet, Text, View, ViewPropTypes } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import Separator from '../components/Separator'; import AccountIcon from './AccountIcon'; import Address from './Address'; -import colors from '../colors'; import { NETWORK_LIST, NetworkProtocols } from '../constants'; -import fonts from '../fonts'; +import fontStyles from '../fontStyles'; import TouchableItem from './TouchableItem'; +import colors from '../colors'; +import { getAddressFromAccountId } from '../util/identitiesUtils'; +import { AccountPrefixedTitle } from './AccountPrefixedTitle'; export default class AccountCard extends React.PureComponent { static propTypes = { - address: PropTypes.string.isRequired, + accountId: PropTypes.string, + isAdd: PropTypes.bool, networkKey: PropTypes.string, onPress: PropTypes.func, seedType: PropTypes.string, style: ViewPropTypes.style, - title: PropTypes.string + testID: PropTypes.string, + title: PropTypes.string, + titlePrefix: PropTypes.string }; static defaultProps = { onPress: () => {}, - title: 'no name' + title: 'No name' }; render() { - const { address, networkKey, onPress, seedType, style } = this.props; + const { + accountId, + isAdd, + isNetworkCard, + networkKey, + networkColor, + onPress, + seedType, + style, + testID, + titlePrefix + } = this.props; let { title } = this.props; title = title.length ? title : AccountCard.defaultProps.title; const seedTypeDisplay = seedType || ''; const network = NETWORK_LIST[networkKey] || NETWORK_LIST[NetworkProtocols.UNKNOWN]; + const extractAddress = getAddressFromAccountId(accountId); + return ( - - + + + {isAdd ? ( + + + + ) : ( - - - {title} - -
- + )} + + {!isNetworkCard && ( + + + {`${network.title}${seedTypeDisplay} `} + + + )} + + {accountId !== '' && ( +
+ )} - - {seedTypeDisplay} - - - {network.title} - - + /> ); @@ -106,45 +133,25 @@ export default class AccountCard extends React.PureComponent { } const styles = StyleSheet.create({ - body: { - paddingBottom: 20 - }, content: { - backgroundColor: colors.card_bg, + alignItems: 'center', flexDirection: 'row', - padding: 10 + paddingLeft: 16 }, desc: { flex: 1, flexDirection: 'column', justifyContent: 'space-between', - paddingLeft: 10 + paddingLeft: 16 }, footer: { - backgroundColor: '#977CF6', - flexDirection: 'row', - justifyContent: 'space-between', - padding: 5 - }, - footerNetwork: { - color: colors.card_bg, - fontFamily: fonts.semiBold - }, - footerSeedType: { - color: colors.card_bg, - fontFamily: fonts.regular + alignSelf: 'stretch', + height: 88, + marginLeft: 8, + width: 8 }, icon: { - height: 47, - width: 47 - }, - secondaryText: { - color: colors.bg_text_sec, - fontFamily: fonts.semiBold, - fontSize: 14 - }, - titleText: { - fontFamily: fonts.semiBold, - fontSize: 20 + height: 40, + width: 40 } }); diff --git a/src/components/AccountIcon.js b/src/components/AccountIcon.js index bb3cc55887..7e8dc8fd83 100644 --- a/src/components/AccountIcon.js +++ b/src/components/AccountIcon.js @@ -19,7 +19,7 @@ import Identicon from '@polkadot/reactnative-identicon'; import PropTypes from 'prop-types'; import React, { useEffect, useState } from 'react'; -import { Image } from 'react-native'; +import { Image, View } from 'react-native'; import Icon from 'react-native-vector-icons/MaterialIcons'; import colors from '../colors'; @@ -29,37 +29,53 @@ import { blockiesIcon } from '../util/native'; export default function AccountIcon(props) { AccountIcon.propTypes = { address: PropTypes.string.isRequired, + network: PropTypes.object, protocol: PropTypes.string.isRequired }; - const { address, protocol, style } = props; + const { address, protocol, style, network } = props; const [ethereumIconUri, setEthereumIconUri] = useState(''); useEffect(() => { + const loadEthereumIcon = function(ethereumAddress) { + blockiesIcon('0x' + ethereumAddress) + .then(uri => { + setEthereumIconUri(uri); + }) + .catch(console.error); + }; + if (protocol === NetworkProtocols.ETHEREUM) { loadEthereumIcon(address); } - }, [protocol, address]); - - const loadEthereumIcon = function(ethereumAddress) { - blockiesIcon('0x' + ethereumAddress) - .then(uri => { - setEthereumIconUri(uri); - }) - .catch(console.error); - }; + }, [address, protocol]); + if (address === '') { + return ( + + + + ); + } if (protocol === NetworkProtocols.SUBSTRATE) { - return ; + return ; } else if (protocol === NetworkProtocols.ETHEREUM && ethereumIconUri) { return ( ); } else { // if there's no protocol or it's unknown we return a warning - return ; + return ; } } diff --git a/src/components/AccountIconChooser.js b/src/components/AccountIconChooser.js index 4695ce0aa5..88858232e7 100644 --- a/src/components/AccountIconChooser.js +++ b/src/components/AccountIconChooser.js @@ -16,6 +16,8 @@ // @flow +'use strict'; + import React from 'react'; import { FlatList, diff --git a/src/components/AccountPrefixedTitle.js b/src/components/AccountPrefixedTitle.js new file mode 100644 index 0000000000..96cac91d73 --- /dev/null +++ b/src/components/AccountPrefixedTitle.js @@ -0,0 +1,50 @@ +// Copyright 2015-2019 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 . + +'use strict'; + +import React from 'react'; +import { Text, View } from 'react-native'; +import PropTypes from 'prop-types'; + +import fontStyles from '../fontStyles'; +import colors from '../colors'; + +AccountPrefixedTitle.propTypes = { + title: PropTypes.string.isRequired, + titlePrefix: PropTypes.string +}; + +export function AccountPrefixedTitle({ titlePrefix, title }) { + return ( + + {titlePrefix && ( + + {titlePrefix} + + )} + + {title} + + + ); +} diff --git a/src/components/AccountSeed.js b/src/components/AccountSeed.js index c5f68a102f..e12991db1c 100644 --- a/src/components/AccountSeed.js +++ b/src/components/AccountSeed.js @@ -20,6 +20,7 @@ import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import colors from '../colors'; import fonts from '../fonts'; +import fontStyles from '../fontStyles'; import PARITY_WORDS from '../../res/parity_wordlist.json'; import BIP39_WORDS from '../../res/bip39_wordlist.json'; import TextInput from './TextInput'; @@ -132,7 +133,8 @@ export default class AccountSeed extends Component { return ( {prefix} - {result} + {address} ); } diff --git a/src/components/Background.js b/src/components/Background.js index 24659109a4..98bda8ce63 100644 --- a/src/components/Background.js +++ b/src/components/Background.js @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +'use strict'; + import React from 'react'; import { StyleSheet, View } from 'react-native'; import colors from '../colors'; diff --git a/src/components/Button.js b/src/components/Button.js index 0bdf1ded47..e4cd43bbd3 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -16,6 +16,8 @@ // @flow +'use strict'; + import PropTypes from 'prop-types'; import React from 'react'; import { @@ -28,7 +30,7 @@ import { ViewPropTypes } from 'react-native'; import colors from '../colors'; -import fonts from '../fonts'; +import fontStyles from '../fontStyles'; export default class Button extends React.PureComponent<{ title: string, @@ -40,6 +42,8 @@ export default class Button extends React.PureComponent<{ static propTypes = { disabled: PropTypes.bool, onPress: PropTypes.func.isRequired, + onlyText: PropTypes.bool, + small: PropTypes.bool, style: ViewPropTypes.style, textStyles: Text.propTypes.style, title: PropTypes.string.isRequired @@ -50,14 +54,25 @@ export default class Button extends React.PureComponent<{ onPress, title, disabled, + small, textStyles, + onlyText, buttonStyles, - testID + testID, + style } = this.props; - const finalTextStyles = [styles.text, textStyles]; + const finalTextStyles = [textStyles]; const finalButtonStyles = [styles.button, buttonStyles]; + if (small) { + finalTextStyles.push(fontStyles.t_important); + finalButtonStyles.push(styles.buttonSmall); + } + if (onlyText) { + finalTextStyles.push({ color: colors.bg_button }); + finalButtonStyles.push(styles.buttonOnlyText); + } if (disabled) { finalTextStyles.push(styles.textDisabled); finalButtonStyles.push(styles.buttonDisabled); @@ -72,8 +87,8 @@ export default class Button extends React.PureComponent<{ onPress={onPress} testID={testID} > - - + + {title} @@ -86,21 +101,28 @@ const styles = StyleSheet.create({ button: { alignItems: 'center', backgroundColor: colors.bg_button, + borderRadius: 60, elevation: 4, - height: 60, - justifyContent: 'center' + height: 56, + justifyContent: 'center', + marginHorizontal: 8, + marginVertical: 8, + paddingHorizontal: 64 }, buttonDisabled: { - backgroundColor: '#dfdfdf', + backgroundColor: colors.card_bgSolid, elevation: 0 }, - text: { - color: 'white', - fontFamily: fonts.bold, - fontSize: 20, - padding: 8 + buttonOnlyText: { + backgroundColor: colors.bg, + elevation: 0, + paddingHorizontal: 0 + }, + buttonSmall: { + height: 48, + paddingHorizontal: 48 }, textDisabled: { - color: '#a1a1a1' + color: colors.card_bg } }); diff --git a/src/components/ButtonIcon.js b/src/components/ButtonIcon.js new file mode 100644 index 0000000000..b001eed1c7 --- /dev/null +++ b/src/components/ButtonIcon.js @@ -0,0 +1,145 @@ +// Copyright 2015-2019 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 . + +// @flow + +'use strict'; + +import React, { useState } from 'react'; +import { + Platform, + TouchableNativeFeedback, + TouchableOpacity, + View, + Text +} from 'react-native'; +import { Icon } from 'react-native-elements'; +import AntIcon from 'react-native-vector-icons/AntDesign'; +import colors from '../colors'; + +const ButtonIcon = props => { + const { + dropdown = false, + renderDropdownElement, + iconName, + iconType, + iconColor, + onPress, + iconBgStyle, + iconSize, + testID, + textStyle, + title, + style + } = props; + + const size = iconSize || 28; + + const styles = { + dropdownView: { + marginRight: 8, + marginTop: size / -12, + marginVertical: 8 + }, + generalView: { + display: 'flex', + flexDirection: 'row', + paddingVertical: 8 + }, + iconTitleView: { + alignItems: 'center', + display: 'flex', + flexDirection: 'row', + marginLeft: 8 + }, + iconTitleViewContainer: { + flex: dropdown && title ? 1 : 0 + }, + iconView: { + backgroundColor: colors.card_bg, + borderRadius: size, + height: size, + paddingLeft: 3, + paddingTop: size / 8, + width: size + }, + title: { + marginLeft: 8 + } + }; + + const Touchable = + Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity; + + const renderIcon = () => { + if (iconType === 'antdesign') { + return ( + + ); + } + return ( + + ); + }; + const [isDropdownOpen, setIsDropsdownOpen] = useState(false); + + return ( + <> + + + + {renderIcon()} + {!!title && {title}} + + + + + {dropdown && ( + + setIsDropsdownOpen(!isDropdownOpen)}> + + + + + + )} + + {isDropdownOpen && renderDropdownElement()} + + ); +}; + +export default ButtonIcon; diff --git a/src/components/ButtonMainAction.js b/src/components/ButtonMainAction.js new file mode 100644 index 0000000000..883362e31c --- /dev/null +++ b/src/components/ButtonMainAction.js @@ -0,0 +1,74 @@ +// Copyright 2015-2019 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 . + +// @flow + +'use strict'; + +import PropTypes from 'prop-types'; +import React from 'react'; +import Button from './Button'; +import { View } from 'react-native'; + +export default class ButtonMainAction extends React.PureComponent { + static propTypes = { + bottom: PropTypes.bool, + disabled: PropTypes.bool, + onPress: PropTypes.func, + testID: PropTypes.string, + title: PropTypes.string + }; + render() { + const { onPress, title, testID, bottom, disabled, style } = this.props; + const finalViewStyles = [styles.body]; + const finalButtonStyles = [styles.button]; + + if (bottom === false) { + finalViewStyles.push(styles.p_relative); + finalButtonStyles.push(styles.p_relative); + } + + return ( + +