Merge branch 'v4' of github.com:bitpay/copay into polish/onboarding

This commit is contained in:
Jason Dreyzehner 2018-01-25 18:22:34 -05:00
commit 7b02d1100c
51 changed files with 2004 additions and 539 deletions

1
.gitignore vendored
View File

@ -30,6 +30,7 @@ src/theme/overrides.scss
src/assets/appConfig.json
src/assets/externalServices.json
src/assets/img/app
src/assets/i18n/crowdin_api_key.txt
src/index.html
src/manifest.json

View File

@ -31,7 +31,7 @@
"glidera": true,
"debitcard": true,
"amazon": true,
"mercadolibre": false,
"mercadolibre": true,
"shapeshift": true
}
}

View File

@ -13,6 +13,7 @@
$color-primary: #647ce8;
$color-secondary: #111b49;
$icon-border-radius: 50%;
$txp-icon-border-radius: 2rem;
$toolbar-background: #1e3186;
$toolbar-scanner: rgba(30, 49, 134, 0.8);

View File

@ -27,11 +27,11 @@
"androidVersion": "40000002",
"_extraCSS": null,
"_enabledExtensions": {
"coinbase": false,
"glidera": false,
"coinbase": true,
"glidera": true,
"debitcard": false,
"amazon": false,
"mercadolibre": false,
"shapeshift": false
"amazon": true,
"mercadolibre": true,
"shapeshift": true
}
}

View File

@ -13,6 +13,7 @@
$color-primary: #1abb9b;
$color-secondary: #31465b;
$icon-border-radius: 3px;
$txp-icon-border-radius: 0.3rem;
$toolbar-background: #192c3a;
$toolbar-scanner: rgba(25, 44, 58, 0.8);

View File

@ -125,41 +125,50 @@
.gravatar {
min-width: 3rem;
min-height: 3rem;
border-radius: $icon-border-radius;
}
// Compatibility Ionic 1
.light,
a.light {
color: color($colors, light);
}
a.light,
.stable,
a.stable {
color: color($colors, light);
}
.positive,
a.positive {
color: color($colors, secondary);
}
.calm,
a.calm {
color: color($colors, light);
color: color($colors, grey);
}
.positive,
a.positive {
color: color($colors, primary);
}
.energized,
a.energized {
color: color($colors, primary);
}
.assertive,
a.assertive {
color: color($colors, danger);
}
.balanced,
a.balanced {
color: color($colors, success);
}
.royal,
a.royal {
color: color($colors, secondary);
color: color($colors, dark);
}
.text-gray {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,248 @@
#!/usr/bin/env node
'use strict';
if (process.argv[2]) {
var no_build = (process.argv[2].toLowerCase() == '--nobuild')
if (no_build == false) {
console.log('Incorrect arg. Please use --nobuild if you would like to download without api key.');
process.exit(1);
};
} else {
var no_build = false;
console.log('\n' +
'Please note: If you do not have the crowdin API key and would like to download the ' +
'translations without building anyways, please make sure your English files are the same ' +
'version as crowdin, and then run this script with --nobuild\n\n' +
'eg. "node crowdin_download.js --nobuild"\n\n');
};
var fs = require('fs');
var path = require('path');
var https = require('https');
var AdmZip = require('adm-zip');
var crowdin_identifier = 'copay'
var local_file_name2 = path.join(__dirname, 'docs/appstore_en.txt')
var local_file_name3 = path.join(__dirname, 'docs/updateinfo_en.txt')
try {
fs.statSync(local_file_name2);
fs.statSync(local_file_name3);
}
catch (e) {
console.log('\n### ABORTING ### One of the following files does not exist:\n' + local_file_name2 + '\n' + local_file_name3);
process.exit(1);
}
try {
// obtain the crowdin api key
var crowdin_api_key = fs.readFileSync(path.join(__dirname, 'crowdin_api_key.txt'), 'utf8')
} catch (e) {
console.log('### ERROR ### You do not have the crowdin api key in ./crowdin_api_key.txt');
process.exit(1);
};
if (no_build == false) { // Reminder: Any changes to the script below must also be made to the else clause and vice versa.
// This call will tell the server to generate a new zip file for you based on most recent translations.
https.get('https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key, function (res) {
console.log('Export Got response: ' + res.statusCode);
res.on('data', function (chunk) {
var resxml = chunk.toString('utf8');
console.log(resxml);
if (resxml.indexOf('status="skipped"') >= 0) {
console.log('Translation build was skipped due to either:\n' +
'1. No changes since last translation build, or\n' +
'2. API limit of once per 30 minutes has not been waited.\n\n' +
'Since we can not guarantee that translations have been built properly, this script will end here.\n' +
'Log in to Copay\'s Crowdin Settings and click the "Build Project" button to assure it is built recently, and then run this ' +
'script again with the --nobuild arg to download translations without checking if built.');
process.exit(1);
};
// Download most recent translations for all languages.
https.get('https://crowdin.com/download/project/' + crowdin_identifier + '.zip', function (res) {
var data = [], dataLen = 0;
res.on('data', function (chunk) {
data.push(chunk);
dataLen += chunk.length;
}).on('end', function () {
var buf = new Buffer(dataLen);
for (var i = 0, len = data.length, pos = 0; i < len; i++) {
data[i].copy(buf, pos);
pos += data[i].length;
};
var zip = new AdmZip(buf);
zip.extractAllTo('./', true);
console.log('Done extracting ZIP file.');
var files = fs.readdirSync('./docs');
for (var i in files) {
debugger;
if (files[i].slice(0, 9) == 'v4appstore_' && files[i].slice(-4) == '.txt' && files[i] != 'appstore_en.txt') {
var english_file = fs.readFileSync(local_file_name2, 'utf8');
var compare_file = fs.readFileSync(path.join(__dirname, 'docs/' + files[i]), 'utf8')
english_file = english_file.replace(/\r\n/g, '\n');
compare_file = compare_file.replace(/\r\n/g, '\n');
if (compare_file == english_file) {
fs.unlinkSync(path.join(__dirname, 'docs/' + files[i]));
};
};
if (files[i].slice(0, 11) == 'v4updateinfo_' && files[i].slice(-4) == '.txt' && files[i] != 'updateinfo_en.txt') {
var english_file = fs.readFileSync(local_file_name3, 'utf8');
var compare_file = fs.readFileSync(path.join(__dirname, 'docs/' + files[i]), 'utf8')
english_file = english_file.replace(/\r\n/g, '\n');
compare_file = compare_file.replace(/\r\n/g, '\n');
if (compare_file == english_file) {
fs.unlinkSync(path.join(__dirname, 'docs/' + files[i]));
};
};
};
console.log('Cleaned out completely untranslated appstore docs.');
var files = fs.readdirSync('./po');
for (var i in files) {
if (files[i] != 'app.pot') {
var po_file = fs.readFileSync(path.join(__dirname, 'po/' + files[i]), 'utf8');
var po_array = po_file.split('\n');
for (var j in po_array) {
if (po_array[j].slice(0, 5) == 'msgid') {
var source_text = po_array[j].slice(5);
} else if (po_array[j].slice(0, 6) == 'msgstr') {
var translate_text = po_array[j].slice(6);
// if a line is not == English, it means there is translation. Keep this file.
if (source_text != translate_text) {
// erase email addresses of last translator for privacy
po_file = po_file.replace(/ <.+@.+\..+>/, '')
fs.writeFileSync(path.join(__dirname, 'po/' + files[i]), po_file);
// split the file into 3 parts, before locale, locale, and after locale.
var lang_pos = po_file.search('"Language: ') + 11;
var po_start = po_file.slice(0, lang_pos);
var po_locale = po_file.slice(lang_pos, lang_pos + 5);
var po_end = po_file.slice(lang_pos + 5);
// check for underscore, if it's there, only take the first 2 letters and reconstruct the po file.
if (po_locale.search('_') > 0) {
fs.writeFileSync(path.join(__dirname, 'po/' + files[i]), po_start + po_locale.slice(0, 2) + po_end);
po_start = '';
po_locale = '';
po_end = '';
};
break;
};
};
if (j == po_array.length - 1) { // All strings are exactly identical to English. Delete po file.
fs.unlinkSync(path.join(__dirname, 'po/' + files[i]));
};
};
};
};
console.log('Cleaned out completely untranslated po files.');
});
});
});
}).on('error', function (e) {
console.log('Export Got error: ' + e.message);
});
} else { // Reminder: Any changes to the script below must also be made to the above and vice versa.
// Download most recent translations for all languages.
https.get('https://api.crowdin.com/api/project/' + crowdin_identifier + '/download/all.zip?key=' + crowdin_api_key, function (res) {
var data = [], dataLen = 0;
res.on('data', function (chunk) {
data.push(chunk);
dataLen += chunk.length;
}).on('end', function () {
var buf = new Buffer(dataLen);
for (var i = 0, len = data.length, pos = 0; i < len; i++) {
data[i].copy(buf, pos);
pos += data[i].length;
};
var zip = new AdmZip(buf);
zip.extractAllTo('./', true);
console.log('Done extracting ZIP file.');
var files = fs.readdirSync('./docs');
for (var i in files) {
if (files[i].slice(0, 9) == 'v4appstore_' && files[i].slice(-4) == '.txt' && files[i] != 'appstore_en.txt') {
var english_file = fs.readFileSync(local_file_name2, 'utf8');
var compare_file = fs.readFileSync(path.join(__dirname, 'docs/' + files[i]), 'utf8')
english_file = english_file.replace(/\r\n/g, '\n');
compare_file = compare_file.replace(/\r\n/g, '\n');
if (compare_file == english_file) {
fs.unlinkSync(path.join(__dirname, 'docs/' + files[i]));
};
};
if (files[i].slice(0, 11) == 'v4updateinfo_' && files[i].slice(-4) == '.txt' && files[i] != 'updateinfo_en.txt') {
var english_file = fs.readFileSync(local_file_name3, 'utf8');
var compare_file = fs.readFileSync(path.join(__dirname, 'docs/' + files[i]), 'utf8')
english_file = english_file.replace(/\r\n/g, '\n');
compare_file = compare_file.replace(/\r\n/g, '\n');
if (compare_file == english_file) {
fs.unlinkSync(path.join(__dirname, 'docs/' + files[i]));
};
};
};
console.log('Cleaned out completely untranslated appstore docs.');
var files = fs.readdirSync('./po');
for (var i in files) {
if (files[i] != 'app.pot') {
var po_file = fs.readFileSync(path.join(__dirname, 'po/' + files[i]), 'utf8');
var po_array = po_file.split('\n');
for (var j in po_array) {
if (po_array[j].slice(0, 5) == 'msgid') {
var source_text = po_array[j].slice(5);
} else if (po_array[j].slice(0, 6) == 'msgstr') {
var translate_text = po_array[j].slice(6);
// if a line is not == English, it means there is translation. Keep this file.
if (source_text != translate_text) {
// erase email addresses of last translator for privacy
po_file = po_file.replace(/ <.+@.+\..+>/, '')
fs.writeFileSync(path.join(__dirname, 'po/' + files[i]), po_file);
// split the file into 3 parts, before locale, locale, and after locale.
var lang_pos = po_file.search('"Language: ') + 11;
var po_start = po_file.slice(0, lang_pos);
var po_locale = po_file.slice(lang_pos, lang_pos + 5);
var po_end = po_file.slice(lang_pos + 5);
// check for underscore, if it's there, only take the first 2 letters and reconstruct the po file.
if (po_locale.search('_') > 0) {
fs.writeFileSync(path.join(__dirname, 'po/' + files[i]), po_start + po_locale.slice(0, 2) + po_end);
po_start = '';
po_locale = '';
po_end = '';
};
break;
};
};
if (j == po_array.length - 1) { // All strings are exactly identical to English. Delete po file.
fs.unlinkSync(path.join(__dirname, 'po/' + files[i]));
};
};
};
};
console.log('Cleaned out completely untranslated po files.');
});
});
};

View File

@ -0,0 +1,67 @@
#!/usr/bin/env node
'use strict';
var fs = require('fs');
var path = require('path');
var https = require('https');
var bhttp = require('bhttp');
var crowdin_identifier = 'copay'
var local_file_name1 = path.join(__dirname, 'app.pot')
// Similar to Github, normalize all line breaks to CRLF so that different people
// using different OSes to update does not constantly swith format back and forth.
var local_file1_text = fs.readFileSync(local_file_name1, 'utf8');
local_file1_text = local_file1_text.replace(/\r\n/g, '\n');
local_file1_text = local_file1_text.replace(/\n/g, '\r\n');
fs.writeFileSync(local_file_name1, local_file1_text);
var local_file1 = fs.createReadStream(local_file_name1)
var local_file_name2 = path.join(__dirname, 'docs/appstore_en.txt')
var local_file2_text = fs.readFileSync(local_file_name2, 'utf8');
local_file2_text = local_file2_text.replace(/\r\n/g, '\n');
local_file2_text = local_file2_text.replace(/\n/g, '\r\n');
fs.writeFileSync(local_file_name2, local_file2_text);
var local_file2 = fs.createReadStream(local_file_name2)
var local_file_name3 = path.join(__dirname, 'docs/updateinfo_en.txt')
var local_file3_text = fs.readFileSync(local_file_name3, 'utf8');
local_file3_text = local_file3_text.replace(/\r\n/g, '\n');
local_file3_text = local_file3_text.replace(/\n/g, '\r\n');
fs.writeFileSync(local_file_name3, local_file3_text);
var local_file3 = fs.createReadStream(local_file_name3)
// obtain the crowdin api key
var crowdin_api_key = fs.readFileSync(path.join(__dirname, 'crowdin_api_key.txt'))
//console.log('api key: ' + crowdin_api_key);
if (crowdin_api_key != '') {
var payload = {
'files[app.pot]': local_file1,
'files[appstore/appstore_en.txt]': local_file2,
'files[appstore/updateinfo_en.txt]': local_file3
};
bhttp.post('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key, payload, {}, function (err, response) {
if (!err) console.log('\nResponse from update file call:\n', response.body.toString());
else console.log('\nError from update file call:\n', err.toString());
// This call will tell the server to generate a new zip file for you based on most recent translations.
https.get('https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key, function (res) {
console.log('Export Got response: ' + res.statusCode);
res.on('data', function (chunk) {
console.log(chunk.toString('utf8'));
});
}).on('error', function (e) {
console.log('Export Got error: ' + e.message);
});
})
};

View File

@ -4,7 +4,7 @@
</ion-navbar>
</ion-header>
<ion-content>
<ion-content no-bounce>
<ion-list>
<button class="list-button" ion-item (click)="goToCreateWallet(false)">
<ion-icon item-start>

View File

@ -7,7 +7,7 @@
</ion-header>
<ion-content>
<ion-content no-bounce>
<div *ngIf="wallet" [hidden]="wallet.notAuthorized">
<ion-list class="copayers-secret">
<ion-item>

View File

@ -4,7 +4,7 @@
</ion-navbar>
</ion-header>
<ion-content>
<ion-content no-bounce>
<form [formGroup]="createForm" (ngSubmit)="setOptsAndCreate()">
<ion-item>
<ion-label stacked>Wallet name</ion-label>
@ -65,34 +65,6 @@
<ion-input type="text" formControlName="recoveryPhrase"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Add a password</ion-label>
<ion-toggle formControlName="addPassword" (ionChange)="resetFormFields()"></ion-toggle>
</ion-item>
<div *ngIf="createForm.value.addPassword">
<ion-item>
<ion-label stacked>Password</ion-label>
<ion-input type="password" formControlName="password"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Confirm password</ion-label>
<ion-input type="password" formControlName="confirmPassword"></ion-input>
</ion-item>
<ion-item>
<div class="warning">
<strong translate>This password cannot be recovered. If the password is lost, there is no way you could recover your funds.</strong>
</div>
</ion-item>
<ion-item>
<ion-label stacked>I have written it down</ion-label>
<ion-checkbox formControlName="recoveryPhraseBackedUp" checked="false"></ion-checkbox>
</ion-item>
</div>
<ion-item *ngIf="createForm.value.selectedSeed == 'new' && createForm.value.coin == 'btc'">
<ion-label stacked>Testnet</ion-label>
<ion-toggle formControlName="testnetEnabled" (ionChange)="setDerivationPath()"></ion-toggle>
@ -112,5 +84,5 @@
</ion-content>
<ion-footer>
<button ion-button block class="button-footer" (click)="setOptsAndCreate()" [disabled]="!createForm.valid || validatePasswords()">Create wallet</button>
<button ion-button block class="button-footer" (click)="setOptsAndCreate()" [disabled]="!createForm.valid">Create wallet</button>
</ion-footer>

View File

@ -1,9 +1,2 @@
.warning {
background-color: #ef473a;
border-color: #e42112;
color: #fff;
padding: 0.5rem;
border: 1px solid;
white-space: normal;
text-align: center;
page-create-wallet {
}

View File

@ -83,10 +83,6 @@ export class CreateWalletPage implements OnInit {
bwsURL: [this.defaults.bws.url],
selectedSeed: ['new'],
recoveryPhrase: [null],
addPassword: [false],
password: [null],
confirmPassword: [null],
recoveryPhraseBackedUp: [null],
derivationPath: [this.derivationPathByDefault],
testnetEnabled: [false],
singleAddress: [false],
@ -95,52 +91,26 @@ export class CreateWalletPage implements OnInit {
this.setTotalCopayers(this.tc);
this.updateRCSelect(this.tc);
this.resetPasswordFields();
}
ngOnInit() {
if (this.isShared) {
this.createForm.get('myName').setValidators([Validators.required]);
}
this.createForm.get('addPassword').valueChanges.subscribe((addPassword: boolean) => {
if (addPassword) {
this.createForm.get('password').setValidators([Validators.required]);
this.createForm.get('confirmPassword').setValidators([Validators.required]);
} else {
this.createForm.get('password').clearValidators();
this.createForm.get('confirmPassword').clearValidators();
}
this.createForm.get('password').updateValueAndValidity();
this.createForm.get('confirmPassword').updateValueAndValidity();
});
}
public validatePasswords(): boolean {
if (this.createForm.value.addPassword) {
if (this.createForm.value.password == this.createForm.value.confirmPassword) {
if (this.createForm.value.recoveryPhraseBackedUp) return false;
}
return true;
}
return false;
}
public setTotalCopayers(n: number): void {
this.createForm.value.totalCopayers = n;
this.createForm.controls['totalCopayers'].setValue(n);
this.updateRCSelect(n);
this.updateSeedSourceSelect();
};
private updateRCSelect(n: number): void {
this.createForm.value.totalCopayers = n;
this.createForm.controls['totalCopayers'].setValue(n);
var maxReq = this.COPAYER_PAIR_LIMITS[n];
this.signatures = _.range(1, maxReq + 1);
this.createForm.value.requiredCopayers = Math.min(Math.trunc(n / 2 + 1), maxReq);
};
this.createForm.controls['requiredCopayers'].setValue(Math.min(Math.trunc(n / 2 + 1), maxReq));
private resetPasswordFields(): void {
this.createForm.value.password = this.createForm.value.confirmPassword = this.createForm.value.recoveryPhraseBackedUp = null;
};
private updateSeedSourceSelect(): void {
@ -153,7 +123,7 @@ export class CreateWalletPage implements OnInit {
label: 'Specify Recovery Phrase',
supportsTestnet: false
}];
this.createForm.value.selectedSeed = this.seedOptions[0].id;
this.createForm.controls['selectedSeed'].setValue(this.seedOptions[0].id); // new or set
};
public seedOptionsChange(seed: any): void {
@ -162,21 +132,15 @@ export class CreateWalletPage implements OnInit {
} else {
this.createForm.get('recoveryPhrase').setValidators(null);
}
this.createForm.value.selectedSeed = seed; // new or set
this.createForm.value.testnet = false;
this.createForm.value.derivationPath = this.derivationPathByDefault;
this.resetFormFields();
}
public resetFormFields(): void {
this.createForm.value.password = null;
this.createForm.value.confirmPassword = null;
this.createForm.value.recoveryPhraseBackedUp = null;
this.createForm.value.recoveryPhrase = null;
this.createForm.controls['selectedSeed'].setValue(seed); // new or set
this.createForm.controls['testnet'].setValue(false);
this.createForm.controls['derivationPath'].setValue(this.derivationPathByDefault);
this.createForm.controls['recoveryPhrase'].setValue(null);
}
public setDerivationPath(): void {
this.createForm.value.derivationPath = this.createForm.value.testnet ? this.derivationPathForTestnet : this.derivationPathByDefault;
let path: string = this.createForm.value.testnet ? this.derivationPathForTestnet : this.derivationPathByDefault;
this.createForm.controls['derivationPath'].setValue(path);
}
public setOptsAndCreate(): void {
@ -201,7 +165,6 @@ export class CreateWalletPage implements OnInit {
} else {
opts.mnemonic = words;
}
opts.passphrase = this.createForm.value.password;
let pathData = this.derivationPathHelperProvider.parse(this.createForm.value.derivationPath);
if (!pathData) {
@ -212,8 +175,6 @@ export class CreateWalletPage implements OnInit {
opts.networkName = pathData.networkName;
opts.derivationStrategy = pathData.derivationStrategy;
} else {
opts.passphrase = this.createForm.value.password;
}
if (setSeed && !opts.mnemonic && !opts.extendedPrivateKey) {

View File

@ -4,7 +4,7 @@
</ion-navbar>
</ion-header>
<ion-content>
<ion-content no-bounce>
<ion-segment [(ngModel)]="selectedTab" color="primary" (ionChange)="selectTab(selectedTab)">
<ion-segment-button value="words" translate>
Words

View File

@ -4,7 +4,7 @@
</ion-navbar>
</ion-header>
<ion-content>
<ion-content no-bounce>
<form [formGroup]="joinForm" (ngSubmit)="setOptsAndJoin()">
<ion-item>
<ion-label stacked>{{'Your name' | translate}}</ion-label>
@ -50,34 +50,6 @@
<ion-input type="text" formControlName="recoveryPhrase"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>{{'Add a password' | translate}}</ion-label>
<ion-toggle formControlName="addPassword" (ionChange)="resetFormFields()"></ion-toggle>
</ion-item>
<div *ngIf="joinForm.value.addPassword">
<ion-item>
<ion-label stacked>{{'Password' | translate}}</ion-label>
<ion-input type="password" formControlName="password"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>{{'Confirm password' | translate}}</ion-label>
<ion-input type="password" formControlName="confirmPassword"></ion-input>
</ion-item>
<ion-item>
<div class="warning">
<strong translate>This password cannot be recovered. If the password is lost, there is no way you could recover your funds.</strong>
</div>
</ion-item>
<ion-item>
<ion-label stacked>{{'I have written it down' | translate}}</ion-label>
<ion-checkbox formControlName="recoveryPhraseBackedUp" checked="false"></ion-checkbox>
</ion-item>
</div>
<ion-item *ngIf="joinForm.value.selectedSeed == 'set'">
<ion-label stacked>{{'Derivation path' | translate}}</ion-label>
<ion-input type="text" formControlName="derivationPath"></ion-input>
@ -87,6 +59,5 @@
</ion-content>
<ion-footer>
<button ion-button block class="button-footer" (click)="setOptsAndJoin()" [disabled]="!joinForm.valid || validatePasswords()"
translate>Join wallet</button>
<button ion-button block class="button-footer" (click)="setOptsAndJoin()" [disabled]="!joinForm.valid" translate>Join wallet</button>
</ion-footer>

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { Logger } from '../../../providers/logger/logger';
@ -19,7 +19,7 @@ import { WalletProvider } from '../../../providers/wallet/wallet';
selector: 'page-join-wallet',
templateUrl: 'join-wallet.html'
})
export class JoinWalletPage implements OnInit {
export class JoinWalletPage {
private defaults: any;
public showAdvOpts: boolean;
@ -54,10 +54,6 @@ export class JoinWalletPage implements OnInit {
bwsURL: [this.defaults.bws.url],
selectedSeed: ['new'],
recoveryPhrase: [null],
addPassword: [false],
password: [null],
confirmPassword: [null],
recoveryPhraseBackedUp: [null],
derivationPath: [this.derivationPathByDefault],
coin: [this.navParams.data.coin ? this.navParams.data.coin : 'btc']
});
@ -75,7 +71,6 @@ export class JoinWalletPage implements OnInit {
ionViewDidLoad() {
this.logger.info('ionViewDidLoad JoinWalletPage');
this.resetFormFields();
}
ionViewWillEnter() {
@ -86,30 +81,6 @@ export class JoinWalletPage implements OnInit {
}
}
ngOnInit() {
this.joinForm.get('addPassword').valueChanges.subscribe((addPassword: boolean) => {
if (addPassword) {
this.joinForm.get('password').setValidators([Validators.required]);
this.joinForm.get('confirmPassword').setValidators([Validators.required]);
} else {
this.joinForm.get('password').clearValidators();
this.joinForm.get('confirmPassword').clearValidators();
}
this.joinForm.get('password').updateValueAndValidity();
this.joinForm.get('confirmPassword').updateValueAndValidity();
})
}
public validatePasswords(): boolean {
if (this.joinForm.value.addPassword) {
if (this.joinForm.value.password == this.joinForm.value.confirmPassword) {
if (this.joinForm.value.recoveryPhraseBackedUp) return false;
}
return true;
}
return false;
}
public onQrCodeScannedJoin(data: string): void { // TODO
this.joinForm.controls['invitationCode'].setValue(data);
}
@ -123,14 +94,6 @@ export class JoinWalletPage implements OnInit {
this.joinForm.controls['selectedSeed'].setValue(seed);
this.joinForm.controls['testnet'].setValue(false);
this.joinForm.controls['derivationPath'].setValue(this.derivationPathByDefault);
this.resetFormFields();
}
public resetFormFields(): void {
this.joinForm.controls['password'].setValue(null);
this.joinForm.controls['confirmPassword'].setValue(null);
this.joinForm.controls['recoveryPhraseBackedUp'].setValue(null);
this.joinForm.controls['recoveryPhrase'].setValue(null);
}
setDerivationPath() {
@ -155,7 +118,6 @@ export class JoinWalletPage implements OnInit {
} else {
opts.mnemonic = words;
}
opts.passphrase = this.joinForm.value.password;
let pathData = this.derivationPathHelperProvider.parse(this.joinForm.value.derivationPath);
if (!pathData) {
@ -165,8 +127,6 @@ export class JoinWalletPage implements OnInit {
opts.networkName = pathData.networkName;
opts.derivationStrategy = pathData.derivationStrategy;
} else {
opts.passphrase = this.joinForm.value.password;
}
if (setSeed && !opts.mnemonic && !opts.extendedPrivateKey) {

View File

@ -7,7 +7,7 @@
</ion-header>
<ion-content>
<ion-content no-bounce>
<div *ngIf="deleted">
<h1 class="deleted-title">{{'Wallet recovery phrase not available' |translate}}</h1>
<ion-item-divider text-wrap>

View File

@ -3,7 +3,7 @@
</ion-header>
<ion-content>
<ion-content no-bounce>
<h1 translate>Are you being watched?</h1>
<p translate>Now is a perfect time to assess your surroundings. Nearby windows? Hidden cameras? Shoulder-spies?</p>
<img src='assets/img/backup/backup-warning.svg' />

View File

@ -61,34 +61,30 @@
</ion-card>
<ion-list *ngIf="txps && txps[0]">
<ion-item-divider>
<ion-row>
<ion-col class="title">
<ion-list-header color="light">
<div item-start>
<span translate>Payment Proposals</span>
</ion-col>
<ion-col text-right (click)="openProposalsPage()">
<ion-badge *ngIf="txpsN>3" item-end>{{txpsN}}</ion-badge>
</ion-col>
</ion-row>
</ion-item-divider>
</div>
<button ion-button item-end (click)="openProposalsPage()" *ngIf="txpsN>3">
<ion-badge>{{txpsN}}</ion-badge>
</button>
</ion-list-header>
<page-txp *ngFor="let txp of txps" [tx]="txp" [addressbook]="addressbook"></page-txp>
</ion-list>
<ion-list *ngIf="notifications && notifications[0] && recentTransactionsEnabled">
<ion-item-divider>
<ion-row>
<ion-col class="title">
<span translate>Recent Transactions</span>
</ion-col>
<ion-col text-right (click)="openActivityPage()">
<ion-badge *ngIf="notificationsN>3" item-end>{{notificationsN}}</ion-badge>
</ion-col>
</ion-row>
</ion-item-divider>
<ion-list-header color="light">
<div item-start>
<span translate>Recent Transactions</span>
</div>
<button ion-button item-end (click)="openActivityPage()" *ngIf="notificationsN>3">
<ion-badge>{{notificationsN}}</ion-badge>
</button>
</ion-list-header>
<button ion-item *ngFor="let notification of notifications" (click)="openNotificationModal(notification)">
<span *ngFor="let notification of notifications" (click)="openNotificationModal(notification)">
<page-wallet-activity [notification]="notification"></page-wallet-activity>
</button>
</span>
</ion-list>
@ -99,25 +95,25 @@
</div>
<ion-list *ngIf="walletsBtc && walletsBtc[0]">
<ion-item-divider>
<ion-row>
<ion-col class="title">
<ion-list-header color="light">
<div class="title" item-start>
<img src="assets/img/icon-bitcoin.svg" alt="Bitcoin Wallets" width="18" />
<span translate>Bitcoin Wallets</span>
</ion-col>
<ion-col text-right class="icons-add-reorder" *ngIf="!showReorderBtc">
<div class="reorder-icon" (click)="reorderBtc()" *ngIf="walletsBtc.length > 1">
</div>
<div item-end *ngIf="!showReorderBtc">
<button ion-button clear icon-only color="secondary" (click)="reorderBtc()" *ngIf="walletsBtc.length > 1">
<ion-icon name="reorder"></ion-icon>
</div>
<div class="add-icon" (click)="goToAddView()">
</button>
<button ion-button clear icon-only color="primary" (click)="goToAddView()">
<ion-icon name="add"></ion-icon>
</div>
</ion-col>
<ion-col text-right class="icons-add-reorder" *ngIf="showReorderBtc" (click)="reorderBtc()">
{{'Done'|translate}}
</ion-col>
</ion-row>
</ion-item-divider>
</button>
</div>
<div item-end *ngIf="showReorderBtc">
<button ion-button clear color="secondary" (click)="reorderBtc()">
{{'Done'|translate}}
</button>
</div>
</ion-list-header>
<ion-list reorder="{{showReorderBtc}}" (ionItemReorder)="reorderWalletsBtc($event)">
<button ion-item *ngFor="let wallet of walletsBtc" (click)="goToWalletDetails(wallet)">
@ -151,25 +147,25 @@
</ion-list>
<ion-list *ngIf="walletsBch && walletsBch[0]">
<ion-item-divider>
<ion-row>
<ion-col class="title">
<img src="assets/img/bitcoin-cash-logo.svg" alt="Bitcoin Cash Wallets" width="22" />
<span translate>Bitcoin Cash Wallets</span>
</ion-col>
<ion-col class="icons-add-reorder" *ngIf="!showReorderBch" text-right>
<div class="reorder-icon" (click)="reorderBch()" *ngIf="walletsBch.length > 1">
<ion-icon name="reorder"></ion-icon>
</div>
<div class="add-icon" (click)="goToAddView('bch')">
<ion-icon name="add"></ion-icon>
</div>
</ion-col>
<ion-col text-right class="icons-add-reorder" *ngIf="showReorderBch" (click)="reorderBch()">
<ion-list-header color="light">
<div class="title" item-start>
<img src="assets/img/bitcoin-cash-logo.svg" alt="Bitcoin Cash Wallets" width="22" />
<span translate>Bitcoin Cash Wallets</span>
</div>
<div item-end *ngIf="!showReorderBch">
<button ion-button clear icon-only color="secondary" (click)="reorderBch()" *ngIf="walletsBch.length > 1">
<ion-icon name="reorder"></ion-icon>
</button>
<button ion-button clear icon-only color="primary" (click)="goToAddView('bch')">
<ion-icon name="add"></ion-icon>
</button>
</div>
<div item-end *ngIf="showReorderBch">
<button ion-button clear color="secondary" (click)="reorderBch()">
{{'Done'|translate}}
</ion-col>
</ion-row>
</ion-item-divider>
</button>
</div>
</ion-list-header>
<ion-list reorder="{{showReorderBch}}" (ionItemReorder)="reorderWalletsBch($event)">
<button ion-item *ngFor="let wallet of walletsBch" (click)="goToWalletDetails(wallet)">
@ -203,7 +199,7 @@
</ion-list>
<ion-list *ngIf="wallets && wallets[0] && buyAndSellItems && buyAndSellItems.length>0">
<ion-item-divider>
<ion-item-divider color="light">
<ion-row>
<ion-col class="title">
<span translate>Buy &amp; Sell Bitcoin</span>
@ -224,7 +220,7 @@
</ion-list>
<ion-list *ngIf="homeIntegrations && homeIntegrations.length>0">
<ion-item-divider>
<ion-item-divider color="light">
<ion-row>
<ion-col class="title">
<span translate>Services</span>
@ -246,7 +242,7 @@
</ion-list>
<ion-list *ngIf="nextStepsItems && nextStepsItems.length>0">
<ion-item-divider>
<ion-item-divider color="light">
<ion-row>
<ion-col class="title">
<span translate>Next steps</span>

View File

@ -1,7 +1,4 @@
page-home {
ion-item-divider {
margin-top: 20px;
}
.home-logo {
height: 24px;
position: relative;
@ -24,29 +21,9 @@ page-home {
}
}
.title {
color: color($colors, dark);
font-weight: 700;
&.col {
padding: 0;
}
>img {
width: 22px;
display: inline-block;
vertical-align: middle;
margin-right: 10px;
}
}
.icons-add-reorder {
justify-content: flex-end;
display: flex;
.add-icon {
padding-left: 10px;
cursor: pointer;
}
.reorder-icon {
cursor: pointer;
vertical-align: sub;
margin-right: 5px;
padding-right: 10px;
}
}
.error {

View File

@ -12,6 +12,10 @@
<span *ngIf="tx.merchant.pr.ca"><ion-icon class="fi-lock"></ion-icon> {{tx.merchant.domain}}</span>
<span *ngIf="!tx.merchant.pr.ca"><ion-icon class="fion-icon-unlock"></ion-icon> {{tx.merchant.domain}}</span>
</span>
<div class="copayer">
<span *ngIf="tx.creatorName">{{ tx.creatorName}}@</span>
<span>{{tx.wallet.name}}</span>
</div>
</div>
<ion-note item-end>

View File

@ -1,6 +1,8 @@
page-txp{
img {
width: 40px;
height: 40px;
border-radius: $icon-border-radius;
}
.label {
display: -webkit-inline-box;
@ -9,6 +11,10 @@ page-txp{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.copayer {
font-size: 12.5px;
font-weight: 300;
}
}
ion-note {
text-align: end;

View File

@ -1,65 +1,55 @@
<div class="wallet-activity">
<div *ngIf="notification.type == 'NewCopayer' && notification.wallet.n>1" translate>
Copayer joined
<button ion-item>
<ion-icon class="wallet-icon-container" item-start>
<div class="icon-color" [ngClass]="{'wallet-color-default': !notification.wallet.color}" [ngStyle]="{'background-color':notification.wallet.color}"></div>
</ion-icon>
<div *ngIf="notification.type == 'NewCopayer' && notification.wallet.n>1">
<span translate>Copayer joined</span>
</div>
<div *ngIf="notification.type == 'NewCopayer' && notification.wallet.n==1" translate>
Wallet created
<div *ngIf="notification.type == 'NewCopayer' && notification.wallet.n==1">
<span translate>Wallet created</span>
</div>
<div *ngIf="notification.type == 'NewOutgoingTx'">
<span translate>Payment Sent </span>
<div class="wallet-activity-amount">
{{notification.data.amount | satToUnit: notification.wallet.coin}}
</div>
<span translate>Payment Sent</span>
</div>
<div *ngIf="notification.type == 'NewIncomingTx'">
<span translate>Payment Received</span>
<div class="wallet-activity-amount">
{{notification.data.amount | satToUnit: notification.wallet.coin}}
</div>
</div>
<div *ngIf="notification.type == 'TxProposalRemoved'">
<span translate>Proposal Deleted</span>:
<b>{{notification.message}}</b>
<div class="wallet-activity-amount">
{{notification.data.amount | satToUnit: notification.wallet.coin}}:
</div>
</div>
<div *ngIf="notification.type == 'TxProposalRejectedBy'">
<span translate>Proposal Rejected</span>:
<b>{{notification.message}}</b>
<div class="wallet-activity-amount">
{{notification.data.amount | satToUnit: notification.wallet.coin}}:
</div>
</div>
<div *ngIf="notification.type == 'NewTxProposal'">
<span translate>New Proposal</span>:
<b>{{notification.message}}</b>
<div class="wallet-activity-amount">
{{notification.data.amount | satToUnit: notification.wallet.coin}}
</div>
</div>
<div *ngIf="notification.type == 'TxProposalAcceptedBy'">
<span translate>Proposal Accepted</span>:
<b>{{notification.message}}</b>
<div class="wallet-activity-amount">
{{notification.data.amount | satToUnit: notification.wallet.coin}}
</div>
</div>
<div class="wallet-activity-note">
<!-- {{notification.types}} -->
<div>
<span class="wallet-activity-note-child color" [ngClass]="{'wallet-color-default': !notification.wallet.color}" [ngStyle]="{'background-color':notification.wallet.color}"></span>
<span *ngIf="notification.creatorName" class="wallet-activity-note-child">{{ notification.creatorName}}@</span>
<span class="wallet-activity-note-child">{{notification.wallet.name}}</span>
</div>
<time class="wallet-activity-note-child">{{ notification.createdOn * 1000 | amTimeAgo}}</time>
<div class="copayer">
<span *ngIf="notification.creatorName">{{ notification.creatorName}}@</span>
<span>{{notification.wallet.name}}</span>
</div>
</div>
<ion-note item-end>
<!-- {{notification.types}} -->
<div class="amount">
{{notification.data.amount | satToUnit: notification.wallet.coin}}
</div>
<time class="date">{{ notification.createdOn * 1000 | amTimeAgo}}</time>
</ion-note>
</button>

View File

@ -2,39 +2,30 @@ page-wallet-activity {
.wallet-color-default {
background-color: color($colors, primary);
}
.wallet-activity {
&-not-pending {
opacity: 0.6;
filter: alpha(opacity=60); /* For IE8 and earlier */
}
&-amount {
float: right;
font-size: 18px;
@media(max-width: 320px) {
text-align: right;
}
}
&-note {
margin-top: 3px;
font-size: 12px!important;
display: flex;
justify-content: space-between;
&-child {
line-height: 30px;
vertical-align: middle;
display: inline-block;
}
.color {
width: 2rem;
height: 2rem;
border-radius: 2rem;
margin-right: 10px;
}
}
.copayer {
font-size: 12.5px;
font-weight: 300;
}
ion-note {
text-align: end;
.amount {
color: color($colors, dark);
}
.date {
font-size: 12.5px;
}
}
.wallet-icon-container {
width: 37px;
}
.icon-color {
width: 2rem;
height: 2rem;
border-radius: $txp-icon-border-radius;
margin: 8px auto;
}
}

View File

@ -5,7 +5,7 @@
</ion-header>
<ion-content>
<ion-content no-bounce>
<ion-list>
<ion-item>
<div class="sending-label">
@ -48,6 +48,8 @@
</ion-item>
</div>
<ion-item-divider color="light"></ion-item-divider>
<ion-item *ngIf="fiatFee && feeRatePerStr">
{{'Fee'|translate}}
<ion-note item-end>
@ -66,13 +68,11 @@
</ion-note>
</ion-item>
<ion-item-divider *ngIf="withdrawalStr" no-lines text-wrap>
A total of {{amountStr}} ({{fiatAmount | currency}} {{currencyIsoCode}}) will be exchanged for {{withdrawalStr}} ({{fiatWithdrawal | currency}} {{currencyIsoCode}}). Would you like to proceed?
</ion-item-divider>
</ion-list>
<p class="text-confirm" *ngIf="withdrawalStr">
A total of {{amountStr}} ({{fiatAmount | currency}} {{currencyIsoCode}}) will be exchanged for {{withdrawalStr}} ({{fiatWithdrawal
| currency}} {{currencyIsoCode}}). Would you like to proceed?
</p>
</ion-content>
<ion-footer *ngIf="fromWallet && toWallet && totalAmountStr">

View File

@ -12,7 +12,7 @@
</ion-header>
<ion-content>
<ion-content no-bounce>
<div class="header-modal">
<div class="title-modal">
<img src="assets/img/shapeshift/icon-shapeshift.svg" alt="ShapeShift" width="50"> {{ssData.title}}
@ -56,8 +56,8 @@
<ion-item-divider color="light">Deposit</ion-item-divider>
<ion-item>
Adddress
<ion-note item-end>
Address
<ion-note item-end text-wrap>
<span *ngIf="!ssData.address">...</span>
<span *ngIf="ssData.address" copy-to-clipboard="{{ssData.address}}">
{{ ssData.address }}
@ -78,8 +78,8 @@
<ion-item-divider color="light">Withdraw</ion-item-divider>
<ion-item>
Adddress
<ion-note item-end>
Address
<ion-note item-end text-wrap>
<span *ngIf="!ssData.withdrawal">...</span>
<span *ngIf="ssData.withdrawal" copy-to-clipboard="{{ssData.withdrawal}}">
{{ ssData.withdrawal }}
@ -99,11 +99,11 @@
<ion-item-divider color="light"></ion-item-divider>
<button ion-item *ngIf="ssData.transaction" (click)="openTransaction(ssData.transaction)">
<button ion-item detail-none *ngIf="ssData.transaction" (click)="openTransaction(ssData.transaction)">
See transaction
</button>
<ion-item-divider color="light"></ion-item-divider>
<button ion-item (click)="remove()">
<button class="assertive" ion-item detail-none (click)="remove()">
Remove
</button>

View File

@ -1,5 +1,6 @@
page-shapeshift-details {
.header-modal {
padding-top: 10px;
.title-modal {
font-size: 26px;
text-align: center;
@ -8,7 +9,7 @@ page-shapeshift-details {
}
}
.subtitle-modal {
font-size: 12px;
font-size: 14px;
text-align: center;
}
.status-modal {

View File

@ -73,6 +73,11 @@ export class ShapeshiftShiftPage {
return hasCachedFunds;
});
if (_.isEmpty(this.fromWallets)) {
this.showErrorAndBack(null, 'No wallets with funds'); // TODO: gettextCatalog
return;
}
this.onFromWalletSelect(this.fromWallets[0]);
}

View File

@ -1,10 +1,10 @@
<ion-header>
<ion-navbar>
<ion-title>shapeShift</ion-title>
<ion-title>ShapeShift</ion-title>
</ion-navbar>
</ion-header>
<ion-content *ngIf="!shifts.data">
<ion-content *ngIf="!shifts.data" no-bounce>
<div class="box-notification warning" *ngIf="network == 'testnet'">
Sandbox version. Only for testing purpose.
</div>
@ -17,53 +17,46 @@
<p>Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.</p>
</div>
<div class="integration-onboarding-cta">
<button ion-button no-low-fee (click)="goTo('Shift')">Start</button>
<button ion-button clear color="light" (click)="openExternalLink('https://shapeshift.io')">Visit Shapeshift.io &rarr;</button>
<button ion-button large outline block no-low-fee (click)="goTo('Shift')">Start</button>
<button ion-button clear small block color="light" (click)="openExternalLink('https://shapeshift.io')">Visit Shapeshift.io &rarr;</button>
</div>
</div>
</ion-content>
<ion-content *ngIf="shifts.data">
<ion-content *ngIf="shifts.data" no-bounce>
<div class="main-header" (click)="update()">
<img src="assets/img/shapeshift/logo-shapeshift.svg" width="200">
<div class="main-header">
<img src="assets/img/shapeshift/logo-shapeshift.svg" width="180" (click)="update()">
<button color="light" ion-button clear icon-right no-low-fee (click)="goTo('Shift')">
Shift
<ion-icon name="arrow-forward"></ion-icon>
</button>
</div>
<ion-list class="shift">
<ion-item-divider class="help" color="light">
<div>
<span>Having problems with a ShapeShift?</span>
<a (click)="openExternalLink('https://shapeshift.zendesk.com/hc/en-us/requests/new')">
Contact the ShapeShift support team.
</a>
</div>
</ion-item-divider>
<button class="shift-btn" ion-item no-low-fee (click)="goTo('Shift')">
<img src="assets/img/shapeshift/icon-shapeshift.svg">
<span>Shift</span>
</button>
</ion-list>
<ion-list>
<ion-item-divider color="light">
Transactions
</ion-item-divider>
<ion-list-header color="light">Transactions</ion-list-header>
<button ion-item *ngFor="let item of shifts.data | keys" (click)="openShiftModal(item.value)">
<div class="shapeshift-address">
<h2>
<span class="item-amount">{{ item.value.amount }}</span>
<span class="ellipsis">{{item.value.title || item.value.address}}</span>
</h2>
<ion-note>{{item.value.date | amTimeAgo}}</ion-note>
<span>
<ion-label>
<div class="ellipsis">{{item.value.title || item.value.address}}</div>
<div class="status">
<span class="assertive" *ngIf="item.value.status == 'failed'">Failed</span>
<span class="balanced" *ngIf="item.value.status == 'complete'">Completed</span>
<span class="dark" *ngIf="item.value.status == 'received'">Pending</span>
<span class="text-gray" *ngIf="item.value.status == 'no_deposits'">Pending</span>
</span>
<span class="royal" *ngIf="item.value.status == 'received'">Pending</span>
<span class="calm" *ngIf="item.value.status == 'no_deposits'">Pending</span>
</div>
</ion-label>
<div item-content text-end>
<div class="text-bold">{{ item.value.amount }}</div>
<div class="date calm">{{item.value.date | amTimeAgo}}</div>
</div>
</button>
</ion-list>
</ion-content>
<ion-footer *ngIf="shifts.data" class="shift-problems">
<span>Having problems with a ShapeShift?</span>
<a (click)="openExternalLink('https://shapeshift.zendesk.com/hc/en-us/requests/new')">
Contact the ShapeShift support team.
</a>
</ion-footer>

View File

@ -26,40 +26,24 @@ page-shapeshift {
&-cta {
position: absolute;
bottom: 5vh;
width: 100%;
button {
width: 85%;
max-width: 300px;
margin-left: auto;
margin-right: auto;
display: block;
}
}
}
.main-header {
height: 120px;
padding: 15px 0;
background: url('../assets/img/shapeshift/shapeshift_background.jpg') center center no-repeat #28394d;
text-align: center;
img {
padding-top: 20px;
display: block;
margin: 0 auto;
}
}
.shift {
margin: 0rem;
img {
width: 30px;
}
&-btn .label {
display: flex;
align-items: center;
}
.help .label {
text-align: center;
white-space: normal;
font-size: 12px;
span {
margin-right: 0.6rem;
}
}
.shift-problems {
padding: 10px;
text-align: center;
white-space: normal;
font-size: 12.5px;
}
.status, .date {
font-size: 12.5px;
}
}

View File

@ -2,7 +2,7 @@
<ion-navbar hideBackButton="true"></ion-navbar>
</ion-header>
<ion-content>
<ion-content no-bounce>
<img src="assets/img/app/onboarding/warning.svg" />
<h1 translate>No backup, no bitcoin.</h1>
<p translate>Since only you control your money, youll need to save your backup phrase in case this app is deleted.</p>

View File

@ -8,7 +8,7 @@
</ion-header>
<ion-content>
<ion-content no-bounce>
<h1 translate>Wallet Created</h1>
<div *ngIf="!showConfirmForm">

View File

@ -1,7 +1,7 @@
<ion-header no-border>
<ion-navbar hideBackButton="true"></ion-navbar>
</ion-header>
<ion-content>
<ion-content no-bounce>
<h1 *ngIf="resume" translate>Quick review!</h1>
<h1 *ngIf="!resume" translate>Almost done! Let's review.</h1>
<p translate>Bitcoin is different &ndash; it cannot be safely held with a bank or web service.</p>

View File

@ -1,4 +1,4 @@
<ion-content>
<ion-content no-bounce>
<div class="logo-tagline">
<img src='assets/img/app/logo-negative.svg' id="logo" />

View File

@ -10,7 +10,7 @@
</ion-header>
<ion-content>
<ion-content no-bounce>
<ion-slides pager="true" (ionSlideDidChange)="slideChanged()">
<ion-slide>
<h1 translate>Bitcoin is secure,

View File

@ -2,25 +2,17 @@
<ion-navbar>
<ion-title>{{'Send' | translate}}</ion-title>
<ion-buttons end>
<button *ngIf="hasBtcWallets || hasBchWallets" ion-button icon-only (click)="openScanner()">
<ion-icon class="icon-scanner" name="qr-scanner"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content no-bounce>
<div *ngIf="hasBtcWallets || hasBchWallets">
<ion-list>
<ion-list-header class="title">{{'Recipient' | translate}}</ion-list-header>
<ion-searchbar placeholder="Search or enter bitcoin address" [(ngModel)]="search" (ngModelChange)="findContact(search)"></ion-searchbar>
</ion-list>
<ion-searchbar placeholder="Search or enter bitcoin address" [(ngModel)]="search" (ngModelChange)="findContact(search)"></ion-searchbar>
<ion-list *ngIf="filteredContactsList && filteredContactsList[0]">
<ion-item-divider class="title">
<ion-list-header color="light">
<span translate>Transfer to Contact</span>
</ion-item-divider>
</ion-list-header>
<button ion-item *ngFor="let item of filteredContactsList" (click)="goToAmount(item)">
<ion-icon item-start>
<gravatar [name]="item.name" [width]="30" [email]="item.email"></gravatar>
@ -32,10 +24,10 @@
</button>
</ion-list>
<ion-list *ngIf="walletBtcList && walletBtcList[0]">
<ion-item-divider class="title">
<ion-list-header class="title" color="light">
<img src="assets/img/icon-bitcoin.svg" alt="Bitcoin Wallets" width="16" />
<span translate>Transfer to Bitcoin Wallet</span>
</ion-item-divider>
</ion-list-header>
<button ion-item *ngFor="let wallet of walletBtcList" (click)="goToAmount(wallet)">
<ion-icon item-start>
@ -51,10 +43,10 @@
</ion-list>
<ion-list *ngIf="walletBchList && walletBchList[0]">
<ion-item-divider class="title">
<ion-list-header class="title" color="light">
<img src="assets/img/bitcoin-cash-logo.svg" alt="Bitcoin Cash Wallets" width="22" />
<span translate>Transfer to Bitcoin Cash Wallet</span>
</ion-item-divider>
</ion-list-header>
<button ion-item *ngFor="let wallet of walletBchList" (click)="goToAmount(wallet)">
<ion-icon item-start>

View File

@ -4,13 +4,9 @@ page-send {
text-align: center;
}
.title {
color: color($colors, dark);
font-weight: 700;
img {
width: 22px;
display: inline-block;
vertical-align: middle;
margin-right: 10px;
vertical-align: sub;
margin-right: 5px;
}
}
.no-wallet-message {

View File

@ -142,10 +142,6 @@ export class SendPage {
});
}
public openScanner(): void {
this.navCtrl.parent.select(2);
}
public showMore(): void {
this.currentContactsPage++;
this.updateContactsList();

View File

@ -6,8 +6,7 @@
<ion-content no-bounce>
<ion-list>
<ion-item-divider color="light"></ion-item-divider>
<button ion-item (click)="openAddressBookPage()">
<button ion-item no-lines (click)="openAddressBookPage()">
<ion-icon name="ios-contacts-outline" item-start></ion-icon>
{{'Address book' | translate}}
</button>

View File

@ -4,7 +4,7 @@
</ion-navbar>
</ion-header>
<ion-content>
<ion-content no-bounce>
<ion-card *ngIf="btx">
<ion-card-content>
<div class="sending-label" *ngIf="btx.confirmations > 0">

View File

@ -11,7 +11,7 @@
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content no-bounce>
<ion-card>
<ion-card-content>

View File

@ -69,43 +69,48 @@ page-wallet-details {
.wallet-info {
font-weight: 500;
color: color($colors, light);
height: 2.5rem;
padding-left: 0.5rem;
span {
margin: 0 3px;
padding-top: 2px;
float: left;
}
height: 30px;
img {
float: left;
margin-right: 4px;
}
.testnet {
height: 2.5rem;
width: 2.5rem;
margin-top: 3px;
}
.testnet-text {
height: 2.5rem;
width: 4.5rem;
margin-right: 5px;
color: color($colors, light);
height: 3rem;
width: 5rem;
}
.read-only {
height: 2.5rem;
width: 1.5rem;
margin-right: 5px;
height: 3rem;
width: 2rem;
}
.read-only-text {
height: 2.5rem;
width: 5rem;
margin-right: 5px;
height: 3rem;
width: 6rem;
}
.auditable-text {
height: 2.5rem;
height: 3rem;
width: 5rem;
}
.custom-bws {
height: 3.2rem;
width: 3.2rem;
}
.wallet-type {
display: block;
float: left;
margin: 0 3px;
padding-top: 5px;
}
ion-spinner {
float: right;
width: 20px;
height: 20px;
margin: 5px 5px 0 0;
* {
stroke: color($colors, light);
}

View File

@ -1,12 +1,15 @@
import { Injectable } from '@angular/core';
import { LoadingController } from 'ionic-angular';
import { Logger } from '../../providers/logger/logger';
import * as _ from 'lodash';
@Injectable()
export class OnGoingProcessProvider {
private loading: any;
private processNames: any;
private pausedOngoingProcess: any;
private ongoingProcess: any;
constructor(
private loadingCtrl: LoadingController,
@ -55,20 +58,29 @@ export class OnGoingProcessProvider {
'topup': 'Top up in progress...',
'duplicatingWallet': 'Duplicating wallet...',
};
this.ongoingProcess = {};
}
public getShowName(processName: string): string {
let showName = this.processNames[processName] || processName;
return showName;
}
public clear() {
this.processNames = {};
private clear() {
this.ongoingProcess = {};
this.loading.dismiss();
};
}
public pause(): void {
this.pausedOngoingProcess = this.ongoingProcess;
this.clear();
}
public resume(): void {
_.forEach(this.pausedOngoingProcess, (v, k) => {
this.set(k, v);
});
this.pausedOngoingProcess = {};
}
public set(processName: string, isOn: boolean): string {
this.logger.debug('ongoingProcess', processName, isOn);
this.ongoingProcess[processName] = isOn;
let showName = this.processNames[processName] || processName;
if (!isOn) {
this.loading.dismiss();

View File

@ -117,7 +117,6 @@ export class FileStorage implements IStorage {
v = v.toString();
}
this.log.debug('Writing:', k, v);
fileWriter.write(v);
}, err => {
this.log.error('Could not create writer', err);

View File

@ -20,6 +20,7 @@ export class LocalStorage implements IStorage {
try {
parsed = JSON.parse(v);
} catch (e) {
//TODO parse is not necessary
}
resolve(parsed || v);
});

View File

@ -11,6 +11,8 @@ import { BwcErrorProvider } from '../bwc-error/bwc-error';
import { PlatformProvider } from '../platform/platform';
import { AppProvider } from '../../providers/app/app';
import { LanguageProvider } from '../../providers/language/language';
import { PopupProvider } from '../popup/popup';
import { OnGoingProcessProvider } from '../on-going-process/on-going-process';
//models
import { Profile } from '../../models/profile/profile.model';
@ -34,7 +36,9 @@ export class ProfileProvider {
private platformProvider: PlatformProvider,
private appProvider: AppProvider,
private languageProvider: LanguageProvider,
private events: Events
private events: Events,
private popupProvider: PopupProvider,
private onGoingProcessProvider: OnGoingProcessProvider
) {
this.throttledBwsEvent = _.throttle((n, wallet) => {
this.newBwsEvent(n, wallet);
@ -333,6 +337,60 @@ export class ProfileProvider {
});
}
// An alert dialog
private askPassword(name: string, title: string): Promise<any> {
return new Promise((resolve, reject) => {
let opts = {
type: 'password'
}
this.popupProvider.ionicPrompt(title, name, opts).then((res: any) => {
return resolve(res);
});
});
}
private showWarningNoEncrypt(): Promise<any> {
return new Promise((resolve, reject) => {
let title = 'Are you sure?'; //TODO gettextcatalog
let msg = 'Your wallet keys will be stored in plan text in this device, if an other app access the store it will be able to access your Bitcoin'; //TODO gettextcatalog
let okText = 'Yes'; //TODO gettextcatalog
let cancelText = 'No'; //TODO gettextcatalog
this.popupProvider.ionicConfirm(title, msg, okText, cancelText).then((res: any) => {
return resolve(res);
});
});
}
private encrypt(wallet: any): Promise<any> {
return new Promise((resolve, reject) => {
let title = 'Please enter a password to encrypt your wallet keys on this device storage'; //TODO gettextcatalog
let warnMsg = 'Your wallet key will be encrypted. The Spending Password cannot be recovered. Be sure to write it down.'; //TODO gettextcatalog
this.askPassword(warnMsg, title).then((password: string) => {
if (!password) {
this.showWarningNoEncrypt().then((res: any) => {
if (res) return resolve(); //TODO gettextcatalog
this.encrypt(wallet).then(() => {
return resolve();
});
});
}
else {
title = 'Confirm your new spending password'; //TODO gettextcatalog
this.askPassword(warnMsg, title).then((password2: string) => {
if (!password2 || password != password2) {
this.encrypt(wallet).then(() => {
return resolve();
});
} else {
wallet.encryptPrivateKey(password);
return resolve();
}
});
}
});
});
}
// Adds and bind a new client to the profile
private addAndBindWalletClient(wallet: any, opts: any): Promise<any> {
return new Promise((resolve, reject) => {
@ -340,40 +398,45 @@ export class ProfileProvider {
return reject('Could not access wallet'); // TODO gettextCatalog
}
let walletId: string = wallet.credentials.walletId
// Encrypt wallet
this.onGoingProcessProvider.pause();
this.encrypt(wallet).then(() => {
this.onGoingProcessProvider.resume();
if (!this.profile.addWallet(JSON.parse(wallet.export()))) {
return reject("Wallet already in " + this.appProvider.info.nameCase); // TODO gettextCatalog
}
let walletId: string = wallet.credentials.walletId
if (!this.profile.addWallet(JSON.parse(wallet.export()))) {
return reject("Wallet already in " + this.appProvider.info.nameCase); // TODO gettextCatalog
}
let skipKeyValidation: boolean = this.shouldSkipValidation(walletId);
if (!skipKeyValidation)
this.runValidation(wallet);
let skipKeyValidation: boolean = this.shouldSkipValidation(walletId);
if (!skipKeyValidation)
this.runValidation(wallet);
this.bindWalletClient(wallet);
this.bindWalletClient(wallet);
let saveBwsUrl = (): Promise<any> => {
return new Promise((resolve, reject) => {
let defaults: any = this.configProvider.getDefaults();
let bwsFor: any = {};
bwsFor[walletId] = opts.bwsurl || defaults.bws.url;
let saveBwsUrl = (): Promise<any> => {
return new Promise((resolve, reject) => {
let defaults: any = this.configProvider.getDefaults();
let bwsFor: any = {};
bwsFor[walletId] = opts.bwsurl || defaults.bws.url;
// Dont save the default
if (bwsFor[walletId] == defaults.bws.url) {
// Dont save the default
if (bwsFor[walletId] == defaults.bws.url) {
return resolve();
}
this.configProvider.set({ bwsFor: bwsFor });
return resolve();
}
});
};
this.configProvider.set({ bwsFor: bwsFor });
return resolve();
});
};
saveBwsUrl().then(() => {
this.persistenceProvider.storeProfile(this.profile).then(() => {
return resolve(wallet);
}).catch((err: any) => {
return reject(err);
saveBwsUrl().then(() => {
this.persistenceProvider.storeProfile(this.profile).then(() => {
return resolve(wallet);
}).catch((err: any) => {
return reject(err);
});
});
});
});

View File

@ -105,10 +105,9 @@ export class ShapeshiftProvider {
}
public getShapeshift(cb) {
var network = this.getNetwork();
let network = this.getNetwork();
this.persistenceProvider.getShapeshift(network).then((ss: any) => {
var _gcds = ss ? JSON.parse(ss) : null;
return cb(null, _gcds);
return cb(null, ss);
}).catch((err: any) => {
return cb(err, null);
});

View File

@ -16,12 +16,6 @@ import { OnGoingProcessProvider } from '../on-going-process/on-going-process';
import { TouchIdProvider } from '../touchid/touchid';
import { FeeProvider } from '../fee/fee';
/* TODO LIST:
- onGoingProcess provider
*/
@Injectable()
export class WalletProvider {
@ -1089,13 +1083,11 @@ export class WalletProvider {
let opts = {
type: 'password'
}
this.popupProvider.ionicPrompt(title, name, opts, null, null).then((res: any) => {
this.popupProvider.ionicPrompt(title, name, opts).then((res: any) => {
return resolve(res);
}).catch((err: any) => {
return reject(err);
});
});
};
}
public encrypt(wallet: any): Promise<any> {
return new Promise((resolve, reject) => {
@ -1115,8 +1107,7 @@ export class WalletProvider {
return reject(err);
});
});
};
}
public decrypt(wallet: any): Promise<any> {
return new Promise((resolve, reject) => {

View File

@ -96,10 +96,6 @@ $list-md-border-color: color($colors, light);
/* Ionic Overrides and Workarounds */
// Please include a description of the problem solved by the workaround.
ion-navbar.hide {
display: block !important;
}
// Hide scrollbars on platforms which don't match iOS, Android, or Windows
// (So scroll bars are not visible in Chrome, NW.js, or Electron.)
.platform-core .scroll-content {