diff --git a/src/pages/send/amount/amount.html b/src/pages/send/amount/amount.html index cf949817e..3c7220243 100644 --- a/src/pages/send/amount/amount.html +++ b/src/pages/send/amount/amount.html @@ -10,57 +10,58 @@ Recipient - - - {{ address }} - - -
-
-
- Amount -
-
- {{ amount || "0.00" }} -
-
- = {{globalResult|| "0.00" }} -
+ + + {{ toAddress }} + + +
+
+
+ Amount
-
-
-
- -
-
-
-
7
-
8
-
9
-
÷
-
- -
-
4
-
5
-
6
-
×
-
- -
-
1
-
2
-
3
-
+
-
- -
-
.
-
0
-
-
-
-
+
+ {{ amountStr || "0.00" }} +
+
+ = {{globalResult|| "0.00" }}
- +
+
+
+ +
+
+
+
7
+
8
+
9
+
÷
+
+ +
+
4
+
5
+
6
+
×
+
+ +
+
1
+
2
+
3
+
+
+
+ +
+
.
+
0
+
+ +
+
-
+
+
+
+ \ No newline at end of file diff --git a/src/pages/send/amount/amount.ts b/src/pages/send/amount/amount.ts index 1701840a5..d40429d2e 100644 --- a/src/pages/send/amount/amount.ts +++ b/src/pages/send/amount/amount.ts @@ -1,6 +1,18 @@ import { Component, HostListener } from '@angular/core'; import { NavController, NavParams } from 'ionic-angular'; +import { Logger } from '@nsalaun/ng-logger'; import * as _ from 'lodash'; + +//providers +import { ProfileProvider } from '../../../providers/profile/profile'; +import { ConfigProvider } from '../../../providers/config/config'; +import { PlatformProvider } from '../../../providers/platform/platform'; +import { NodeWebkitProvider } from '../../../providers/node-webkit/node-webkit'; +import { RateProvider } from '../../../providers/rate/rate'; +import { Filter } from '../../../providers/filter/filter'; +import { TxFormatProvider } from '../../../providers/tx-format/tx-format'; + +//pages import { ConfirmPage } from '../confirm/confirm'; import { CustomAmountPage } from '../../receive/custom-amount/custom-amount'; @@ -10,12 +22,21 @@ import { CustomAmountPage } from '../../receive/custom-amount/custom-amount'; }) export class AmountPage { - public address: string; - public amount: string; + public amountStr: string = ''; public smallFont: boolean; public allowSend: boolean; public globalResult: string; public fromSend: boolean; + public unit: string; + public alternativeUnit: string; + public recipientType: string; + public toAddress: string; + public name: string; + public email: string; + public color: string; + public amount: number; + public showSendMax: boolean = false; + public useSendMax: boolean; private LENGTH_EXPRESSION_LIMIT = 19; private SMALL_FONT_SIZE_LIMIT = 10; @@ -23,18 +44,135 @@ export class AmountPage { private unitIndex: number = 0; private reNr: RegExp = /^[1234567890\.]$/; private reOp: RegExp = /^[\*\+\-\/]$/; + private fiatCode: string; + private altUnitIndex: number = 0; + private fixedUnit: boolean; + private _id: number; + private nextStep: string; - constructor(public navCtrl: NavController, public navParams: NavParams) { - this.amount = ''; + // Config Related values + public config: any; + public walletConfig: any; + public unitToSatoshi: number; + public unitDecimals: number; + public satToUnit: number; + public configFeeLevel: string; + public satToBtc: number; + + constructor( + public navCtrl: NavController, + public navParams: NavParams, + public profileProvider: ProfileProvider, + private configProvider: ConfigProvider, + private logger: Logger, + private platformProvider: PlatformProvider, + private nodeWebkitProvider: NodeWebkitProvider, + private rateProvider: RateProvider, + private filter: Filter, + private txFormatProvider: TxFormatProvider + ) { this.allowSend = false; + this.config = this.configProvider.get(); + this.walletConfig = this.config.wallet; + this.unitToSatoshi = this.walletConfig.settings.unitToSatoshi; + this.unitDecimals = this.walletConfig.settings.unitDecimals; + this.satToUnit = 1 / this.unitToSatoshi; + this.satToBtc = 1 / 100000000; + this.configFeeLevel = this.walletConfig.settings.feeLevel ? this.walletConfig.settings.feeLevel : 'normal'; } ionViewDidLoad() { console.log('Params', this.navParams.data); - this.address = this.navParams.data.address; + this.toAddress = this.navParams.data.toAddress; this.fromSend = this.navParams.data.fromSend; + this._id = this.navParams.data.id; + this.nextStep = this.navParams.data.nextStep; + this.recipientType = this.navParams.data.recipientType || null; + this.name = this.navParams.data.name; + this.email = this.navParams.data.email; + this.color = this.navParams.data.color; + this.amount = this.navParams.data.amount; } + ionViewDidEnter() { + this.setAvailableUnits(); + this.updateUnitUI(); + //this.showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' || $ionicHistory.backView().stateName == 'tabs.bitpayCard'); TODO + if (!this.nextStep && !this.toAddress) { + this.logger.error('Bad params at amount') + throw ('bad params'); + } + // in SAT ALWAYS + if (this.amount) { + this.amountStr = ((this.amount) * this.satToUnit).toFixed(this.unitDecimals); + } + this.processAmount(); + } + + private paste(value: string): void { + this.amountStr = value; + this.processAmount(); + }; + + public processClipboard(): void { + if (!this.platformProvider.isNW) return; + var value = this.nodeWebkitProvider.readFromClipboard(); + if (value && this.evaluate(value) > 0) this.paste(this.evaluate(value)); + }; + + public showSendMaxMenu(): void { + this.showSendMax = true; + } + + public sendMax(): void { + this.showSendMax = false; + this.useSendMax = true; + this.finish(); + }; + + public toggleAlternative(): void { + if (this.amountStr && this.isExpression(this.amountStr)) { + let amount = this.evaluate(this.format(this.amountStr)); + this.globalResult = '= ' + this.processResult(amount); + } + }; + + public changeUnit(): void { + if (this.fixedUnit) return; + + this.unitIndex++; + if (this.unitIndex >= this.availableUnits.length) this.unitIndex = 0; + + + if (this.availableUnits[this.unitIndex].isFiat) { + // Always return to BTC... TODO? + this.altUnitIndex = 0; + } else { + this.altUnitIndex = _.findIndex(this.availableUnits, { + isFiat: true + }); + } + + this.updateUnitUI(); + }; + + public changeAlternativeUnit(): void { + + // Do nothing is fiat is not main unit + if (!this.availableUnits[this.unitIndex].isFiat) return; + + var nextCoin = _.findIndex(this.availableUnits, function (x) { + if (x.isFiat) return false; + if (x.id == this.availableUnits[this.altUnitIndex].id) return false; + return true; + }); + + if (nextCoin >= 0) { + this.altUnitIndex = nextCoin; + this.updateUnitUI(); + } + }; + @HostListener('document:keydown', ['$event']) handleKeyboardEvent(event: KeyboardEvent) { if (!event.key) return; if (event.which === 8) { @@ -51,26 +189,26 @@ export class AmountPage { } else if (event.keyCode === 13) this.finish(); } - pushDigit(digit: string) { - if (this.amount && this.amount.length >= this.LENGTH_EXPRESSION_LIMIT) return; - if (this.amount.indexOf('.') > -1 && digit == '.') return; + public pushDigit(digit: string): void { + if (this.amountStr && this.amountStr.length >= this.LENGTH_EXPRESSION_LIMIT) return; + if (this.amountStr.indexOf('.') > -1 && digit == '.') return; // TODO: next line - Need: isFiat - //if (this.availableUnits[this.unitIndex].isFiat && this.amount.indexOf('.') > -1 && this.amount[this.amount.indexOf('.') + 2]) return; + //if (this.availableUnits[this.unitIndex].isFiat && this.amountStr.indexOf('.') > -1 && this.amountStr[this.amountStr.indexOf('.') + 2]) return; - this.amount = (this.amount + digit).replace('..', '.'); + this.amountStr = (this.amountStr + digit).replace('..', '.'); this.checkFontSize(); this.processAmount(); }; - removeDigit() { - this.amount = (this.amount).toString().slice(0, -1); + public removeDigit(): void { + this.amountStr = (this.amountStr).toString().slice(0, -1); this.processAmount(); this.checkFontSize(); }; - pushOperator(operator: string) { - if (!this.amount || this.amount.length == 0) return; - this.amount = this._pushOperator(this.amount, operator); + public pushOperator(operator: string): void { + if (!this.amountStr || this.amountStr.length == 0) return; + this.amountStr = this._pushOperator(this.amountStr, operator); }; private _pushOperator(val: string, operator: string) { @@ -81,27 +219,27 @@ export class AmountPage { } }; - isOperator(val: string) { + private isOperator(val: string): boolean { const regex = /[\/\-\+\x\*]/; return regex.test(val); }; - isExpression(val: string) { + private isExpression(val: string): boolean { const regex = /^\.?\d+(\.?\d+)?([\/\-\+\*x]\d?\.?\d+)+$/; return regex.test(val); }; - checkFontSize() { - if (this.amount && this.amount.length >= this.SMALL_FONT_SIZE_LIMIT) this.smallFont = true; + private checkFontSize(): void { + if (this.amountStr && this.amountStr.length >= this.SMALL_FONT_SIZE_LIMIT) this.smallFont = true; else this.smallFont = false; }; - processAmount() { - var formatedValue = this.format(this.amount); + private processAmount(): void { + var formatedValue = this.format(this.amountStr); var result = this.evaluate(formatedValue); this.allowSend = _.isNumber(result) && +result > 0; if (_.isNumber(result)) { - this.globalResult = this.isExpression(this.amount) ? '= ' + this.processResult(result) : ''; + this.globalResult = this.isExpression(this.amountStr) ? '= ' + this.processResult(result) : ''; // TODO this.globalResult is always undefinded - Need: processResult() /* if (this.availableUnits[this.unitIndex].isFiat) { @@ -141,40 +279,139 @@ export class AmountPage { return result; }; - processResult(val: number) { - // TODO: implement this function correctly - Need: txFormatService, isFiat, $filter - console.log("processResult TODO"); - /*if (this.availableUnits[this.unitIndex].isFiat) return $filter('formatFiatAmount')(val); - else return txFormatService.formatAmount(val.toFixed(unitDecimals) * unitToSatoshi, true);*/ + private processResult(val: number): number { + if (this.availableUnits[this.unitIndex].isFiat) return this.filter.formatFiatAmount(val); + else return this.txFormatProvider.formatAmount(parseInt(val.toFixed(this.unitDecimals)) * this.unitToSatoshi, true); }; - fromFiat(val: number) { - // TODO: implement next line correctly - Need: rateService - //return parseFloat((rateService.fromFiat(val, fiatCode, availableUnits[altUnitIndex].id) * satToUnit).toFixed(unitDecimals)); + private fromFiat(val: number): number { + return parseFloat((this.rateProvider.fromFiat(val, this.fiatCode, this.availableUnits[this.altUnitIndex].id) * this.satToUnit).toFixed(this.unitDecimals)); }; - toFiat(val) { - // TODO: implement next line correctly - Need: rateService - /*if (!rateService.getRate(fiatCode)) return; - return parseFloat((rateService.toFiat(val * unitToSatoshi, fiatCode, availableUnits[unitIndex].id)).toFixed(2));*/ + private toFiat(val): number { + if (!this.rateProvider.getRate(this.fiatCode)) return; + return parseFloat((this.rateProvider.toFiat(val * this.unitToSatoshi, this.fiatCode, this.availableUnits[this.unitIndex].id)).toFixed(2)); }; - finish() { - let data: any = { - recipientType: null, - amount: this.globalResult, - address: this.address, - toName: null, - toEmail: null, - toColor: 'red', - coin: 'btc', - useSendMax: false + public finish(): void { + + let unit = this.availableUnits[this.unitIndex]; + let _amount = this.evaluate(this.format(this.amountStr)); + var coin = unit.id; + if (unit.isFiat) { + coin = this.availableUnits[this.altUnitIndex].id; } - if (this.fromSend) { - this.navCtrl.push(ConfirmPage, data); + + if (this.nextStep) { + + this.navCtrl.push(this.nextStep, { + id: this._id, + amount: this.useSendMax ? null : _amount, + currency: unit.id.toUpperCase(), + coin: coin, + useSendMax: this.useSendMax + }); } else { - console.log("To do"); - this.navCtrl.push(CustomAmountPage, data); + let amount = _amount; + + if (unit.isFiat) { + amount = (this.fromFiat(amount) * this.unitToSatoshi).toFixed(0); + } else { + amount = (amount * this.unitToSatoshi).toFixed(0); + } + + let data: any = { + recipientType: this.recipientType, + amount: this.globalResult, + toAddress: this.toAddress, + name: this.name, + email: this.email, + color: this.color, + coin: coin, + useSendMax: this.useSendMax + } + this.navCtrl.push(ConfirmPage, data); } } + + + private setAvailableUnits(): void { + + let hasBTCWallets = this.profileProvider.getWallets({ + coin: 'btc' + }).length; + + if (hasBTCWallets) { + this.availableUnits.push({ + name: 'Bitcoin', + id: 'btc', + shortName: 'BTC', + }); + } + + let hasBCHWallets = this.profileProvider.getWallets({ + coin: 'bch' + }).length; + + if (hasBCHWallets) { + this.availableUnits.push({ + name: 'Bitcoin Cash', + id: 'bch', + shortName: 'BCH', + }); + }; + + let unitIndex = 0; + if (this.navParams.data.coin) { + var coins = this.navParams.data.coin.split(','); + var newAvailableUnits = []; + + _.each(coins, (c) => { + var coin = _.find(this.availableUnits, { + id: c + }); + if (!coin) { + this.logger.warn('Could not find desired coin:' + this.navParams.data.coin) + } else { + newAvailableUnits.push(coin); + } + }); + + if (newAvailableUnits.length > 0) { + this.availableUnits = newAvailableUnits; + } + } + // currency have preference + let fiatName; + if (this.navParams.data.currency) { + this.fiatCode = this.navParams.data.currency; + this.altUnitIndex = unitIndex + unitIndex = this.availableUnits.length; + } else { + this.fiatCode = this.config.alternativeIsoCode || 'USD'; + fiatName = this.config.alternanativeName || this.fiatCode; + this.altUnitIndex = this.availableUnits.length; + } + + this.availableUnits.push({ + name: fiatName || this.fiatCode, + // TODO + id: this.fiatCode, + shortName: this.fiatCode, + isFiat: true, + }); + + if (this.navParams.data.fixedUnit) { + this.fixedUnit = true; + } + } + + private updateUnitUI(): void { + this.unit = this.availableUnits[this.unitIndex].shortName; + this.alternativeUnit = this.availableUnits[this.altUnitIndex].shortName; + + this.processAmount(); + this.logger.debug('Update unit coin @amount unit:' + this.unit + " alternativeUnit:" + this.alternativeUnit); + }; + } diff --git a/src/pages/send/confirm/confirm.ts b/src/pages/send/confirm/confirm.ts index 2fa4cfa1e..0678da53f 100644 --- a/src/pages/send/confirm/confirm.ts +++ b/src/pages/send/confirm/confirm.ts @@ -30,7 +30,7 @@ import { TxConfirmNotificationProvider } from '../../../providers/tx-confirm-not export class ConfirmPage { public data: any; - public address: string; + public toAddress: string; public amount: string; public coin: string; @@ -96,7 +96,7 @@ export class ConfirmPage { ionViewDidLoad() { console.log('ionViewDidLoad ConfirmPage'); this.data = this.navParams.data; - this.address = this.navParams.data.address; + this.toAddress = this.navParams.data.toAddress; this.amount = this.navParams.data.amount; this.coin = this.navParams.data.coin; } @@ -106,7 +106,7 @@ export class ConfirmPage { let B = this.coin == 'bch' ? this.bwcProvider.getBitcoreCash() : this.bwcProvider.getBitcore(); let networkName; try { - networkName = (new B.Address(this.address)).network.name; + networkName = (new B.Address(this.toAddress)).network.name; } catch (e) { let message = 'Copay only supports Bitcoin Cash using new version numbers addresses'; // TODO gettextCatalog let backText = 'Go back'; // TODO gettextCatalog @@ -124,9 +124,9 @@ export class ConfirmPage { // Grab stateParams let tx: any = { - toAmount: parseFloat(this.navParams.data.amount) * 100000000, // TODO review this line '* 100000000' convert satoshi to BTC + amount: parseFloat(this.navParams.data.amount) * 100000000, // TODO review this line '* 100000000' convert satoshi to BTC sendMax: this.navParams.data.useSendMax == 'true' ? true : false, - toAddress: this.navParams.data.address, + toAddress: this.navParams.data.toAddress, description: this.navParams.data.description, paypro: this.navParams.data.paypro, @@ -135,9 +135,9 @@ export class ConfirmPage { // Vanity tx info (not in the real tx) recipientType: this.navParams.data.recipientType || null, - toName: this.navParams.data.toName, - toEmail: this.navParams.data.toEmail, - toColor: this.navParams.data.toColor, + name: this.navParams.data.name, + email: this.navParams.data.email, + color: this.navParams.data.color, network: networkName, coin: this.navParams.data.coin, txp: {}, @@ -151,7 +151,7 @@ export class ConfirmPage { this.walletSelectorTitle = 'Send from'; // TODO gettextCatalog - this.setWalletSelector(tx.coin, tx.network, tx.toAmount).then(() => { + this.setWalletSelector(tx.coin, tx.network, tx.amount).then(() => { if (this.wallets.length > 1) { this.showWalletSelector(); } else if (this.wallets.length) { @@ -323,13 +323,13 @@ export class ConfirmPage { this.tx = tx; let updateAmount = (): void => { - if (!tx.toAmount) return; + if (!tx.amount) return; // Amount - tx.amountStr = this.txFormatProvider.formatAmountStr(wallet.coin, tx.toAmount); + tx.amountStr = this.txFormatProvider.formatAmountStr(wallet.coin, tx.amount); tx.amountValueStr = tx.amountStr.split(' ')[0]; tx.amountUnitStr = tx.amountStr.split(' ')[1]; - this.txFormatProvider.formatAlternativeStr(wallet.coin, tx.toAmount).then((v: string) => { + this.txFormatProvider.formatAlternativeStr(wallet.coin, tx.amount).then((v: string) => { tx.alternativeAmountStr = v; }); } @@ -363,7 +363,7 @@ export class ConfirmPage { } tx.sendMaxInfo = sendMaxInfo; - tx.toAmount = tx.sendMaxInfo.amount; + tx.amount = tx.sendMaxInfo.amount; updateAmount(); this.onGoingProcessProvider.set('calculatingFee', false); setTimeout(() => { @@ -462,7 +462,7 @@ export class ConfirmPage { return reject(msg); } - if (tx.toAmount > Number.MAX_SAFE_INTEGER) { + if (tx.amount > Number.MAX_SAFE_INTEGER) { let msg = 'Amount too big'; // TODO gettextCatalog this.logger.warn(msg); this.setSendError(msg); @@ -473,7 +473,7 @@ export class ConfirmPage { txp.outputs = [{ 'toAddress': tx.toAddress, - 'amount': tx.toAmount, + 'amount': tx.amount, 'message': tx.description }]; @@ -485,7 +485,7 @@ export class ConfirmPage { txp.feePerKb = tx.feeRate; } else txp.feeLevel = tx.feeLevel; } - + txp.feeLevel = 'normal'; txp.message = tx.description; if (tx.paypro) { @@ -493,6 +493,7 @@ export class ConfirmPage { } txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; txp.dryRun = dryRun; + this.walletProvider.createTx(wallet, txp).then((ctxp: any) => { return resolve(ctxp); }).catch((err: any) => { diff --git a/src/pages/send/send.ts b/src/pages/send/send.ts index a94327314..6b3d4bbb6 100644 --- a/src/pages/send/send.ts +++ b/src/pages/send/send.ts @@ -157,13 +157,13 @@ export class SendPage { this.popupProvider.ionicAlert('Error - no address'); return; } - this.logger.debug('Got toAddress:' + addr + ' | ' + item.name); + this.logger.debug('Got address:' + addr + ' | ' + item.name); this.navCtrl.push(AmountPage, { recipientType: item.recipientType, - address: addr, - toName: item.name, - toEmail: item.email, - toColor: item.color, + toAddress: addr, + name: item.name, + email: item.email, + color: item.color, coin: item.coin, fromSend: true }); diff --git a/src/providers/incoming-data/incoming-data.ts b/src/providers/incoming-data/incoming-data.ts index ac9f40077..646910055 100644 --- a/src/providers/incoming-data/incoming-data.ts +++ b/src/providers/incoming-data/incoming-data.ts @@ -264,23 +264,23 @@ export class IncomingDataProvider { this.navCtrl.push(SendPage, {}); if (amount) { this.navCtrl.push(ConfirmPage, { - toAmount: amount, - address: addr, + amount: amount, + toAddress: addr, description: message, coin: coin }); } else { this.navCtrl.push(AmountPage, { - address: addr, + toAddress: addr, coin: coin }); } } - private goToAmountPage(address: string, coin?: string) { + private goToAmountPage(toAddress: string, coin?: string) { let fromSend = this.navCtrl.getActive().name === 'SendPage'; this.navCtrl.push(AmountPage, { - address: address, + toAddress: toAddress, coin: coin, fromSend: fromSend }); @@ -288,8 +288,8 @@ export class IncomingDataProvider { private handlePayPro(payProDetails: any, coin?: string): void { var stateParams = { - toAmount: payProDetails.amount, - address: payProDetails.toAddress, + amount: payProDetails.amount, + toAddress: payProDetails.toAddress, description: payProDetails.memo, paypro: payProDetails, coin: coin, diff --git a/src/providers/rate/rate.ts b/src/providers/rate/rate.ts index 5f0d0c0a9..1aa2c69b3 100644 --- a/src/providers/rate/rate.ts +++ b/src/providers/rate/rate.ts @@ -16,7 +16,7 @@ export class RateProvider { private rateServiceUrl = 'https://bitpay.com/api/rates'; private bchRateServiceUrl = 'https://api.kraken.com/0/public/Ticker?pair=BCHUSD,BCHEUR'; - + constructor(public http: Http) { console.log('Hello RateProvider Provider'); this._rates = {}; @@ -28,7 +28,7 @@ export class RateProvider { } updateRates(): Promise { - return new Promise ((resolve, reject) => { + return new Promise((resolve, reject) => { let self = this; this.getBTC().then((dataBTC) => { @@ -44,23 +44,23 @@ export class RateProvider { this.getBCH().then((dataBCH) => { _.each(dataBCH.result, (data, paircode) => { - var code = paircode.substr(3,3); - var rate =data.c[0]; + var code = paircode.substr(3, 3); + var rate = data.c[0]; self._ratesBCH[code] = rate; }); this._isAvailable = true; resolve(); }) - .catch((errorBCH) => { - console.log("Error: ", errorBCH); - reject(errorBCH); - }); + .catch((errorBCH) => { + console.log("Error: ", errorBCH); + reject(errorBCH); + }); }) - .catch((errorBTC) => { - console.log("Error: ", errorBTC); - reject(errorBTC); - }); + .catch((errorBTC) => { + console.log("Error: ", errorBTC); + reject(errorBTC); + }); }); } @@ -78,17 +78,17 @@ export class RateProvider { .catch((error) => console.log("Error", error)); } - getRate(code, chain) { + getRate(code, chain?) { if (chain == 'bch') return this._ratesBCH[code]; else return this._rates[code]; }; - + getAlternatives() { return this._alternatives; }; - + toFiat(satoshis, code, chain) { return satoshis * this.SAT_TO_BTC * this.getRate(code, chain); }; @@ -99,7 +99,7 @@ export class RateProvider { listAlternatives(sort: boolean) { var self = this; - + var alternatives = _.map(this.getAlternatives(), (item) => { return { name: item.name, @@ -107,7 +107,7 @@ export class RateProvider { } }); if (sort) { - alternatives.sort( (a, b) => { + alternatives.sort((a, b) => { return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1; }); } @@ -115,11 +115,11 @@ export class RateProvider { }; //TODO IMPROVE WHEN AVAILABLE - whenAvailable() { - return new Promise((resolve, reject)=> { + whenAvailable() { + return new Promise((resolve, reject) => { if (this._isAvailable) resolve(); else { - this.updateRates().then(()=>{ + this.updateRates().then(() => { resolve(); }); }