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",
|
||||
"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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 *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>
|
||||
|
||||
</ion-content>
|
||||
|
|
|
@ -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<object> = [];
|
||||
|
||||
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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
private get(addr: string): Promise<any> {
|
||||
public get(addr: string): Promise<any> {
|
||||
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<any> {
|
||||
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');
|
||||
|
|
|
@ -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