From 89564f0d45982e656b445d1f0439e4be956cd596 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 31 Aug 2018 18:03:19 -0700 Subject: [PATCH 01/11] Remove unused styles from settings unit/fiat. --- src/view/setting-fiat.js | 2 +- src/view/setting-unit.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/view/setting-fiat.js b/src/view/setting-fiat.js index 73a1f1ec0..17bfd19b9 100644 --- a/src/view/setting-fiat.js +++ b/src/view/setting-fiat.js @@ -28,7 +28,7 @@ const styles = StyleSheet.create({ const SettingFiatView = ({ store, nav, setting }) => { return ( - +
nav.goSettings()} /> diff --git a/src/view/setting-unit.js b/src/view/setting-unit.js index 728ac4364..a62ef4b2b 100644 --- a/src/view/setting-unit.js +++ b/src/view/setting-unit.js @@ -28,7 +28,7 @@ const styles = StyleSheet.create({ const SettingUnitView = ({ store, nav, setting }) => { return ( - <Background color={color.blackDark} style={styles.wrapper}> + <Background color={color.blackDark}> <Header separator> <BackButton onPress={() => nav.goSettings()} /> <Title title="Bitcoin Units" /> From ad921270a9c02a328d410e3564aa7d0ddd2129d6 Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:38:33 -0700 Subject: [PATCH 02/11] Add recovery window const to config and initWallet. --- src/action/wallet.js | 18 +++++++++++++++--- src/config.js | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/action/wallet.js b/src/action/wallet.js index bbf25b767..c14bafd1a 100644 --- a/src/action/wallet.js +++ b/src/action/wallet.js @@ -5,7 +5,12 @@ import { observe, when } from 'mobx'; import { toBuffer, parseSat, checkHttpStatus, nap, poll } from '../helper'; -import { MIN_PASSWORD_LENGTH, NOTIFICATION_DELAY, RATE_DELAY } from '../config'; +import { + MIN_PASSWORD_LENGTH, + NOTIFICATION_DELAY, + RATE_DELAY, + RECOVERY_WINDOW, +} from '../config'; import * as log from './log'; class WalletAction { @@ -181,21 +186,28 @@ class WalletAction { * screen. * @param {string} options.walletPassword The user chosen password * @param {Array} options.seedMnemonic The seed words to generate the wallet + * @param {number} options.recoveryWindow The number of addresses to recover * @return {Promise<undefined>} */ - async initWallet({ walletPassword, seedMnemonic }) { + async initWallet({ walletPassword, seedMnemonic, recoveryWindow = 0 }) { try { await this._grpc.sendUnlockerCommand('InitWallet', { wallet_password: toBuffer(walletPassword), cipher_seed_mnemonic: seedMnemonic, + recovery_window: recoveryWindow, }); this._store.walletUnlocked = true; this._nav.goSeedSuccess(); } catch (err) { - this._notification.display({ msg: 'Initializing wallet failed', err }); + this._notification.display({ + type: 'error', + msg: `Initializing wallet failed: ${err.details}`, + }); } } + } + /** * Check the password input by the user by attempting to unlock the wallet. * @return {Promise<undefined>} diff --git a/src/config.js b/src/config.js index f80868f5c..e028c925e 100644 --- a/src/config.js +++ b/src/config.js @@ -19,6 +19,7 @@ module.exports.PREFIX_URI = `${prefixName}:`; module.exports.DEFAULT_ROUTE = 'Welcome'; module.exports.MIN_PASSWORD_LENGTH = 8; module.exports.MAX_LOG_LENGTH = 10000; +module.exports.RECOVERY_WINDOW = 250; module.exports.UNITS = { sat: { display: 'SAT', displayLong: 'Satoshi', denominator: 1 }, From f01ded52f9e2823b3613f8327c6ebb81dbe36bf7 Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:40:52 -0700 Subject: [PATCH 03/11] Add `restoring` and `restoreSeed` fields to store + setters. --- src/action/wallet.js | 18 ++++++++++++++++++ src/store.js | 2 ++ test/unit/action/wallet.spec.js | 15 +++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/action/wallet.js b/src/action/wallet.js index c14bafd1a..6384f013b 100644 --- a/src/action/wallet.js +++ b/src/action/wallet.js @@ -46,6 +46,16 @@ class WalletAction { this._store.wallet.seedVerify[index] = word; } + /** + * Set the restore seed input by the seed word and + * seed index. + * @param {string} options.word The seed word + * @param {number} options.index The seed index + */ + setRestoreSeed({ word, index }) { + this._store.wallet.restoreSeed[index] = word; + } + // // Wallet Password actions // @@ -87,6 +97,14 @@ class WalletAction { this._store.wallet.passwordVerify = password; } + /** + * Set whether or not we're restoring the wallet. + * @param {boolean} options.restoring Whether or not we're restoring. + */ + setRestoringWallet({ restoring }) { + this._store.wallet.restoring = restoring; + } + // // Wallet actions // diff --git a/src/store.js b/src/store.js index ad1f4303c..5c8d978eb 100644 --- a/src/store.js +++ b/src/store.js @@ -40,6 +40,8 @@ export class Store { password: '', passwordVerify: '', seedVerify: ['', '', ''], + restoring: false, + restoreSeed: Array(24).fill(''), }, transactions: [], selectedTransaction: null, diff --git a/test/unit/action/wallet.spec.js b/test/unit/action/wallet.spec.js index 673606e56..b76f465c4 100644 --- a/test/unit/action/wallet.spec.js +++ b/test/unit/action/wallet.spec.js @@ -87,6 +87,13 @@ describe('Action Wallet Unit Tests', () => { }); }); + describe('setRestoringWallet()', () => { + it('should clear attributes', () => { + wallet.setRestoringWallet({ restoring: true }); + expect(store.wallet.restoring, 'to equal', true); + }); + }); + describe('init()', () => { it('should generate seed and navigate to onboarding', async () => { grpc.sendUnlockerCommand.withArgs('GenSeed').resolves({ @@ -228,6 +235,14 @@ describe('Action Wallet Unit Tests', () => { }); }); + + describe('setRestoreSeed()', () => { + it('should clear attributes', () => { + wallet.setRestoreSeed({ word: 'foo', index: 1 }); + expect(store.wallet.restoreSeed[1], 'to equal', 'foo'); + }); + }); + describe('initInitialDeposit()', () => { it('should navigate to new address screen if address is non-null', () => { store.walletAddress = 'non-null-addr'; From 90f9d737218bef03aefd490ee7fe1dc663ab9675 Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:43:27 -0700 Subject: [PATCH 04/11] Add wallet.restoreWallet, giving lnd the seed and password to restore. --- src/action/wallet.js | 14 ++++++++++++++ test/unit/action/wallet.spec.js | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/action/wallet.js b/src/action/wallet.js index 6384f013b..9c6d1b402 100644 --- a/src/action/wallet.js +++ b/src/action/wallet.js @@ -235,6 +235,20 @@ class WalletAction { await this.unlockWallet({ walletPassword: password }); } + /** + * Initialize the wallet with the password input the seed that was already + * inputted, and the default recovery window. + * @return {Promise<undefined>} + */ + async restoreWallet() { + const { password, restoreSeed } = this._store.wallet; + await this.initWallet({ + walletPassword: password, + seedMnemonic: restoreSeed.toJSON(), + recoveryWindow: RECOVERY_WINDOW, + }); + } + /** * Unlock the wallet by calling the grpc api with the user chosen password. * @param {string} options.walletPassword The password used to encrypt the wallet diff --git a/test/unit/action/wallet.spec.js b/test/unit/action/wallet.spec.js index b76f465c4..9cd25724b 100644 --- a/test/unit/action/wallet.spec.js +++ b/test/unit/action/wallet.spec.js @@ -7,6 +7,7 @@ import NotificationAction from '../../../src/action/notification'; import * as logger from '../../../src/action/log'; import nock from 'nock'; import 'isomorphic-fetch'; +import { RECOVERY_WINDOW } from '../../../src/config'; describe('Action Wallet Unit Tests', () => { let store; @@ -275,6 +276,24 @@ describe('Action Wallet Unit Tests', () => { }); }); + describe('restoreWallet()', () => { + beforeEach(() => { + sandbox.stub(wallet, 'initWallet'); + }); + + it('calls initWallet with password and restoreSeed', async () => { + wallet.setPassword({ password: 'secret123' }); + const seed = Array(24).fill('foo'); + store.wallet.restoreSeed = seed; + await wallet.restoreWallet(); + expect(wallet.initWallet, 'was called with', { + walletPassword: 'secret123', + seedMnemonic: seed, + recoveryWindow: RECOVERY_WINDOW, + }); + }); + }); + describe('unlockWallet()', () => { it('should unlock wallet', async () => { grpc.sendUnlockerCommand.withArgs('UnlockWallet').resolves(); From bc93d7e22366f3453aa7a1f5e0a7fec27c4ed2ba Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:44:10 -0700 Subject: [PATCH 05/11] Add computed fields for restore: seed indices, verify and copy. --- src/computed/seed.js | 13 +++++++++++++ test/unit/computed/seed.spec.js | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/computed/seed.js b/src/computed/seed.js index 085c1e04c..992062ddf 100644 --- a/src/computed/seed.js +++ b/src/computed/seed.js @@ -17,6 +17,19 @@ const ComputedSeed = store => { const c2 = formatOrdinal(seedVerifyIndexes[2]); return `Type the ${c0}, ${c1}, and ${c2} words of your recovery phrase.`; }), + restoreIndexes: computed(() => [...Array(24).keys()].map(x => ++x)), + restoreVerifyIndexes: computed(() => { + const { restoreIndexes } = store; + const { restoreIndex } = store.wallet; + return restoreIndexes.slice(restoreIndex, restoreIndex + 3); + }), + restoreVerifyCopy: computed(() => { + const { restoreVerifyIndexes } = store; + const c0 = formatOrdinal(restoreVerifyIndexes[0]); + const c1 = formatOrdinal(restoreVerifyIndexes[1]); + const c2 = formatOrdinal(restoreVerifyIndexes[2]); + return `Type the ${c0}, ${c1}, and ${c2} words of your recovery phrase.`; + }), }); }; diff --git a/test/unit/computed/seed.spec.js b/test/unit/computed/seed.spec.js index 10fa4ee1e..70f249198 100644 --- a/test/unit/computed/seed.spec.js +++ b/test/unit/computed/seed.spec.js @@ -54,6 +54,25 @@ describe('Computed Seed Unit Tests', () => { } expect(store.seedVerifyCopy, 'to match', /^Type /); }); + + it('should set restore seed check attributes', () => { + store.wallet.restoreIndex = 3; + ComputedSeed(store); + expect(store.restoreIndexes.length, 'to equal', 24); + for (let i = 0; i < 24; i++) { + expect(store.restoreIndexes[i], 'to be greater than', 0); + expect(store.restoreIndexes[i], 'to be less than', 25); + if (i > 0) { + expect( + store.restoreIndexes[i], + 'to equal', + store.restoreIndexes[i - 1] + 1 + ); + } + } + expect(store.restoreVerifyIndexes, 'to equal', [4, 5, 6]); + expect(store.restoreVerifyCopy, 'to match', /^Type /); + }); }); describe('formatOrdinal()', () => { From c5ef05a31d1b9342b08456d4585a7ab97817fa68 Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:45:29 -0700 Subject: [PATCH 06/11] Add restoreIndex field to store and init functions for wallet restore. restoreIndex keeps track of how many seed words the user has entered so we know which ones are remaining for the user to enter. We want to reset these values on the start of the restore process, as well as ensure they are up to date as the user goes from one page of restore entries to the next. --- src/action/wallet.js | 35 ++++++++++++++++++++++++++++ src/store.js | 1 + test/unit/action/wallet.spec.js | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/src/action/wallet.js b/src/action/wallet.js index 9c6d1b402..63d9e777f 100644 --- a/src/action/wallet.js +++ b/src/action/wallet.js @@ -224,6 +224,41 @@ class WalletAction { } } + /** + * Initialize the restore wallet view by resetting input values and then + * navigating to the view. + * @return {undefined} + */ + initRestoreWallet() { + this._store.wallet.restoreIndex = 0; + this._nav.goRestoreSeed(); + } + + /** + * Initialize the next restore wallet view by setting a new restoreIndex or, + * if all seed words have been entered, navigating to the password entry + * view. + * @return {undefined} + */ + initNextRestorePage() { + if (this._store.wallet.restoreIndex < 21) { + this._store.wallet.restoreIndex += 3; + } else { + this._nav.goRestorePassword(); + } + } + + /** + * Initialize the previous restore wallet view by setting a new restoreIndex + * or, if on the first seed entry page, navigating to the select seed view. + * @return {undefined} + */ + initPrevRestorePage() { + if (this._store.wallet.restoreIndex >= 3) { + this._store.wallet.restoreIndex -= 3; + } else { + this._nav.goSelectSeed(); + } } /** diff --git a/src/store.js b/src/store.js index 5c8d978eb..33e0d4232 100644 --- a/src/store.js +++ b/src/store.js @@ -41,6 +41,7 @@ export class Store { passwordVerify: '', seedVerify: ['', '', ''], restoring: false, + restoreIndex: 0, restoreSeed: Array(24).fill(''), }, transactions: [], diff --git a/test/unit/action/wallet.spec.js b/test/unit/action/wallet.spec.js index 9cd25724b..f5ef26deb 100644 --- a/test/unit/action/wallet.spec.js +++ b/test/unit/action/wallet.spec.js @@ -236,6 +236,15 @@ describe('Action Wallet Unit Tests', () => { }); }); + describe('initRestoreWallet()', () => { + it('should clear attributes and navigate to view', () => { + store.wallet.restoreIndex = 42; + wallet.initRestoreWallet(); + expect(store.wallet.restoreSeed.length, 'to equal', 24); + expect(store.wallet.restoreIndex, 'to equal', 0); + expect(nav.goRestoreSeed, 'was called once'); + }); + }); describe('setRestoreSeed()', () => { it('should clear attributes', () => { @@ -244,6 +253,38 @@ describe('Action Wallet Unit Tests', () => { }); }); + describe('initPrevRestorePage()', () => { + it('should navigate to select seed if restoreIndex < 3', () => { + store.wallet.restoreIndex = 2; + wallet.initPrevRestorePage(); + expect(nav.goSelectSeed, 'was called once'); + expect(store.wallet.restoreIndex, 'to equal', 2); + }); + + it('should decrement restoreIndex if greater than 2', async () => { + store.wallet.restoreIndex = 3; + wallet.initPrevRestorePage(); + expect(nav.goSelectSeed, 'was not called'); + expect(store.wallet.restoreIndex, 'to equal', 0); + }); + }); + + describe('initNextRestorePage()', () => { + it('should navigate to password screen if restoreIndex > 20', () => { + store.wallet.restoreIndex = 21; + wallet.initNextRestorePage(); + expect(nav.goRestorePassword, 'was called once'); + expect(store.wallet.restoreIndex, 'to equal', 21); + }); + + it('should increment restoreIndex if less than 21', async () => { + store.wallet.restoreIndex = 18; + wallet.initNextRestorePage(); + expect(nav.goRestorePassword, 'was not called'); + expect(store.wallet.restoreIndex, 'to equal', 21); + }); + }); + describe('initInitialDeposit()', () => { it('should navigate to new address screen if address is non-null', () => { store.walletAddress = 'non-null-addr'; From 1fd414c97629a12961b3a6b3148f43d1f18003b4 Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:46:49 -0700 Subject: [PATCH 07/11] Add "SelectSeed" view. This view is shown on a fresh start of the app to allow the user to either restore an existing wallet or initialize a new one. --- src/action/nav.js | 4 ++ src/view/main.js | 4 ++ src/view/select-seed.js | 72 ++++++++++++++++++++++++++++++++++++ stories/screen-story.js | 4 ++ test/unit/action/nav.spec.js | 7 ++++ 5 files changed, 91 insertions(+) create mode 100644 src/view/select-seed.js diff --git a/src/action/nav.js b/src/action/nav.js index 3f9a53d2f..c659c6741 100644 --- a/src/action/nav.js +++ b/src/action/nav.js @@ -13,6 +13,10 @@ class NavAction { this._store.route = 'Loader'; } + goSelectSeed() { + this._store.route = 'SelectSeed'; + } + goSeed() { this._store.route = 'Seed'; } diff --git a/src/view/main.js b/src/view/main.js index 66920b1f7..e58352285 100644 --- a/src/view/main.js +++ b/src/view/main.js @@ -4,6 +4,7 @@ import Container from '../component/container'; import { NotificationBar } from '../component/notification'; import Welcome from './welcome'; import Loader from './loader'; +import SelectSeed from './select-seed'; import Seed from './seed'; import SeedVerify from './seed-verify'; import SeedSuccess from './seed-success'; @@ -56,6 +57,9 @@ class MainView extends Component { /> {route === 'Welcome' && <Welcome />} {route === 'Loader' && <Loader />} + {route === 'SelectSeed' && ( + <SelectSeed store={store} wallet={wallet} nav={nav} /> + )} {route === 'Seed' && <Seed store={store} wallet={wallet} />} {route === 'SeedVerify' && ( <SeedVerify store={store} nav={nav} wallet={wallet} /> diff --git a/src/view/select-seed.js b/src/view/select-seed.js new file mode 100644 index 000000000..0fa748268 --- /dev/null +++ b/src/view/select-seed.js @@ -0,0 +1,72 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { observer } from 'mobx-react'; +import PropTypes from 'prop-types'; +import Background from '../component/background'; +import MainContent from '../component/main-content'; +import { H1Text, CopyText } from '../component/text'; +import { RadioButton, GlasButton } from '../component/button'; +import { SettingItem } from '../component/list'; +import { color } from '../component/style'; + +// +// Select Seed View +// + +const styles = StyleSheet.create({ + content: { + justifyContent: 'center', + paddingLeft: 50, + paddingRight: 50, + }, + copyTxt: { + textAlign: 'center', + marginTop: 10, + maxWidth: 450, + }, + list: { + marginTop: 50, + width: 400, + }, +}); + +const SelectSeedView = ({ store, wallet, nav }) => ( + <Background color={color.blackDark}> + <MainContent style={styles.content}> + <H1Text>Recovery phrase?</H1Text> + <CopyText style={styles.copyTxt}> + If you already have a recovery phrase, you can use that now. Otherwise, + you should generate a new wallet. + </CopyText> + <View style={styles.list}> + <SettingItem + name="Generate a new wallet" + onSelect={() => wallet.setRestoringWallet({ restoring: false })} + > + <RadioButton selected={store.wallet.restoring === false} /> + </SettingItem> + <SettingItem + name="Recover an existing wallet" + onSelect={() => wallet.setRestoringWallet({ restoring: true })} + > + <RadioButton selected={store.wallet.restoring === true} /> + </SettingItem> + </View> + </MainContent> + <GlasButton + onPress={() => + store.wallet.restoring ? wallet.initRestoreWallet() : nav.goSeed() + } + > + Next + </GlasButton> + </Background> +); + +SelectSeedView.propTypes = { + store: PropTypes.object.isRequired, + wallet: PropTypes.object.isRequired, + nav: PropTypes.object.isRequired, +}; + +export default observer(SelectSeedView); diff --git a/stories/screen-story.js b/stories/screen-story.js index 6dfa0d1db..16299885d 100644 --- a/stories/screen-story.js +++ b/stories/screen-story.js @@ -41,6 +41,7 @@ import PayBitcoinDone from '../src/view/pay-bitcoin-done'; import NoRoute from '../src/view/no-route'; import Loader from '../src/view/loader'; import LoaderSyncing from '../src/view/loader-syncing'; +import SelectSeed from '../src/view/select-seed'; import SeedSuccess from '../src/view/seed-success'; import Seed from '../src/view/seed'; import SeedVerify from '../src/view/seed-verify'; @@ -81,6 +82,9 @@ sinon.stub(channel, 'closeSelectedChannel'); storiesOf('Screens', module) .add('Welcome', () => <Welcome />) .add('Loader - First Time', () => <Loader />) + .add('Select Seed', () => ( + <SelectSeed store={store} wallet={wallet} nav={nav} /> + )) .add('Seed', () => <Seed store={store} wallet={wallet} />) .add('Seed Verify', () => ( <SeedVerify store={store} nav={nav} wallet={wallet} /> diff --git a/test/unit/action/nav.spec.js b/test/unit/action/nav.spec.js index 5ff749de1..dc575af5f 100644 --- a/test/unit/action/nav.spec.js +++ b/test/unit/action/nav.spec.js @@ -25,6 +25,13 @@ describe('Action Nav Unit Tests', () => { }); }); + describe('goSelectSeed()', () => { + it('should set correct route', () => { + nav.goSelectSeed(); + expect(store.route, 'to equal', 'SelectSeed'); + }); + }); + describe('goSeed()', () => { it('should set correct route', () => { nav.goSeed(); From aca414dd7b74f9dca6baefdbfd4947468cd61e42 Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:47:15 -0700 Subject: [PATCH 08/11] Navigate to SelectSeed instead of Seed on fresh app start. --- src/action/wallet.js | 2 +- test/unit/action/wallet.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/action/wallet.js b/src/action/wallet.js index 63d9e777f..1a9266127 100644 --- a/src/action/wallet.js +++ b/src/action/wallet.js @@ -121,7 +121,7 @@ class WalletAction { this._store.firstStart = true; this._nav.goLoader(); await nap(NOTIFICATION_DELAY); - this._nav.goSeed(); + this._nav.goSelectSeed(); } catch (err) { this.initPassword(); } diff --git a/test/unit/action/wallet.spec.js b/test/unit/action/wallet.spec.js index f5ef26deb..d564d25d1 100644 --- a/test/unit/action/wallet.spec.js +++ b/test/unit/action/wallet.spec.js @@ -104,7 +104,7 @@ describe('Action Wallet Unit Tests', () => { expect(store.firstStart, 'to be', true); expect(store.seedMnemonic, 'to equal', 'foo bar'); expect(nav.goLoader, 'was called once'); - expect(nav.goSeed, 'was called once'); + expect(nav.goSelectSeed, 'was called once'); }); it('should navigate to password unlock if wallet already exists', async () => { From 2ae3fa163572904dd0f91ada06fbc4983778207b Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:48:49 -0700 Subject: [PATCH 09/11] Modify RestoreWallet (now RestoreSeed) to utilize new seedVerifyIndices and inits. Correctly initialize the next restore page before navigating to it, display the correct restore seed verify indices and enter the user input into wallet.restoreSeed. --- .../{restore-wallet.js => restore-seed.js} | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) rename src/view/{restore-wallet.js => restore-seed.js} (67%) diff --git a/src/view/restore-wallet.js b/src/view/restore-seed.js similarity index 67% rename from src/view/restore-wallet.js rename to src/view/restore-seed.js index 5bc261527..eeabdd568 100644 --- a/src/view/restore-wallet.js +++ b/src/view/restore-seed.js @@ -12,7 +12,7 @@ import { Header } from '../component/header'; import Card from '../component/card'; // -// Restore Wallet View +// Restore Wallet Seed View // const styles = StyleSheet.create({ @@ -32,10 +32,10 @@ const styles = StyleSheet.create({ }, }); -const RestoreWalletView = ({ store, nav, wallet }) => ( +const RestoreSeedView = ({ store, wallet }) => ( <Background image="purple-gradient-bg"> <Header> - <BackButton onPress={() => nav.goSeed()} /> + <BackButton onPress={() => wallet.initPrevRestorePage()} /> <Button disabled onPress={() => {}} /> </Header> <MainContent style={styles.content}> @@ -43,27 +43,28 @@ const RestoreWalletView = ({ store, nav, wallet }) => ( <H1Text style={styles.title}>Restore your wallet</H1Text> </View> <Card style={styles.card}> - <FormSubText>{store.seedVerifyCopy}</FormSubText> - {store.seedVerifyIndexes.map((seedIndex, i) => ( + <FormSubText>{store.restoreVerifyCopy}</FormSubText> + {store.restoreVerifyIndexes.map((seedIndex, i) => ( <SeedEntry seedIndex={seedIndex} - value={store.wallet.seedVerify[i]} - onChangeText={word => wallet.setSeedVerify({ word, index: i })} + value={store.wallet.restoreSeed[seedIndex - 1]} + onChangeText={word => + wallet.setRestoreSeed({ word, index: seedIndex - 1 }) + } key={i} autoFocus={i === 0} - onSubmitEditing={() => wallet.checkSeed()} + onSubmitEditing={() => wallet.initNextRestorePage()} /> ))} </Card> - <GlasButton onPress={() => wallet.checkSeed()}>Next</GlasButton> + <GlasButton onPress={() => wallet.initNextRestorePage()}>Next</GlasButton> </MainContent> </Background> ); -RestoreWalletView.propTypes = { +RestoreSeedView.propTypes = { store: PropTypes.object.isRequired, - nav: PropTypes.object.isRequired, wallet: PropTypes.object.isRequired, }; -export default observer(RestoreWalletView); +export default observer(RestoreSeedView); From 0c8b010338b0466e5927a54c56ba407416a7371d Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:50:33 -0700 Subject: [PATCH 10/11] Add RestorePassword view for password entry during restore. --- src/action/nav.js | 3 ++ src/view/main.js | 4 +++ src/view/restore-password.js | 70 ++++++++++++++++++++++++++++++++++++ stories/screen-story.js | 4 +++ test/unit/action/nav.spec.js | 7 ++++ 5 files changed, 88 insertions(+) create mode 100644 src/view/restore-password.js diff --git a/src/action/nav.js b/src/action/nav.js index c659c6741..bd3b4b378 100644 --- a/src/action/nav.js +++ b/src/action/nav.js @@ -27,6 +27,9 @@ class NavAction { goRestoreWallet() { // this._store.route = 'RestoreWallet'; + + goRestorePassword() { + this._store.route = 'RestorePassword'; } goSeedSuccess() { diff --git a/src/view/main.js b/src/view/main.js index e58352285..58a0496ee 100644 --- a/src/view/main.js +++ b/src/view/main.js @@ -9,6 +9,7 @@ import Seed from './seed'; import SeedVerify from './seed-verify'; import SeedSuccess from './seed-success'; import SetPassword from './set-password'; +import RestorePassword from './restore-password'; import Password from './password'; import NewAddress from './new-address'; import LoaderSyncing from './loader-syncing'; @@ -68,6 +69,9 @@ class MainView extends Component { {route === 'SetPassword' && ( <SetPassword store={store} wallet={wallet} /> )} + {route === 'RestorePassword' && ( + <RestorePassword store={store} wallet={wallet} nav={nav} /> + )} {route === 'Password' && <Password store={store} wallet={wallet} />} {route === 'NewAddress' && ( <NewAddress store={store} invoice={invoice} info={info} /> diff --git a/src/view/restore-password.js b/src/view/restore-password.js new file mode 100644 index 000000000..881fd85be --- /dev/null +++ b/src/view/restore-password.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { observer } from 'mobx-react'; +import PropTypes from 'prop-types'; +import Background from '../component/background'; +import MainContent from '../component/main-content'; +import { H1Text } from '../component/text'; +import { Header } from '../component/header'; +import { Button, BackButton, GlasButton } from '../component/button'; +import { InputField } from '../component/field'; +import Card from '../component/card'; +import { FormSubText, FormStretcher } from '../component/form'; + +// +// Restore Wallet Password View +// + +const styles = StyleSheet.create({ + content: { + justifyContent: 'flex-end', + }, + title: { + textAlign: 'center', + marginBottom: 20, + }, + card: { + maxHeight: 350, + maxWidth: 680, + paddingLeft: 45, + paddingRight: 45, + paddingBottom: 50, + }, +}); + +const RestorePasswordView = ({ store, wallet, nav }) => ( + <Background image="purple-gradient-bg"> + <Header> + <BackButton onPress={() => nav.goSelectSeed()} /> + <Button disabled onPress={() => {}} /> + </Header> + <MainContent style={styles.content}> + <View> + <H1Text style={styles.title}>Restore wallet</H1Text> + </View> + <Card style={styles.card}> + <FormSubText>Please enter your password.</FormSubText> + <FormStretcher> + <InputField + style={styles.input} + placeholder="Password" + secureTextEntry={true} + autoFocus={true} + value={store.wallet.password} + onChangeText={password => wallet.setPassword({ password })} + onSubmitEditing={() => wallet.restoreWallet()} + /> + </FormStretcher> + </Card> + <GlasButton onPress={() => wallet.restoreWallet()}>Restore</GlasButton> + </MainContent> + </Background> +); + +RestorePasswordView.propTypes = { + store: PropTypes.object.isRequired, + wallet: PropTypes.object.isRequired, + nav: PropTypes.object.isRequired, +}; + +export default observer(RestorePasswordView); diff --git a/stories/screen-story.js b/stories/screen-story.js index 16299885d..2fc90554f 100644 --- a/stories/screen-story.js +++ b/stories/screen-story.js @@ -47,6 +47,7 @@ import Seed from '../src/view/seed'; import SeedVerify from '../src/view/seed-verify'; import SetPassword from '../src/view/set-password'; import Password from '../src/view/password'; +import RestorePassword from '../src/view/restore-password'; import NewAddress from '../src/view/new-address'; import Wait from '../src/view/wait'; import RestoreWallet from '../src/view/restore-wallet'; @@ -95,6 +96,9 @@ storiesOf('Screens', module) .add('Seed Success', () => <SeedSuccess wallet={wallet} />) .add('Set Password', () => <SetPassword store={store} wallet={wallet} />) .add('Password', () => <Password store={store} wallet={wallet} />) + .add('Restore Wallet: Password', () => ( + <RestorePassword store={store} wallet={wallet} nav={nav} /> + )) .add('New Address', () => ( <NewAddress store={store} invoice={invoice} info={info} /> )) diff --git a/test/unit/action/nav.spec.js b/test/unit/action/nav.spec.js index dc575af5f..57871a5aa 100644 --- a/test/unit/action/nav.spec.js +++ b/test/unit/action/nav.spec.js @@ -74,6 +74,13 @@ describe('Action Nav Unit Tests', () => { }); }); + describe('goRestorePassword()', () => { + it('should set correct route', () => { + nav.goRestorePassword(); + expect(store.route, 'to equal', 'RestorePassword'); + }); + }); + describe('goNewAddress()', () => { it('should set correct route', () => { nav.goNewAddress(); From d7781544d3561f378ec401594d16db1726d63c49 Mon Sep 17 00:00:00 2001 From: Valentine Wallace <valentine.m.wallace@gmail.com> Date: Tue, 4 Sep 2018 18:51:56 -0700 Subject: [PATCH 11/11] Hook up RestoreSeed view. This view was previously named RestoreWallet. --- src/action/nav.js | 5 +++-- src/view/main.js | 4 ++++ stories/screen-story.js | 6 +++--- test/unit/action/nav.spec.js | 6 +++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/action/nav.js b/src/action/nav.js index bd3b4b378..d4560c79c 100644 --- a/src/action/nav.js +++ b/src/action/nav.js @@ -25,8 +25,9 @@ class NavAction { this._store.route = 'SeedVerify'; } - goRestoreWallet() { - // this._store.route = 'RestoreWallet'; + goRestoreSeed() { + this._store.route = 'RestoreSeed'; + } goRestorePassword() { this._store.route = 'RestorePassword'; diff --git a/src/view/main.js b/src/view/main.js index 58a0496ee..3f79c28e6 100644 --- a/src/view/main.js +++ b/src/view/main.js @@ -9,6 +9,7 @@ import Seed from './seed'; import SeedVerify from './seed-verify'; import SeedSuccess from './seed-success'; import SetPassword from './set-password'; +import RestoreSeed from './restore-seed'; import RestorePassword from './restore-password'; import Password from './password'; import NewAddress from './new-address'; @@ -69,6 +70,9 @@ class MainView extends Component { {route === 'SetPassword' && ( <SetPassword store={store} wallet={wallet} /> )} + {route === 'RestoreSeed' && ( + <RestoreSeed store={store} wallet={wallet} /> + )} {route === 'RestorePassword' && ( <RestorePassword store={store} wallet={wallet} nav={nav} /> )} diff --git a/stories/screen-story.js b/stories/screen-story.js index 2fc90554f..5da47ede7 100644 --- a/stories/screen-story.js +++ b/stories/screen-story.js @@ -50,7 +50,7 @@ import Password from '../src/view/password'; import RestorePassword from '../src/view/restore-password'; import NewAddress from '../src/view/new-address'; import Wait from '../src/view/wait'; -import RestoreWallet from '../src/view/restore-wallet'; +import RestoreSeed from '../src/view/restore-seed'; const store = new Store(); store.init(); @@ -90,8 +90,8 @@ storiesOf('Screens', module) .add('Seed Verify', () => ( <SeedVerify store={store} nav={nav} wallet={wallet} /> )) - .add('Restore Wallet', () => ( - <RestoreWallet store={store} nav={nav} wallet={wallet} /> + .add('Restore Wallet: Seed', () => ( + <RestoreSeed store={store} wallet={wallet} /> )) .add('Seed Success', () => <SeedSuccess wallet={wallet} />) .add('Set Password', () => <SetPassword store={store} wallet={wallet} />) diff --git a/test/unit/action/nav.spec.js b/test/unit/action/nav.spec.js index 57871a5aa..86e2b1c29 100644 --- a/test/unit/action/nav.spec.js +++ b/test/unit/action/nav.spec.js @@ -46,10 +46,10 @@ describe('Action Nav Unit Tests', () => { }); }); - describe.skip('goRestoreWallet()', () => { + describe('goRestoreSeed()', () => { it('should set correct route', () => { - nav.goRestoreWallet(); - expect(store.route, 'to equal', 'RestoreWallet'); + nav.goRestoreSeed(); + expect(store.route, 'to equal', 'RestoreSeed'); }); });