From decc9e9dbab2717bb5da7227aaa8c9768b3e65d3 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 27 Aug 2014 14:20:13 -0300 Subject: [PATCH 01/16] Mocked up usd <-> alternative --- js/controllers/send.js | 39 +++++++++++++++++++++++++++++++++++++++ views/send.html | 16 ++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/js/controllers/send.js b/js/controllers/send.js index 87208e276..0dea1743b 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -6,9 +6,48 @@ angular.module('copayApp.controllers').controller('SendController', $scope.title = 'Send'; $scope.loading = false; var satToUnit = 1 / config.unitToSatoshi; + var configAlternativeToSatoshi = (1 / 512 / 1e-8); + var satToAlternative = 1 / configAlternativeToSatoshi; // TODO: Change $scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit; $scope.unitToBtc = config.unitToSatoshi / bitcore.util.COIN; $scope.minAmount = config.limits.minAmountSatoshi * satToUnit; + $scope.minAlternativeAmount = config.limits.minAmountSatoshi * satToAlternative; + $rootScope.alternativeName = 'Dollars'; + $rootScope.alternativeShort = 'USD'; + + $scope._amount = 0; + $scope._alternative = 0; +// Mockup + var alternativeToUnit = function(val) { + return val * configAlternativeToSatoshi * satToUnit; + }; + var unitToAlternative = function(val) { + return val * config.unitToSatoshi * satToAlternative; + }; + Object.defineProperty($scope, + "alternative", { + get: function () { + return this._alternative; + }, + set: function (newValue) { + this._alternative = newValue; + this._amount = alternativeToUnit(this._alternative); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty($scope, + "amount", { + get: function () { + return this._amount; + }, + set: function (newValue) { + this._amount = newValue; + this._alternative = unitToAlternative(this._amount); + }, + enumerable: true, + configurable: true + }); $scope.loadTxs = function() { var opts = { diff --git a/views/send.html b/views/send.html index a2d82be0d..7ce6aa6d2 100644 --- a/views/send.html +++ b/views/send.html @@ -83,6 +83,22 @@ +
+
+ +
+ +
+
+ {{$root.alternativeShort}} +
+
+
From 3da033cb06b9a2f595037d565252553782cb8eb5 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 27 Aug 2014 16:01:25 -0300 Subject: [PATCH 02/16] Add RateService to get info about fiat currencies --- js/controllers/send.js | 71 +++++++++++++++++++++++++----------------- js/services/rate.js | 40 ++++++++++++++++++++++++ views/send.html | 6 ++-- 3 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 js/services/rate.js diff --git a/js/controllers/send.js b/js/controllers/send.js index 0dea1743b..7fd8bebd8 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -2,7 +2,7 @@ var bitcore = require('bitcore'); angular.module('copayApp.controllers').controller('SendController', - function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils) { + function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils, rateService) { $scope.title = 'Send'; $scope.loading = false; var satToUnit = 1 / config.unitToSatoshi; @@ -12,41 +12,56 @@ angular.module('copayApp.controllers').controller('SendController', $scope.unitToBtc = config.unitToSatoshi / bitcore.util.COIN; $scope.minAmount = config.limits.minAmountSatoshi * satToUnit; $scope.minAlternativeAmount = config.limits.minAmountSatoshi * satToAlternative; +// Mockup $rootScope.alternativeName = 'Dollars'; - $rootScope.alternativeShort = 'USD'; + $rootScope.alternativeIsoCode = 'USD'; + config.unitDecimals = 2; + this.rateService = rateService; $scope._amount = 0; $scope._alternative = 0; -// Mockup - var alternativeToUnit = function(val) { - return val * configAlternativeToSatoshi * satToUnit; - }; - var unitToAlternative = function(val) { - return val * config.unitToSatoshi * satToAlternative; + this.amountFilter = function(val) { + if (val) { + return val.toFixed(config.unitDecimals); + } }; + this.fiatFilter = function(val) { + if (val) { + return val.toFixed(2); + } + } + Object.defineProperty($scope, - "alternative", { - get: function () { - return this._alternative; - }, - set: function (newValue) { - this._alternative = newValue; - this._amount = alternativeToUnit(this._alternative); - }, - enumerable: true, - configurable: true + "alternative", { + get: function () { + return this._alternative; + }, + set: function (newValue) { + this._alternative = newValue; + if (typeof(newValue) === 'number') { + this._amount = -(-( + rateService.fromFiat(newValue, $rootScope.alternativeIsoCode) * satToUnit + ).toFixed(config.unitDecimals)); + } + }, + enumerable: true, + configurable: true }); Object.defineProperty($scope, - "amount", { - get: function () { - return this._amount; - }, - set: function (newValue) { - this._amount = newValue; - this._alternative = unitToAlternative(this._amount); - }, - enumerable: true, - configurable: true + "amount", { + get: function () { + return this._amount; + }, + set: function (newValue) { + this._amount = newValue; + if (newValue) { + this._alternative = -(-( + rateService.toFiat(newValue * config.unitToSatoshi, $rootScope.alternativeIsoCode) + ).toFixed(2)); + } + }, + enumerable: true, + configurable: true }); $scope.loadTxs = function() { diff --git a/js/services/rate.js b/js/services/rate.js new file mode 100644 index 000000000..84809cc30 --- /dev/null +++ b/js/services/rate.js @@ -0,0 +1,40 @@ +'use strict'; + +var RateService = function($http) { + this.isAvailable = false; + this.SAT_TO_BTC = 1 / 1e8; + var that = this; + var backoff = 5; + var retrieve = function() { + $http({method: 'GET', url: 'https://bitpay.com/api/rates'}). + success(function(data, status, headers, config) { + var rates = {}; + data.forEach(function(element) { + rates[element.code] = element.rate; + }); + that.isAvailable = true; + that.rates = rates; + }). + error(function(data, status, headers, config) { + backoff *= 1.5; + setTimeout(retrieve, backoff * 1000); + }); + }; + retrieve(); +}; + +RateService.prototype.toFiat = function(satoshis, code) { + if (!this.isAvailable) { + return 0; + } + return satoshis * this.SAT_TO_BTC * this.rates[code]; +}; + +RateService.prototype.fromFiat = function(amount, code) { + if (!this.isAvailable) { + return 0; + } + return amount / this.rates[code] / this.SAT_TO_BTC; +}; + +angular.module('copayApp.services').service('rateService', RateService); diff --git a/views/send.html b/views/send.html index 7ce6aa6d2..c58a71683 100644 --- a/views/send.html +++ b/views/send.html @@ -1,4 +1,4 @@ -
+

Send Proposals

@@ -88,14 +88,14 @@
- {{$root.alternativeShort}} + {{$root.alternativeIsoCode}}
From 3225553d78cfa60667f91513a2fc0bd09ba3e8c9 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 27 Aug 2014 17:15:05 -0300 Subject: [PATCH 03/16] Currency list in settings --- config.js | 2 ++ js/controllers/send.js | 11 ++++---- js/controllers/settings.js | 40 +++++++++++++++++++++++----- js/services/rate.js | 54 ++++++++++++++++++++++++++++++++------ views/send.html | 4 +-- views/settings.html | 5 ++++ 6 files changed, 94 insertions(+), 22 deletions(-) diff --git a/config.js b/config.js index 5c658ea68..959cb3707 100644 --- a/config.js +++ b/config.js @@ -7,6 +7,8 @@ var defaultConfig = { // DEFAULT unit: Bit unitName: 'bits', unitToSatoshi: 100, + alternativeName: 'US Dollar', + alternativeIsoCode: 'USD', // wallet limits limits: { diff --git a/js/controllers/send.js b/js/controllers/send.js index 7fd8bebd8..713bb8b40 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -12,10 +12,9 @@ angular.module('copayApp.controllers').controller('SendController', $scope.unitToBtc = config.unitToSatoshi / bitcore.util.COIN; $scope.minAmount = config.limits.minAmountSatoshi * satToUnit; $scope.minAlternativeAmount = config.limits.minAmountSatoshi * satToAlternative; -// Mockup - $rootScope.alternativeName = 'Dollars'; - $rootScope.alternativeIsoCode = 'USD'; - config.unitDecimals = 2; + + this.alternativeName = config.alternativeName; + this.alternativeIsoCode = config.alternativeIsoCode; this.rateService = rateService; $scope._amount = 0; @@ -40,7 +39,7 @@ angular.module('copayApp.controllers').controller('SendController', this._alternative = newValue; if (typeof(newValue) === 'number') { this._amount = -(-( - rateService.fromFiat(newValue, $rootScope.alternativeIsoCode) * satToUnit + rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit ).toFixed(config.unitDecimals)); } }, @@ -56,7 +55,7 @@ angular.module('copayApp.controllers').controller('SendController', this._amount = newValue; if (newValue) { this._alternative = -(-( - rateService.toFiat(newValue * config.unitToSatoshi, $rootScope.alternativeIsoCode) + rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode) ).toFixed(2)); } }, diff --git a/js/controllers/settings.js b/js/controllers/settings.js index 49f716519..af943edb9 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $location, controllerUtils) { +angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $location, controllerUtils, rateService) { controllerUtils.redirIfLogged(); $scope.title = 'Settings'; @@ -14,27 +14,51 @@ angular.module('copayApp.controllers').controller('SettingsController', function $scope.unitOpts = [{ name: 'Satoshis (100,000,000 satoshis = 1BTC)', shortName: 'SAT', - value: 1 + value: 1, + decimals: 0 }, { name: 'bits (1,000,000 bits = 1BTC)', shortName: 'bits', - value: 100 + value: 100, + decimals: 2 }, { name: 'mBTC (1,000 mBTC = 1BTC)', shortName: 'mBTC', - value: 100000 + value: 100000, + decimals: 5 }, { name: 'BTC', shortName: 'BTC', - value: 100000000 + value: 100000000, + decimals: 8 }]; + $scope.selectedAlternative = { + name: 'US Dollar', + isoCode: 'USD' + }; + $scope.alternativeOpts = rateService.alternatives; + + rateService.whenAvailable(function() { + $scope.alternativeOpts = rateService.listAlternatives(); + for (var ii in $scope.alternativeOpts) { + if (config.alternativeIsoCode === $scope.alternativeOpts[ii].isoCode) { + $scope.selectedAlternative = $scope.alternativeOpts[ii]; + } + } + }); + for (var ii in $scope.unitOpts) { if (config.unitName === $scope.unitOpts[ii].shortName) { $scope.selectedUnit = $scope.unitOpts[ii]; break; } } + for (var ii in $scope.alternativeOpts) { + if (config.alternativeIsoCode === $scope.alternativeOpts[ii].isoCode) { + $scope.selectedAlternative = $scope.alternativeOpts[ii]; + } + } $scope.changeNetwork = function() { $scope.insightHost = $scope.networkName !== 'testnet' ? 'test-insight.bitpay.com' : 'insight.bitpay.com'; @@ -68,7 +92,11 @@ angular.module('copayApp.controllers').controller('SettingsController', function disableVideo: $scope.disableVideo, unitName: $scope.selectedUnit.shortName, unitToSatoshi: $scope.selectedUnit.value, - version: copay.version, + unitDecimals: $scope.selectedUnit.decimals, + alternativeName: $scope.selectedAlternative.name, + alternativeIsoCode: $scope.selectedAlternative.isoCode, + + version: copay.version })); // Go home reloading the application diff --git a/js/services/rate.js b/js/services/rate.js index 84809cc30..6aa56c356 100644 --- a/js/services/rate.js +++ b/js/services/rate.js @@ -1,28 +1,51 @@ 'use strict'; -var RateService = function($http) { +var request = require('request'); + +var RateService = function() { this.isAvailable = false; this.SAT_TO_BTC = 1 / 1e8; + this.queued = []; + this.alternatives = []; var that = this; var backoff = 5; var retrieve = function() { - $http({method: 'GET', url: 'https://bitpay.com/api/rates'}). - success(function(data, status, headers, config) { + request.get({ + url:'https://bitpay.com/api/rates', + json: true + }, function(err, response, listOfCurrencies) { + if (err) { + backoff *= 1.5; + setTimeout(retrieve, backoff * 1000); + return; + } var rates = {}; - data.forEach(function(element) { + listOfCurrencies.forEach(function(element) { rates[element.code] = element.rate; + that.alternatives.push({ + name: element.name, + isoCode: element.code, + rate: element.rate + }); }); that.isAvailable = true; that.rates = rates; - }). - error(function(data, status, headers, config) { - backoff *= 1.5; - setTimeout(retrieve, backoff * 1000); + that.queued.forEach(function(callback) { + setTimeout(callback, 0); + }); }); }; retrieve(); }; +RateService.prototype.whenAvailable = function(callback) { + if (this.isAvailable) { + setTimeout(callback, 0); + } else { + this.queued.push(callback); + } +}; + RateService.prototype.toFiat = function(satoshis, code) { if (!this.isAvailable) { return 0; @@ -37,4 +60,19 @@ RateService.prototype.fromFiat = function(amount, code) { return amount / this.rates[code] / this.SAT_TO_BTC; }; +RateService.prototype.listAlternatives = function() { + if (!this.isAvailable) { + return []; + } + + var alts = []; + this.alternatives.forEach(function(element) { + alts.push({ + name: element.name, + isoCode: element.isoCode + }); + }); + return alts; +}; + angular.module('copayApp.services').service('rateService', RateService); diff --git a/views/send.html b/views/send.html index c58a71683..118558936 100644 --- a/views/send.html +++ b/views/send.html @@ -85,7 +85,7 @@
- +
- {{$root.alternativeIsoCode}} + {{ctrl.alternativeIsoCode}}
diff --git a/views/settings.html b/views/settings.html index 2b231322b..548c2cbb6 100644 --- a/views/settings.html +++ b/views/settings.html @@ -26,6 +26,11 @@ +
+ Alternative Currency + +
Videoconferencing From 0f0e7b78d549ed75f175cbb331b2352b0854f1cf Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 27 Aug 2014 17:19:28 -0300 Subject: [PATCH 04/16] remove temporary vars --- js/controllers/send.js | 22 ++++++---------------- views/send.html | 2 +- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 713bb8b40..2b0041b4c 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -6,12 +6,9 @@ angular.module('copayApp.controllers').controller('SendController', $scope.title = 'Send'; $scope.loading = false; var satToUnit = 1 / config.unitToSatoshi; - var configAlternativeToSatoshi = (1 / 512 / 1e-8); - var satToAlternative = 1 / configAlternativeToSatoshi; // TODO: Change $scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit; $scope.unitToBtc = config.unitToSatoshi / bitcore.util.COIN; $scope.minAmount = config.limits.minAmountSatoshi * satToUnit; - $scope.minAlternativeAmount = config.limits.minAmountSatoshi * satToAlternative; this.alternativeName = config.alternativeName; this.alternativeIsoCode = config.alternativeIsoCode; @@ -19,16 +16,9 @@ angular.module('copayApp.controllers').controller('SendController', $scope._amount = 0; $scope._alternative = 0; - this.amountFilter = function(val) { - if (val) { - return val.toFixed(config.unitDecimals); - } + var makeNumber = function(val) { + return -(-val); }; - this.fiatFilter = function(val) { - if (val) { - return val.toFixed(2); - } - } Object.defineProperty($scope, "alternative", { @@ -38,8 +28,8 @@ angular.module('copayApp.controllers').controller('SendController', set: function (newValue) { this._alternative = newValue; if (typeof(newValue) === 'number') { - this._amount = -(-( - rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit + this._amount = makeNumber( + (rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit ).toFixed(config.unitDecimals)); } }, @@ -54,8 +44,8 @@ angular.module('copayApp.controllers').controller('SendController', set: function (newValue) { this._amount = newValue; if (newValue) { - this._alternative = -(-( - rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode) + this._alternative = makeNumber( + (rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode) ).toFixed(2)); } }, diff --git a/views/send.html b/views/send.html index 118558936..2c93546a9 100644 --- a/views/send.html +++ b/views/send.html @@ -90,7 +90,7 @@
From fef87921a07260d3a26b9b20c7ced6fbeb57c071 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 27 Aug 2014 17:37:56 -0300 Subject: [PATCH 05/16] Add dependencies to package.json --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 383f137d1..258a25603 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,9 @@ "travis-cov": "0.2.5", "uglifyify": "1.2.3", "crypto-js": "3.1.2", - "shelljs": "0.3.0" + "shelljs":"0.3.0", + "browser-request": "0.3.2", + "request": "2.40.0" }, "main": "app.js", "homepage": "https://github.com/bitpay/copay", From 4e940602190b9cffcd82d2553543ed1161854a29 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Thu, 28 Aug 2014 11:37:39 -0300 Subject: [PATCH 06/16] Better style --- js/controllers/send.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 2b0041b4c..86fc09017 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -14,12 +14,12 @@ angular.module('copayApp.controllers').controller('SendController', this.alternativeIsoCode = config.alternativeIsoCode; this.rateService = rateService; - $scope._amount = 0; - $scope._alternative = 0; - var makeNumber = function(val) { - return -(-val); - }; + $scope.amount = 0; + /** + * Setting the two related amounts as properties prevents an infinite + * recursion for watches while preserving the original angular updates + */ Object.defineProperty($scope, "alternative", { get: function () { @@ -28,9 +28,9 @@ angular.module('copayApp.controllers').controller('SendController', set: function (newValue) { this._alternative = newValue; if (typeof(newValue) === 'number') { - this._amount = makeNumber( + this._amount = Number.parseFloat( (rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit - ).toFixed(config.unitDecimals)); + ).toFixed(config.unitDecimals), 10); } }, enumerable: true, @@ -44,9 +44,9 @@ angular.module('copayApp.controllers').controller('SendController', set: function (newValue) { this._amount = newValue; if (newValue) { - this._alternative = makeNumber( + this._alternative = Number.parseFloat( (rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode) - ).toFixed(2)); + ).toFixed(2), 10); } }, enumerable: true, From c1336ea1cd78e29b1256e22a5b800ef8900c060d Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Thu, 28 Aug 2014 11:46:59 -0300 Subject: [PATCH 07/16] Delete duplicated code --- js/controllers/settings.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/js/controllers/settings.js b/js/controllers/settings.js index af943edb9..1d4d79c23 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -54,11 +54,6 @@ angular.module('copayApp.controllers').controller('SettingsController', function break; } } - for (var ii in $scope.alternativeOpts) { - if (config.alternativeIsoCode === $scope.alternativeOpts[ii].isoCode) { - $scope.selectedAlternative = $scope.alternativeOpts[ii]; - } - } $scope.changeNetwork = function() { $scope.insightHost = $scope.networkName !== 'testnet' ? 'test-insight.bitpay.com' : 'insight.bitpay.com'; From da8aa18a1b1dda8baf523b5a59e5464d4587c9a7 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Thu, 28 Aug 2014 17:44:17 -0300 Subject: [PATCH 08/16] Request as a service instead of required() --- js/services/rate.js | 4 +--- js/services/request.js | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 js/services/request.js diff --git a/js/services/rate.js b/js/services/rate.js index 6aa56c356..9b7e1980e 100644 --- a/js/services/rate.js +++ b/js/services/rate.js @@ -1,8 +1,6 @@ 'use strict'; -var request = require('request'); - -var RateService = function() { +var RateService = function(request) { this.isAvailable = false; this.SAT_TO_BTC = 1 / 1e8; this.queued = []; diff --git a/js/services/request.js b/js/services/request.js new file mode 100644 index 000000000..5a92093c9 --- /dev/null +++ b/js/services/request.js @@ -0,0 +1,6 @@ +'use strict'; + +angular.module('copayApp.services').factory('request', function() { + return require('request'); +}); + From cf143898f9ae60cd2cc249167d0c9d17fa6b012b Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Thu, 28 Aug 2014 17:44:40 -0300 Subject: [PATCH 09/16] test for rate service --- test/run.sh | 0 test/unit/services/servicesSpec.js | 22 ++++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) mode change 100644 => 100755 test/run.sh diff --git a/test/run.sh b/test/run.sh old mode 100644 new mode 100755 diff --git a/test/unit/services/servicesSpec.js b/test/unit/services/servicesSpec.js index df7eed5bf..3552f725d 100644 --- a/test/unit/services/servicesSpec.js +++ b/test/unit/services/servicesSpec.js @@ -79,8 +79,6 @@ describe("Unit: controllerUtils", function() { expect($rootScope.addrInfos[0].address).to.be.equal(Waddr);; })); }); - - }); describe("Unit: Notification Service", function() { @@ -135,3 +133,23 @@ describe("Unit: uriHandler service", function() { }).should.not.throw(); })); }); + +describe('Unit: Rate Service', function() { + beforeEach(angular.mock.module('copayApp.services')); + it('should be injected correctly', inject(function(rateService) { + should.exist(rateService); + })); + it('should be possible to ask if it is available', + inject(function(rateService) { + should.exist(rateService.isAvailable); + }) + ); + it('should be possible to ask for conversion', + inject(function(rateService) { + rateService.whenAvailable(function() { + rateService.rates['LOL'] = 2; + (1 * 1e8).should.equal(rateService.fromFiat(2, 'LOL')); + }); + }) + ); +}); From ac6dfc80359d1c77523e3db23b0ca4fe155d6213 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Thu, 28 Aug 2014 21:06:49 -0300 Subject: [PATCH 10/16] Add more tests to controller --- js/services/rate.js | 4 +-- test/unit/controllers/controllersSpec.js | 36 ++++++++++++++++++++---- test/unit/services/servicesSpec.js | 19 +++++++++++-- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/js/services/rate.js b/js/services/rate.js index 9b7e1980e..764e5c914 100644 --- a/js/services/rate.js +++ b/js/services/rate.js @@ -29,7 +29,7 @@ var RateService = function(request) { that.isAvailable = true; that.rates = rates; that.queued.forEach(function(callback) { - setTimeout(callback, 0); + setTimeout(callback, 1); }); }); }; @@ -38,7 +38,7 @@ var RateService = function(request) { RateService.prototype.whenAvailable = function(callback) { if (this.isAvailable) { - setTimeout(callback, 0); + setTimeout(callback, 1); } else { this.queued.push(callback); } diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index c056246c8..aeb7a5006 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -29,7 +29,9 @@ describe("Unit: Controllers", function() { totalCopayers: 5, spendUnconfirmed: 1, reconnectDelay: 100, - networkName: 'testnet' + networkName: 'testnet', + alternativeName: 'lol currency', + alternativeIsoCode: 'LOL' }; it('Copay config should be binded', function() { @@ -124,11 +126,20 @@ describe("Unit: Controllers", function() { }); describe('Send Controller', function() { - var scope, form, sendForm; + var scope, form, sendForm, sendCtrl; beforeEach(angular.mock.module('copayApp')); + beforeEach(module(function($provide) { + $provide.value('request', { + 'get': function(_, cb) { + cb(null, null, [{name: 'lol currency', code: 'LOL', rate: 2}]); + } + }); + })); beforeEach(angular.mock.inject(function($compile, $rootScope, $controller) { scope = $rootScope.$new(); $rootScope.wallet = new FakeWallet(walletConfig); + config.alternativeName = 'lol currency'; + config.alternativeIsoCode = 'LOL'; var element = angular.element( '
' + '' + @@ -147,11 +158,12 @@ describe("Unit: Controllers", function() { '' + '' + '' + + '' + '' + '
' ); $compile(element2)(scope); - $controller('SendController', { + sendCtrl = $controller('SendController', { $scope: scope, $modal: {}, }); @@ -241,8 +253,22 @@ describe("Unit: Controllers", function() { config.unitToSatoshi = old; }); - - + it('should convert bits amount to fiat', function(done) { + sendCtrl.rateService.whenAvailable(function() { + sendForm.amount.$setViewValue(1e6); + scope.$digest(); + expect(scope.alternative).to.equal(2); + done(); + }); + }); + it('should convert fiat to bits amount', function(done) { + sendCtrl.rateService.whenAvailable(function() { + sendForm.alternative.$setViewValue(2); + scope.$digest(); + expect(scope.amount).to.equal(1e6); + done(); + }); + }); it('should create and send a transaction proposal', function() { sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); diff --git a/test/unit/services/servicesSpec.js b/test/unit/services/servicesSpec.js index 3552f725d..144e9488b 100644 --- a/test/unit/services/servicesSpec.js +++ b/test/unit/services/servicesSpec.js @@ -144,11 +144,24 @@ describe('Unit: Rate Service', function() { should.exist(rateService.isAvailable); }) ); - it('should be possible to ask for conversion', + beforeEach(module(function($provide) { + $provide.value('request', { + 'get': function(_, cb) { + cb(null, null, [{name: 'lol currency', code: 'LOL', rate: 2}]); + } + }); + })); + it('should be possible to ask for conversion from fiat', inject(function(rateService) { rateService.whenAvailable(function() { - rateService.rates['LOL'] = 2; - (1 * 1e8).should.equal(rateService.fromFiat(2, 'LOL')); + (1).should.equal(rateService.fromFiat(2, 'LOL')); + }); + }) + ); + it('should be possible to ask for conversion to fiat', + inject(function(rateService) { + rateService.whenAvailable(function() { + (2).should.equal(rateService.toFiat(1e8, 'LOL')); }); }) ); From 1c480d21a6685cbb4bbd6a034cdd3853667a4e1e Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 29 Aug 2014 12:09:39 -0300 Subject: [PATCH 11/16] Address @matiu's comments --- config.js | 5 +++++ js/services/rate.js | 21 ++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/config.js b/config.js index 959cb3707..d71dd7624 100644 --- a/config.js +++ b/config.js @@ -56,6 +56,11 @@ var defaultConfig = { storageSalt: 'mjuBtGybi/4=', }, + rate: { + url: 'https://bitpay.com/api/rates', + updateFrequencySeconds: 60 * 60 + }, + disableVideo: true, verbose: 1, }; diff --git a/js/services/rate.js b/js/services/rate.js index 764e5c914..0c28ae046 100644 --- a/js/services/rate.js +++ b/js/services/rate.js @@ -2,19 +2,25 @@ var RateService = function(request) { this.isAvailable = false; + this.UNAVAILABLE_ERROR = 'Service is not available - check for service.isAvailable or use service.whenAvailable'; this.SAT_TO_BTC = 1 / 1e8; + var MINS_IN_HOUR = 60; + var MILLIS_IN_SECOND = 1000; + var rateServiceConfig = config.rate; + var updateFrequencySeconds = rateServiceConfig.updateFrequencySeconds || 60 * MINS_IN_HOUR; + var rateServiceUrl = rateServiceConfig.url || 'https://bitpay.com/api/rates'; this.queued = []; this.alternatives = []; var that = this; - var backoff = 5; + var backoffSeconds = 5; var retrieve = function() { request.get({ - url:'https://bitpay.com/api/rates', + url: rateServiceUrl, json: true }, function(err, response, listOfCurrencies) { if (err) { - backoff *= 1.5; - setTimeout(retrieve, backoff * 1000); + backoffSeconds *= 1.5; + setTimeout(retrieve, backoffSeconds * MILLIS_IN_SECOND); return; } var rates = {}; @@ -31,6 +37,7 @@ var RateService = function(request) { that.queued.forEach(function(callback) { setTimeout(callback, 1); }); + setTimeout(retrieve, updateFrequencySeconds * MILLIS_IN_SECOND); }); }; retrieve(); @@ -46,21 +53,21 @@ RateService.prototype.whenAvailable = function(callback) { RateService.prototype.toFiat = function(satoshis, code) { if (!this.isAvailable) { - return 0; + throw new Error(this.UNAVAILABLE_ERROR); } return satoshis * this.SAT_TO_BTC * this.rates[code]; }; RateService.prototype.fromFiat = function(amount, code) { if (!this.isAvailable) { - return 0; + throw new Error(this.UNAVAILABLE_ERROR); } return amount / this.rates[code] / this.SAT_TO_BTC; }; RateService.prototype.listAlternatives = function() { if (!this.isAvailable) { - return []; + throw new Error(this.UNAVAILABLE_ERROR); } var alts = []; From 22b92aca6107d9de09d6213c1d37753145797199 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 29 Aug 2014 13:36:38 -0300 Subject: [PATCH 12/16] Address needed change to transaction resume, fix tests --- js/controllers/send.js | 24 ++++++++++++++++-------- test/unit/controllers/controllersSpec.js | 7 ++++--- views/send.html | 11 +++++++---- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 86fc09017..c82ed9280 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -8,13 +8,19 @@ angular.module('copayApp.controllers').controller('SendController', var satToUnit = 1 / config.unitToSatoshi; $scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit; $scope.unitToBtc = config.unitToSatoshi / bitcore.util.COIN; + $scope.unitToSatoshi = config.unitToSatoshi; $scope.minAmount = config.limits.minAmountSatoshi * satToUnit; - this.alternativeName = config.alternativeName; - this.alternativeIsoCode = config.alternativeIsoCode; - this.rateService = rateService; + $scope.alternativeName = config.alternativeName; + $scope.alternativeIsoCode = config.alternativeIsoCode; - $scope.amount = 0; + $scope.isRateAvailable = false; + $scope.rateService = rateService; + + rateService.whenAvailable(function() { + $scope.isRateAvailable = true; + $scope.$digest(); + }); /** * Setting the two related amounts as properties prevents an infinite @@ -26,12 +32,13 @@ angular.module('copayApp.controllers').controller('SendController', return this._alternative; }, set: function (newValue) { + newValue = -(-newValue) || 0; this._alternative = newValue; - if (typeof(newValue) === 'number') { + if ($scope.isRateAvailable) { this._amount = Number.parseFloat( (rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit ).toFixed(config.unitDecimals), 10); - } + }; }, enumerable: true, configurable: true @@ -42,12 +49,13 @@ angular.module('copayApp.controllers').controller('SendController', return this._amount; }, set: function (newValue) { + newValue = -(-newValue) || 0; this._amount = newValue; - if (newValue) { + if ($scope.isRateAvailable) { this._alternative = Number.parseFloat( (rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode) ).toFixed(2), 10); - } + }; }, enumerable: true, configurable: true diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index aeb7a5006..f4ec299e6 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -135,8 +135,9 @@ describe("Unit: Controllers", function() { } }); })); - beforeEach(angular.mock.inject(function($compile, $rootScope, $controller) { + beforeEach(angular.mock.inject(function($compile, $rootScope, $controller, rateService) { scope = $rootScope.$new(); + scope.rateService = rateService; $rootScope.wallet = new FakeWallet(walletConfig); config.alternativeName = 'lol currency'; config.alternativeIsoCode = 'LOL'; @@ -254,7 +255,7 @@ describe("Unit: Controllers", function() { }); it('should convert bits amount to fiat', function(done) { - sendCtrl.rateService.whenAvailable(function() { + scope.rateService.whenAvailable(function() { sendForm.amount.$setViewValue(1e6); scope.$digest(); expect(scope.alternative).to.equal(2); @@ -262,7 +263,7 @@ describe("Unit: Controllers", function() { }); }); it('should convert fiat to bits amount', function(done) { - sendCtrl.rateService.whenAvailable(function() { + scope.rateService.whenAvailable(function() { sendForm.alternative.$setViewValue(2); scope.$digest(); expect(scope.amount).to.equal(1e6); diff --git a/views/send.html b/views/send.html index 2c93546a9..cb06736e9 100644 --- a/views/send.html +++ b/views/send.html @@ -1,4 +1,4 @@ -
+

Send Proposals

@@ -85,17 +85,17 @@
- +
- {{ctrl.alternativeIsoCode}} + {{alternativeIsoCode}}
@@ -135,6 +135,9 @@
Total amount for this transaction:

{{amount + defaultFee |noFractionNumber}} {{$root.unitName}} + + {{ rateService.toFiat((amount + defaultFee) * unitToSatoshi, alternativeIsoCode) | noFractionNumber: 2 }} {{ alternativeIsoCode }} + {{ ((amount + defaultFee) * unitToBtc)|noFractionNumber:8}} BTC
Including fee of {{defaultFee|noFractionNumber}} {{$root.unitName}} From 7529a629b8fc0149a3902977c62127a441513ec4 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 29 Aug 2014 14:06:20 -0300 Subject: [PATCH 13/16] Fixes send controller --- js/controllers/send.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index c82ed9280..3dc673183 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -32,13 +32,14 @@ angular.module('copayApp.controllers').controller('SendController', return this._alternative; }, set: function (newValue) { - newValue = -(-newValue) || 0; this._alternative = newValue; - if ($scope.isRateAvailable) { + if (typeof(newValue) === 'number' && $scope.isRateAvailable) { this._amount = Number.parseFloat( (rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit ).toFixed(config.unitDecimals), 10); - }; + } else { + this._amount = 0; + } }, enumerable: true, configurable: true @@ -49,13 +50,14 @@ angular.module('copayApp.controllers').controller('SendController', return this._amount; }, set: function (newValue) { - newValue = -(-newValue) || 0; this._amount = newValue; - if ($scope.isRateAvailable) { + if (typeof(newValue) === 'number' && $scope.isRateAvailable) { this._alternative = Number.parseFloat( (rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode) ).toFixed(2), 10); - }; + } else { + this._alternative = 0; + } }, enumerable: true, configurable: true From d3f48661e13b3a9c1b8e60b7158aba30473b397b Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Fri, 29 Aug 2014 18:38:19 -0300 Subject: [PATCH 14/16] Add minor improvements --- views/send.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/send.html b/views/send.html index cb06736e9..a99d4b95a 100644 --- a/views/send.html +++ b/views/send.html @@ -126,7 +126,7 @@

-
+
Send to

@@ -137,9 +137,9 @@ {{amount + defaultFee |noFractionNumber}} {{$root.unitName}} {{ rateService.toFiat((amount + defaultFee) * unitToSatoshi, alternativeIsoCode) | noFractionNumber: 2 }} {{ alternativeIsoCode }} +
- {{ ((amount + defaultFee) * unitToBtc)|noFractionNumber:8}} BTC
Including fee of {{defaultFee|noFractionNumber}} {{$root.unitName}}

From 54b3482b1fec980266b7561d64f4729e7e8c6ad7 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Mon, 1 Sep 2014 10:42:30 -0300 Subject: [PATCH 15/16] Add dollar balance to sidebar --- js/services/controllerUtils.js | 12 ++++++++++-- views/includes/sidebar.html | 7 +++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 143536504..03b1eaff5 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -2,7 +2,7 @@ var bitcore = require('bitcore'); angular.module('copayApp.services') - .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, video, uriHandler) { + .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, video, uriHandler, rateService) { var root = {}; root.getVideoMutedStatus = function(copayer) { if (!$rootScope.videoInfo) return; @@ -217,7 +217,15 @@ angular.module('copayApp.services') $rootScope.balanceByAddr = balanceByAddr; root.updateAddressList(); $rootScope.updatingBalance = false; - return cb ? cb() : null; + + rateService.whenAvailable(function() { + $rootScope.totalBalanceAlternative = rateService.toFiat(balanceSat, config.alternativeIsoCode); + $rootScope.alternativeIsoCode = config.alternativeIsoCode; + $rootScope.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, config.alternativeIsoCode); + + + return cb ? cb() : null; + }); }); }; diff --git a/views/includes/sidebar.html b/views/includes/sidebar.html index a22bb2475..c0b942533 100644 --- a/views/includes/sidebar.html +++ b/views/includes/sidebar.html @@ -24,10 +24,9 @@ class="has-tip" data-options="disable_for_touch:true" tooltip-popup-delay='500' - tooltip="{{totalBalanceBTC |noFractionNumber:8}} BTC" + tooltip="{{totalBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}" tooltip-trigger="mouseenter" - tooltip-placement="bottom">{{totalBalance || 0 - |noFractionNumber}} {{$root.unitName}} + tooltip-placement="bottom">{{totalBalance || 0 |noFractionNumber}} {{$root.unitName}}
Locked   @@ -38,7 +37,7 @@ class="has-tip" data-options="disable_for_touch:true" tooltip-popup-delay='500' - tooltip="{{lockedBalanceBTC |noFractionNumber:8}} BTC" + tooltip="{{lockedBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}" tooltip-trigger="mouseenter" tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}}   From 91dedb8bc168d5935ab2d3aeb0cdb1a6c58bbd98 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Mon, 1 Sep 2014 11:04:57 -0300 Subject: [PATCH 16/16] Fixes settings screen --- js/controllers/settings.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/controllers/settings.js b/js/controllers/settings.js index 1d4d79c23..89b9206ef 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -34,10 +34,11 @@ angular.module('copayApp.controllers').controller('SettingsController', function }]; $scope.selectedAlternative = { - name: 'US Dollar', - isoCode: 'USD' + name: config.alternativeName, + isoCode: config.alternativeIsoCode }; - $scope.alternativeOpts = rateService.alternatives; + $scope.alternativeOpts = rateService.isAvailable ? + rateService.listAlternatives() : [$scope.selectedAlternative]; rateService.whenAvailable(function() { $scope.alternativeOpts = rateService.listAlternatives();