From 707521569e5718b132c816fce51b896cb098c9d4 Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Thu, 9 Nov 2017 00:08:27 -0300 Subject: [PATCH] Add, remove, search address book. Adds bitcoin address validator --- package.json | 2 +- src/app/app.module.ts | 4 ++ src/pages/addressbook/add/add.html | 35 ++++++++++ src/pages/addressbook/add/add.scss | 3 + src/pages/addressbook/add/add.ts | 75 ++++++++++++++++++++++ src/pages/addressbook/addressbook.html | 6 ++ src/pages/addressbook/addressbook.ts | 35 ++++++++++ src/pages/addressbook/view/view.html | 23 +++++++ src/pages/addressbook/view/view.scss | 3 + src/pages/addressbook/view/view.ts | 62 ++++++++++++++++++ src/providers/address-book/address-book.ts | 14 ++-- src/validators/address.ts | 62 ++++++++++++++++++ 12 files changed, 316 insertions(+), 8 deletions(-) create mode 100644 src/pages/addressbook/add/add.html create mode 100644 src/pages/addressbook/add/add.scss create mode 100644 src/pages/addressbook/add/add.ts create mode 100644 src/pages/addressbook/view/view.html create mode 100644 src/pages/addressbook/view/view.scss create mode 100644 src/pages/addressbook/view/view.ts create mode 100644 src/validators/address.ts diff --git a/package.json b/package.json index c35350792..9c33cab9d 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "codecov": "2.2.0", "fs-extra": "^4.0.2", "html-loader": "0.4.5", - "ionic": "3.16.0", + "ionic": "3.17.0", "jasmine-core": "2.6.4", "jasmine-spec-reporter": "4.1.1", "karma": "1.7.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6ba288894..798b90a19 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -39,6 +39,8 @@ import { TourPage } from '../pages/onboarding/tour/tour'; import { BackupWarningPage } from '../pages/backup/backup-warning/backup-warning'; import { BackupGamePage } from '../pages/backup/backup-game/backup-game'; import { AddressbookPage } from '../pages/addressbook/addressbook'; +import { AddressbookAddPage } from '../pages/addressbook/add/add'; +import { AddressbookViewPage } from '../pages/addressbook/view/view'; /* Tabs */ import { HomePage } from '../pages/home/home'; @@ -106,6 +108,8 @@ let pages: any = [ BackupWarningPage, BackupGamePage, AddressbookPage, + AddressbookAddPage, + AddressbookViewPage, AboutPage, AdvancedPage, AltCurrencyPage, diff --git a/src/pages/addressbook/add/add.html b/src/pages/addressbook/add/add.html new file mode 100644 index 000000000..f4f59e1ce --- /dev/null +++ b/src/pages/addressbook/add/add.html @@ -0,0 +1,35 @@ + + + + {{ 'Add Contact' | translate }} + + + + + + + +
+ + + + {{ 'Name' | translate }} + + + + + {{ 'Email' | translate }} + + + + + {{ 'Bitcoin Address' | translate }} + + + + + + +
+ +
diff --git a/src/pages/addressbook/add/add.scss b/src/pages/addressbook/add/add.scss new file mode 100644 index 000000000..a3bd00853 --- /dev/null +++ b/src/pages/addressbook/add/add.scss @@ -0,0 +1,3 @@ +page-addressbook-add { + +} diff --git a/src/pages/addressbook/add/add.ts b/src/pages/addressbook/add/add.ts new file mode 100644 index 000000000..6479f4634 --- /dev/null +++ b/src/pages/addressbook/add/add.ts @@ -0,0 +1,75 @@ +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors } from '@angular/forms'; +import { NavController, NavParams, AlertController } from 'ionic-angular'; +import { BwcProvider } from '../../../providers/bwc/bwc'; +import { AddressBookProvider } from '../../../providers/address-book/address-book'; +import { AddressValidator } from '../../../validators/address'; + +@Component({ + selector: 'page-addressbook-add', + templateUrl: 'add.html', +}) +export class AddressbookAddPage { + + private addressBookAdd: FormGroup; + private submitAttempt: boolean = false; + + constructor( + public navCtrl: NavController, + public navParams: NavParams, + public alertCtrl: AlertController, + public bwc: BwcProvider, + public ab: AddressBookProvider, + public formBuilder: FormBuilder + ) { + this.addressBookAdd = this.formBuilder.group({ + name: ['', Validators.compose([Validators.required, Validators.pattern('[a-zA-Z0-9 ]*')])], + email: ['', this.emailOrEmpty], + address: ['', Validators.compose([Validators.required, new AddressValidator(bwc).isValid])] + }); + } + + ionViewDidLoad() { + console.log('ionViewDidLoad AddressbookAddPage'); + } + + private emailOrEmpty(control: AbstractControl): ValidationErrors | null { + return control.value === '' ? null : Validators.email(control); + } + + public save() { + this.submitAttempt = true; + + if(this.addressBookAdd.valid){ + this.ab.add(this.addressBookAdd.value).then((ab) => { + this.navCtrl.pop(); + + }).catch((err) => { + let opts = { + title: err, + buttons: [{ + text: 'OK', + handler: () => { + this.navCtrl.pop(); + } + }], + } + this.alertCtrl.create(opts).present(); + }); + } + else { + let opts = { + title: 'Error', + message: 'Could not save the contact', + buttons: [{ + text: 'OK', + handler: () => { + this.navCtrl.pop(); + } + }], + } + this.alertCtrl.create(opts).present(); + } + } + +} diff --git a/src/pages/addressbook/addressbook.html b/src/pages/addressbook/addressbook.html index 279390952..235fe85b7 100644 --- a/src/pages/addressbook/addressbook.html +++ b/src/pages/addressbook/addressbook.html @@ -27,6 +27,12 @@
+ + + + {{ entry.name }} + +
diff --git a/src/pages/addressbook/addressbook.ts b/src/pages/addressbook/addressbook.ts index 91b9bd643..7d9f4d7ab 100644 --- a/src/pages/addressbook/addressbook.ts +++ b/src/pages/addressbook/addressbook.ts @@ -1,5 +1,7 @@ import { Component } from '@angular/core'; import { NavController, NavParams, AlertController } from 'ionic-angular'; +import { AddressbookAddPage } from './add/add'; +import { AddressbookViewPage } from './view/view'; import { AddressBookProvider } from '../../providers/address-book/address-book'; import { Logger } from '@nsalaun/ng-logger'; import * as _ from 'lodash'; @@ -10,9 +12,13 @@ import * as _ from 'lodash'; }) export class AddressbookPage { + private cache: boolean = false; + private isEmptyList: boolean; private addressbook: Array = []; + private searchQuery: string = ''; + constructor( public navCtrl: NavController, public navParams: NavParams, @@ -23,6 +29,11 @@ export class AddressbookPage { this.initAddressbook(); } + ionViewDidEnter() { + if (this.cache) this.initAddressbook(); + this.cache = true; + } + private initAddressbook() { this.addressbookProvider.list().then((ab) => { this.isEmptyList = _.isEmpty(ab); @@ -54,4 +65,28 @@ export class AddressbookPage { }); }; + private addEntry() { + this.navCtrl.push(AddressbookAddPage); + }; + + private viewEntry(ab: any) { + this.navCtrl.push(AddressbookViewPage, { address: ab.address }); + } + + private getItems(ev: any) { + // Reset items back to all of the items + this.initAddressbook(); + + // set val to the value of the searchbar + let val = ev.target.value; + + // if the value is an empty string don't filter the items + if (val && val.trim() != '') { + this.addressbook = this.addressbook.filter((item) => { + let name = item['name']; + return _.includes(name.toLowerCase(), val.toLowerCase()); + }); + } + } + } diff --git a/src/pages/addressbook/view/view.html b/src/pages/addressbook/view/view.html new file mode 100644 index 000000000..9208339dc --- /dev/null +++ b/src/pages/addressbook/view/view.html @@ -0,0 +1,23 @@ + + + + {{ contact.name }} + + + + + + + + + + {{ contact.name }} + + + {{ contact.address }} + + + + + + diff --git a/src/pages/addressbook/view/view.scss b/src/pages/addressbook/view/view.scss new file mode 100644 index 000000000..27003aac0 --- /dev/null +++ b/src/pages/addressbook/view/view.scss @@ -0,0 +1,3 @@ +page-addressbook-view { + +} diff --git a/src/pages/addressbook/view/view.ts b/src/pages/addressbook/view/view.ts new file mode 100644 index 000000000..d7683681f --- /dev/null +++ b/src/pages/addressbook/view/view.ts @@ -0,0 +1,62 @@ +import { Component } from '@angular/core'; +import { NavController, NavParams, AlertController } from 'ionic-angular'; +import { AddressBookProvider } from '../../../providers/address-book/address-book'; + +@Component({ + selector: 'page-addressbook-view', + templateUrl: 'view.html', +}) +export class AddressbookViewPage { + + private address: string; + private contact: Object = { + name: '', + address: '', + email: '' + }; + + constructor( + public navCtrl: NavController, + public navParams: NavParams, + public alertCtrl: AlertController, + public ab: AddressBookProvider + ) { + this.address = this.navParams.get('address'); + } + + ionViewDidLoad() { + this.ab.get(this.address).then((entry) => { + this.contact = entry; + }).catch((err) => { + let alertError = this.alertCtrl.create({ + title: err, + buttons: [ + { + text: 'Go back', + handler: () => { + this.navCtrl.pop(); + } + } + ] + }); + alertError.present(); + }); + } + + private remove() { + this.ab.remove(this.address).then(() => { + this.navCtrl.pop(); + }).catch((err) => { + let alertError = this.alertCtrl.create({ + title: err, + buttons: [ + { + text: 'OK' + } + ] + }); + alertError.present(); + }); + } + +} diff --git a/src/providers/address-book/address-book.ts b/src/providers/address-book/address-book.ts index 7c5553396..b6a487be7 100644 --- a/src/providers/address-book/address-book.ts +++ b/src/providers/address-book/address-book.ts @@ -27,14 +27,14 @@ export class AddressBookProvider { return network; }; - private get(addr: string): Promise { + public get(addr: string): Promise { return new Promise((resolve, reject) => { this.persistenceProvider.getAddressbook('testnet').then((ab: any) => { - if (ab) ab = JSON.parse(ab); + if (ab && _.isString(ab)) ab = JSON.parse(ab); if (ab && ab[addr]) return resolve(ab[addr]); this.persistenceProvider.getAddressbook('livenet').then((ab: any) => { - if (ab) ab = JSON.parse(ab); + if (ab && _.isString(ab)) ab = JSON.parse(ab); if (ab && ab[addr]) return resolve(ab[addr]); return resolve(); }).catch((err: any) => { @@ -49,11 +49,11 @@ export class AddressBookProvider { public list(): Promise { return new Promise((resolve, reject) => { this.persistenceProvider.getAddressbook('testnet').then((ab: any) => { - if (ab) ab = JSON.parse(ab); + if (ab && _.isString(ab)) ab = JSON.parse(ab); ab = ab || {}; this.persistenceProvider.getAddressbook('livenet').then((ab2: any) => { - if (ab2) ab2 = JSON.parse(ab2); + if (ab2 && _.isString(ab)) ab2 = JSON.parse(ab2); ab2 = ab2 || {}; return resolve(_.defaults(ab2, ab)); @@ -71,7 +71,7 @@ export class AddressBookProvider { var network = this.getNetwork(entry.address); if (_.isEmpty(network)) return reject('Not valid bitcoin address'); this.persistenceProvider.getAddressbook(network).then((ab: any) => { - if (ab) ab = JSON.parse(ab); + if (ab && _.isString(ab)) ab = JSON.parse(ab); ab = ab || {}; if (_.isArray(ab)) ab = {}; // No array if (ab[entry.address]) return reject('Entry already exist'); @@ -96,7 +96,7 @@ export class AddressBookProvider { var network = this.getNetwork(addr); if (_.isEmpty(network)) return reject('Not valid bitcoin address'); this.persistenceProvider.getAddressbook(network).then((ab: any) => { - if (ab) ab = JSON.parse(ab); + if (ab && _.isString(ab)) ab = JSON.parse(ab); ab = ab || {}; if (_.isEmpty(ab)) return reject('Addressbook is empty'); if (!ab[addr]) return reject('Entry does not exist'); diff --git a/src/validators/address.ts b/src/validators/address.ts new file mode 100644 index 000000000..e029e514a --- /dev/null +++ b/src/validators/address.ts @@ -0,0 +1,62 @@ +import { FormControl } from '@angular/forms'; +import { BwcProvider } from '../providers/bwc/bwc'; + +export class AddressValidator { + + static bitcore: BwcProvider; + + constructor(bwc: BwcProvider) { + AddressValidator.bitcore = bwc; + } + + isValid(control: FormControl): any { + + let b = AddressValidator.bitcore.getBitcore(); + let c = AddressValidator.bitcore.getBitcoreCash(); + + let URI = b.URI; + let Address = b.Address; + let URICash = c.URI; + let AddressCash = c.Address; + + // Regular url + if (/^https?:\/\//.test(control.value)) { + return null; + } + + // Bip21 uri + let uri, isAddressValidLivenet, isAddressValidTestnet; + if (/^bitcoin:/.test(control.value)) { + let isUriValid = URI.isValid(control.value); + if (isUriValid) { + uri = new URI(control.value); + isAddressValidLivenet = Address.isValid(uri.address.toString(), 'livenet') + isAddressValidTestnet = Address.isValid(uri.address.toString(), 'testnet') + } + if (isUriValid && (isAddressValidLivenet || isAddressValidTestnet)) { + return null; + } + } else if (/^bitcoincash:/.test(control.value)) { + let isUriValid = URICash.isValid(control.value); + if (isUriValid) { + uri = new URICash(control.value); + isAddressValidLivenet = AddressCash.isValid(uri.address.toString(), 'livenet') + } + if (isUriValid && isAddressValidLivenet) { + return null; + } + } + + // Regular Address: try Bitcoin and Bitcoin Cash + let regularAddressLivenet = Address.isValid(control.value, 'livenet'); + let regularAddressTestnet = Address.isValid(control.value, 'testnet'); + let regularAddressCashLivenet = AddressCash.isValid(control.value, 'livenet'); + if (regularAddressLivenet || regularAddressTestnet || regularAddressCashLivenet) { + return null; + } + + return { + "Invalid Address": true + }; + } +}