mirror of https://github.com/BTCPrivate/copay.git
Add, remove, search address book. Adds bitcoin address validator
This commit is contained in:
parent
9348049123
commit
707521569e
|
@ -131,7 +131,7 @@
|
||||||
"codecov": "2.2.0",
|
"codecov": "2.2.0",
|
||||||
"fs-extra": "^4.0.2",
|
"fs-extra": "^4.0.2",
|
||||||
"html-loader": "0.4.5",
|
"html-loader": "0.4.5",
|
||||||
"ionic": "3.16.0",
|
"ionic": "3.17.0",
|
||||||
"jasmine-core": "2.6.4",
|
"jasmine-core": "2.6.4",
|
||||||
"jasmine-spec-reporter": "4.1.1",
|
"jasmine-spec-reporter": "4.1.1",
|
||||||
"karma": "1.7.0",
|
"karma": "1.7.0",
|
||||||
|
|
|
@ -39,6 +39,8 @@ import { TourPage } from '../pages/onboarding/tour/tour';
|
||||||
import { BackupWarningPage } from '../pages/backup/backup-warning/backup-warning';
|
import { BackupWarningPage } from '../pages/backup/backup-warning/backup-warning';
|
||||||
import { BackupGamePage } from '../pages/backup/backup-game/backup-game';
|
import { BackupGamePage } from '../pages/backup/backup-game/backup-game';
|
||||||
import { AddressbookPage } from '../pages/addressbook/addressbook';
|
import { AddressbookPage } from '../pages/addressbook/addressbook';
|
||||||
|
import { AddressbookAddPage } from '../pages/addressbook/add/add';
|
||||||
|
import { AddressbookViewPage } from '../pages/addressbook/view/view';
|
||||||
|
|
||||||
/* Tabs */
|
/* Tabs */
|
||||||
import { HomePage } from '../pages/home/home';
|
import { HomePage } from '../pages/home/home';
|
||||||
|
@ -106,6 +108,8 @@ let pages: any = [
|
||||||
BackupWarningPage,
|
BackupWarningPage,
|
||||||
BackupGamePage,
|
BackupGamePage,
|
||||||
AddressbookPage,
|
AddressbookPage,
|
||||||
|
AddressbookAddPage,
|
||||||
|
AddressbookViewPage,
|
||||||
AboutPage,
|
AboutPage,
|
||||||
AdvancedPage,
|
AdvancedPage,
|
||||||
AltCurrencyPage,
|
AltCurrencyPage,
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<ion-header>
|
||||||
|
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>{{ 'Add Contact' | translate }}</ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
<ion-content padding>
|
||||||
|
|
||||||
|
<form [formGroup]="addressBookAdd">
|
||||||
|
<ion-list>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label stacked>{{ 'Name' | translate }}</ion-label>
|
||||||
|
<ion-input formControlName="name" type="text"></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label stacked>{{ 'Email' | translate }}</ion-label>
|
||||||
|
<ion-input formControlName="email" type="email"></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label stacked>{{ 'Bitcoin Address' | translate }}</ion-label>
|
||||||
|
<ion-input formControlName="address" type="text" address-validator></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<button ion-button block (click)="save()" [disabled]="addressBookAdd.invalid || submitAttempt">{{ 'Save' | translate }}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,3 @@
|
||||||
|
page-addressbook-add {
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,6 +27,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!isEmptyList">
|
<div *ngIf="!isEmptyList">
|
||||||
|
<ion-searchbar (ionInput)="getItems($event)"></ion-searchbar>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item *ngFor="let entry of addressbook" (click)="viewEntry(entry)">
|
||||||
|
{{ entry.name }}
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NavController, NavParams, AlertController } from 'ionic-angular';
|
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 { AddressBookProvider } from '../../providers/address-book/address-book';
|
||||||
import { Logger } from '@nsalaun/ng-logger';
|
import { Logger } from '@nsalaun/ng-logger';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
@ -10,9 +12,13 @@ import * as _ from 'lodash';
|
||||||
})
|
})
|
||||||
export class AddressbookPage {
|
export class AddressbookPage {
|
||||||
|
|
||||||
|
private cache: boolean = false;
|
||||||
|
|
||||||
private isEmptyList: boolean;
|
private isEmptyList: boolean;
|
||||||
private addressbook: Array<object> = [];
|
private addressbook: Array<object> = [];
|
||||||
|
|
||||||
|
private searchQuery: string = '';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public navCtrl: NavController,
|
public navCtrl: NavController,
|
||||||
public navParams: NavParams,
|
public navParams: NavParams,
|
||||||
|
@ -23,6 +29,11 @@ export class AddressbookPage {
|
||||||
this.initAddressbook();
|
this.initAddressbook();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ionViewDidEnter() {
|
||||||
|
if (this.cache) this.initAddressbook();
|
||||||
|
this.cache = true;
|
||||||
|
}
|
||||||
|
|
||||||
private initAddressbook() {
|
private initAddressbook() {
|
||||||
this.addressbookProvider.list().then((ab) => {
|
this.addressbookProvider.list().then((ab) => {
|
||||||
this.isEmptyList = _.isEmpty(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());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<ion-header>
|
||||||
|
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>{{ contact.name }}</ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
<ion-content padding>
|
||||||
|
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>
|
||||||
|
{{ contact.name }}
|
||||||
|
</ion-card-header>
|
||||||
|
<ion-card-content text-wrap>
|
||||||
|
{{ contact.address }}
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<button ion-button clear color="danger" (click)="remove()">Remove</button>
|
||||||
|
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,3 @@
|
||||||
|
page-addressbook-view {
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,14 +27,14 @@ export class AddressBookProvider {
|
||||||
return network;
|
return network;
|
||||||
};
|
};
|
||||||
|
|
||||||
private get(addr: string): Promise<any> {
|
public get(addr: string): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.persistenceProvider.getAddressbook('testnet').then((ab: any) => {
|
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]);
|
if (ab && ab[addr]) return resolve(ab[addr]);
|
||||||
|
|
||||||
this.persistenceProvider.getAddressbook('livenet').then((ab: any) => {
|
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]);
|
if (ab && ab[addr]) return resolve(ab[addr]);
|
||||||
return resolve();
|
return resolve();
|
||||||
}).catch((err: any) => {
|
}).catch((err: any) => {
|
||||||
|
@ -49,11 +49,11 @@ export class AddressBookProvider {
|
||||||
public list(): Promise<any> {
|
public list(): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.persistenceProvider.getAddressbook('testnet').then((ab: any) => {
|
this.persistenceProvider.getAddressbook('testnet').then((ab: any) => {
|
||||||
if (ab) ab = JSON.parse(ab);
|
if (ab && _.isString(ab)) ab = JSON.parse(ab);
|
||||||
|
|
||||||
ab = ab || {};
|
ab = ab || {};
|
||||||
this.persistenceProvider.getAddressbook('livenet').then((ab2: any) => {
|
this.persistenceProvider.getAddressbook('livenet').then((ab2: any) => {
|
||||||
if (ab2) ab2 = JSON.parse(ab2);
|
if (ab2 && _.isString(ab)) ab2 = JSON.parse(ab2);
|
||||||
|
|
||||||
ab2 = ab2 || {};
|
ab2 = ab2 || {};
|
||||||
return resolve(_.defaults(ab2, ab));
|
return resolve(_.defaults(ab2, ab));
|
||||||
|
@ -71,7 +71,7 @@ export class AddressBookProvider {
|
||||||
var network = this.getNetwork(entry.address);
|
var network = this.getNetwork(entry.address);
|
||||||
if (_.isEmpty(network)) return reject('Not valid bitcoin address');
|
if (_.isEmpty(network)) return reject('Not valid bitcoin address');
|
||||||
this.persistenceProvider.getAddressbook(network).then((ab: any) => {
|
this.persistenceProvider.getAddressbook(network).then((ab: any) => {
|
||||||
if (ab) ab = JSON.parse(ab);
|
if (ab && _.isString(ab)) ab = JSON.parse(ab);
|
||||||
ab = ab || {};
|
ab = ab || {};
|
||||||
if (_.isArray(ab)) ab = {}; // No array
|
if (_.isArray(ab)) ab = {}; // No array
|
||||||
if (ab[entry.address]) return reject('Entry already exist');
|
if (ab[entry.address]) return reject('Entry already exist');
|
||||||
|
@ -96,7 +96,7 @@ export class AddressBookProvider {
|
||||||
var network = this.getNetwork(addr);
|
var network = this.getNetwork(addr);
|
||||||
if (_.isEmpty(network)) return reject('Not valid bitcoin address');
|
if (_.isEmpty(network)) return reject('Not valid bitcoin address');
|
||||||
this.persistenceProvider.getAddressbook(network).then((ab: any) => {
|
this.persistenceProvider.getAddressbook(network).then((ab: any) => {
|
||||||
if (ab) ab = JSON.parse(ab);
|
if (ab && _.isString(ab)) ab = JSON.parse(ab);
|
||||||
ab = ab || {};
|
ab = ab || {};
|
||||||
if (_.isEmpty(ab)) return reject('Addressbook is empty');
|
if (_.isEmpty(ab)) return reject('Addressbook is empty');
|
||||||
if (!ab[addr]) return reject('Entry does not exist');
|
if (!ab[addr]) return reject('Entry does not exist');
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue