diff --git a/TODO b/TODO index 53967d2d9..6ee9401cb 100644 --- a/TODO +++ b/TODO @@ -1,12 +1 @@ - - - homeWallet address...e sta ok? -- receive controller .. owned --- -// pkr.cache = opts.cache; - -(en send.js) -// $rootScope.pendingTxCount = res.pendingForUs; - --- probar payment intent - -// TODO refrescar en 'add' +- addressbock diff --git a/css/src/main.css b/css/src/main.css index a693ef294..ddccd1c8b 100644 --- a/css/src/main.css +++ b/css/src/main.css @@ -99,8 +99,16 @@ header .alt-currency { font-weight: 700; } +.color-greeni { + color: #1abc9c !important; +} + +.color-yellowi { + color: yellow !important; +} + .alt-currency.green { - background: #1ABC9C; + background: #1abc9c; } .alt-currency.red { @@ -409,20 +417,6 @@ a:hover { border-radius: 3px 3px 0 3px; } -a.missing-copayers { - background: #7A8C9E; - position: absolute; - display: block; - right: -1px; - bottom: -18px; - padding: 0.2rem 0.5rem; - font-size: 10px; - border-radius: 0 0 3px 3px; - text-transform: uppercase; - color: #fff; - font-weight: 700; -} - ul.tx-copayers { background: #E4E8EC; padding: 0.3rem 0.8rem; diff --git a/js/controllers/copayers.js b/js/controllers/copayers.js index 0fd363eeb..a6b38989c 100644 --- a/js/controllers/copayers.js +++ b/js/controllers/copayers.js @@ -1,22 +1,33 @@ 'use strict'; angular.module('copayApp.controllers').controller('CopayersController', - function($scope, $rootScope, $location) { + function($scope, $rootScope, $timeout, go) { - if (!$rootScope.wallet.isComplete()) { + console.log('[copayers.js.5]'); //TODO + + $scope.init = function() { + var w = $rootScope.wallet; $rootScope.title = 'Waiting copayers for ' + $rootScope.wallet.getName(); - } - $scope.loading = false; - $scope.secret = $rootScope.wallet.getSecret(); + $scope.loading = false; + $scope.secret = $rootScope.wallet.getSecret(); - $scope.goToWallet = function() { - $location.path('/homeWallet'); + w.on('publicKeyRingUpdated', $scope.updateList); + w.on('ready', $scope.updateList); + $scope.updateList(); }; - $scope.copayersList = function() { - if ($rootScope.wallet) { - $scope.copayers = $rootScope.wallet.getRegisteredPeerIds(); + $scope.updateList = function() { + var w = $rootScope.wallet; + + $scope.copayers = $rootScope.wallet.getRegisteredPeerIds(); + if (w.isComplete()) { + + w.removeListener('publicKeyRingUpdated', $scope.updateList); + w.removeListener('ready', $scope.updateList); + go.walletHome(); } - return $scope.copayers; - } + $timeout(function() { + $rootScope.$digest(); + }, 1); + }; }); diff --git a/js/controllers/createProfile.js b/js/controllers/createProfile.js index 6bdad77a7..72a63d1e0 100644 --- a/js/controllers/createProfile.js +++ b/js/controllers/createProfile.js @@ -5,7 +5,10 @@ angular.module('copayApp.controllers').controller('CreateProfileController', fun var _credentials, _firstpin; $scope.init = function() { - identityService.goWalletHome(); + + if ($rootScope.wallet) + go.walletHome(); + $scope.isMobile = isMobile.any(); $scope.createStep = 'storage'; diff --git a/js/controllers/home.js b/js/controllers/home.js index 045683eab..12cf6dda1 100644 --- a/js/controllers/home.js +++ b/js/controllers/home.js @@ -5,7 +5,7 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc // Global go. This should be in a better place TODO // We dont do a 'go' directive, to use the benefits of ng-touch with ng-click $rootScope.go = function (path) { - go.go(path); + go.path(path); }; var _credentials, _firstpin; @@ -25,8 +25,8 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc $rootScope.fromEmailConfirmation = false; } - if ($rootScope.iden) { - identityService.goWalletHome(); + if ($rootScope.wallet) { + go.walletHome(); } Compatibility.check($scope); @@ -178,7 +178,7 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc } } }); - } + }; function getParam(sname) { var params = location.search.substr(location.search.indexOf("?") + 1); diff --git a/js/controllers/homeWallet.js b/js/controllers/homeWallet.js index f2e1d51bd..091e5f731 100644 --- a/js/controllers/homeWallet.js +++ b/js/controllers/homeWallet.js @@ -1,15 +1,17 @@ 'use strict'; -angular.module('copayApp.controllers').controller('HomeWalletController', function($scope, $rootScope, $timeout, $filter, $location, rateService, notification, identityService) { - $scope.init = function() { +angular.module('copayApp.controllers').controller('HomeWalletController', function($scope, $rootScope, $timeout, $filter, $modal, rateService, notification, txStatus, identityService) { + $scope.initHome = function() { + var w = $rootScope.wallet; + $rootScope.title = 'Home'; - - $scope.rateService = rateService; $scope.isRateAvailable = false; - var w = $rootScope.wallet; - w.on('txProposalEvent', _updateTxs); + if (w.isShared()) + $scope.copayers = w.getRegisteredPeerIds(); + + w.on('txProposalEvent', _updateTxs); _updateTxs(); rateService.whenAvailable(function() { @@ -18,7 +20,9 @@ angular.module('copayApp.controllers').controller('HomeWalletController', functi }); }; - // This is necesarry, since wallet can change in homeWallet, without running init() again. + // This is necessary, since wallet can change in homeWallet, + // without running init() again. + var removeWatch; removeWatch = $rootScope.$watch('wallet.id', function(newWallet, oldWallet) { if ($rootScope.wallet && $rootScope.wallet.isComplete() && newWallet !== oldWallet) { @@ -40,23 +44,7 @@ angular.module('copayApp.controllers').controller('HomeWalletController', functi } }); - - // TODO duplicated on controller send. move to a service. - $scope.notifyStatus = function(status) { - if (status == copay.Wallet.TX_BROADCASTED) - notification.success('Success', 'Transaction broadcasted!'); - else if (status == copay.Wallet.TX_PROPOSAL_SENT) - notification.info('Success', 'Transaction proposal created'); - else if (status == copay.Wallet.TX_SIGNED) - notification.success('Success', 'Transaction proposal was signed'); - else if (status == copay.Wallet.TX_SIGNED_AND_BROADCASTED) - notification.success('Success', 'Transaction signed and broadcasted!'); - else - notification.error('Error', 'Unknown error occured'); - }; - - - $scope.$on("$destroy", function() { + $scope.$on("$destroy", function() { var w = $rootScope.wallet; if (w) { removeWatch(); @@ -109,7 +97,8 @@ angular.module('copayApp.controllers').controller('HomeWalletController', functi $scope.error = $scope.success = null; w.signAndSend(ntxid, function(err, id, status) { $scope.loading = false; - $scope.notifyStatus(status); + if (!txStatus.notify(status)) + $scope.error = status; _updateTxs(); }); }; @@ -117,8 +106,32 @@ angular.module('copayApp.controllers').controller('HomeWalletController', functi $scope.reject = function(ntxid) { var w = $rootScope.wallet; w.reject(ntxid); - notification.warning('Transaction rejected', 'You rejected the transaction successfully'); + txStatus.notify('txRejected'); _updateTxs(); }; + + $scope.openTxModal = function(tx) { + var ModalInstanceCtrl = function($scope, $modalInstance) { + $scope.tx = tx; + + $scope.getShortNetworkName = function() { + var w = $rootScope.wallet; + return w.getNetworkName().substring(0, 4); + }; + + $scope.cancel = function() { + $modalInstance.dismiss('cancel'); + }; + }; + + $modal.open({ + templateUrl: 'views/modals/txp-details.html', + windowClass: 'tiny', + controller: ModalInstanceCtrl, + }); + }; + + + }); diff --git a/js/controllers/paymentIntent.js b/js/controllers/paymentIntent.js deleted file mode 100644 index 73021c785..000000000 --- a/js/controllers/paymentIntent.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('PaymentIntentController', function($rootScope, $scope, $modal, $location, $timeout, balanceService) { - - $rootScope.title = 'Payment intent'; - - $scope.open = function() { - var modalInstance = $modal.open({ - templateUrl: 'myModalContent.html', - controller: ModalInstanceCtrl - }); - }; - - - // Please note that $modalInstance represents a modal window (instance) dependency. - // It is not the same as the $modal service used above. - - var ModalInstanceCtrl = function($scope, $modalInstance, identityService) { - $scope.loading = true; - $scope.setWallets = function() { - if (!$rootScope.iden) return; - var ret = _.filter($rootScope.iden.listWallets(), function(w) { - return w.balanceInfo && w.balanceInfo.totalBalanceBTC; - }); - $timeout(function() { - $scope.wallets = ret; - $scope.loading = false; - $scope.$digest(); - }, 1000); - }; - if ($rootScope.iden) { - var iden = $rootScope.iden; - iden.on('newWallet', function() { - $scope.setWallets(); - }); - } - $scope.ok = function(selectedItem) { - identityService.setPaymentWallet(selectedItem); - $modalInstance.close(); - }; - - $scope.cancel = function() { - $rootScope.pendingPayment = null; - $modalInstance.close(); - $location.path('/homeWallet'); - }; - }; - -}); diff --git a/js/controllers/paymentUri.js b/js/controllers/paymentUri.js new file mode 100644 index 000000000..dd855cc8c --- /dev/null +++ b/js/controllers/paymentUri.js @@ -0,0 +1,20 @@ +var bitcore = require('bitcore'); + +angular.module('copayApp.controllers').controller('paymentUriController', function($rootScope, $scope, $routeParams, $location, go) { + + // Build bitcoinURI with querystring + var query = []; + angular.forEach($location.search(), function(value, key) { + query.push(key + "=" + value); + }); + var queryString = query ? query.join("&") : null; + var bitcoinURI = $routeParams.data + ( queryString ? '?' + queryString : ''); + var uri = new bitcore.BIP21(bitcoinURI); + + if (uri && uri.address && (_.isString(uri.address) || uri.address.isValid()) ) { + copay.logger.debug('Payment Intent:', bitcoinURI); + $rootScope.pendingPayment = bitcoinURI; + } + + go.home(); +}); diff --git a/js/controllers/receive.js b/js/controllers/receive.js index 660a02417..8f5038f83 100644 --- a/js/controllers/receive.js +++ b/js/controllers/receive.js @@ -21,10 +21,8 @@ angular.module('copayApp.controllers').controller('ReceiveController', var lastAddr = _.first(w.getAddressesOrderer()); var balance = w.balanceInfo.balanceByAddr; - if (balance[lastAddr]>0) - $scope.loading = true; - while (balance && balance[lastAddr] > 0) { + $scope.loading = true; $scope.newAddr(); lastAddr = w.generateAddress(null); }; diff --git a/js/controllers/send.js b/js/controllers/send.js index af50a2edb..7bfd9bfae 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -3,172 +3,168 @@ var bitcore = require('bitcore'); var preconditions = require('preconditions').singleton(); angular.module('copayApp.controllers').controller('SendController', - function($scope, $rootScope, $window, $timeout, $modal, $filter, $location, isMobile, notification, rateService) { - var w = $rootScope.wallet; - preconditions.checkState(w); - preconditions.checkState(w.settings.unitToSatoshi); - - $rootScope.title = w.isShared() ? 'Create Transaction Proposal' : 'Send'; - $scope.loading = false; - $scope.error = $scope.success = null; - var satToUnit = 1 / w.settings.unitToSatoshi; - $scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit; - $scope.unitToBtc = w.settings.unitToSatoshi / bitcore.util.COIN; - $scope.unitToSatoshi = w.settings.unitToSatoshi; - - $scope.alternativeName = w.settings.alternativeName; - $scope.alternativeIsoCode = w.settings.alternativeIsoCode; - - $scope.isRateAvailable = false; - $scope.rateService = rateService; - $scope.showScanner = false; - $scope.myId = w.getMyCopayerId(); - $scope.isMobile = isMobile.any(); - - rateService.whenAvailable(function() { - $scope.isRateAvailable = true; - $scope.$digest(); - }); - - $scope.setAlternativeAmount = function(w, tx, cb) { - rateService.whenAvailable(function() { - _.each(tx.outs, function(out) { - var valueSat = out.valueSat * w.settings.unitToSatoshi; - out.alternativeAmount = $filter('noFractionNumber')(rateService.toFiat(valueSat, $scope.alternativeIsoCode), 2); - out.alternativeIsoCode = $scope.alternativeIsoCode; - }); - if (cb) return cb(tx); - }); - }; - - /** - * 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() { - return this._alternative; - }, - set: function(newValue) { - this._alternative = newValue; - if (typeof(newValue) === 'number' && $scope.isRateAvailable) { - this._amount = parseFloat( - (rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10); - } else { - this._amount = 0; - } - }, - enumerable: true, - configurable: true - }); - Object.defineProperty($scope, - "amount", { - get: function() { - return this._amount; - }, - set: function(newValue) { - this._amount = newValue; - if (typeof(newValue) === 'number' && $scope.isRateAvailable) { - - this._alternative = parseFloat( - (rateService.toFiat(newValue * w.settings.unitToSatoshi, $scope.alternativeIsoCode)).toFixed(2), 10); - } else { - this._alternative = 0; - } - }, - enumerable: true, - configurable: true - }); - Object.defineProperty($scope, - "address", { - get: function() { - return this._address; - }, - set: function(newValue) { - this._address = newValue; - _onChanged(); - }, - enumerable: true, - configurable: true - }); + function($scope, $rootScope, $window, $timeout, $modal, $filter, notification, isMobile, rateService, txStatus) { + var satToUnit; $scope.init = function() { - // Empty - }; + var w = $rootScope.wallet; + preconditions.checkState(w); + preconditions.checkState(w.settings.unitToSatoshi); - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; - window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; + $rootScope.title = w.isShared() ? 'Create Transaction Proposal' : 'Send'; + $scope.loading = false; + $scope.error = $scope.success = null; - if (!window.cordova && !navigator.getUserMedia) - $scope.disableScanner = 1; + satToUnit = 1 / w.settings.unitToSatoshi; - $scope._showError = function(err) { - copay.logger.error(err); + $scope.alternativeName = w.settings.alternativeName; + $scope.alternativeIsoCode = w.settings.alternativeIsoCode; + + $scope.isRateAvailable = false; + $scope.rateService = rateService; + $scope.showScanner = false; + $scope.myId = w.getMyCopayerId(); + $scope.isMobile = isMobile.any(); + + if ($rootScope.pendingPayment) { + $timeout(function() { + $scope.setFromUri($rootScope.pendingPayment) + $rootScope.pendingPayment = null; + }, 100); + } + + $scope.setInputs(); + $scope.setScanner(); + + rateService.whenAvailable(function() { + $scope.isRateAvailable = true; + $scope.$digest(); + }); + } + + $scope.setInputs = function() { + var w = $rootScope.wallet; + var unitToSat = w.settings.unitToSatoshi; + /** + * 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() { + return this.__alternative; + }, + set: function(newValue) { + this.__alternative = newValue; + if (typeof(newValue) === 'number' && $scope.isRateAvailable) { + this._amount = parseFloat( + (rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10); + } else { + this._amount = 0; + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty($scope, + "_amount", { + get: function() { + return this.__amount; + }, + set: function(newValue) { + this.__amount = newValue; + if (typeof(newValue) === 'number' && $scope.isRateAvailable) { + this.__alternative = parseFloat( + (rateService.toFiat(newValue * unitToSat, $scope.alternativeIsoCode)).toFixed(2), 10); + } else { + this.__alternative = 0; + } + }, + enumerable: true, + configurable: true + }); + + Object.defineProperty($scope, + "_address", { + get: function() { + return this.__address; + }, + set: function(newValue) { + this.__address = $scope.onAddressChange(newValue); + }, + enumerable: true, + configurable: true + }); + }; + + $scope.setScanner = function() { + navigator.getUserMedia = navigator.getUserMedia || + navigator.webkitGetUserMedia || navigator.mozGetUserMedia || + navigator.msGetUserMedia; + window.URL = window.URL || window.webkitURL || + window.mozURL || window.msURL; + + if (!window.cordova && !navigator.getUserMedia) + $scope.disableScanner = 1; + }; + + + $scope.setError = function(err) { + var w = $rootScope.wallet; + copay.logger.warn(err); var msg = err.toString(); if (msg.match('BIG')) msg = 'The transaction have too many inputs. Try creating many transactions for smaller amounts' - if (msg.match('totalNeededAmount')) - msg = 'Not enough funds' + if (msg.match('totalNeededAmount') || msg.match('unspent not set')) + msg = 'Insufficient funds' - - if (msg.match('unspent not set')) - msg = 'Not enough funds' + if (msg.match('expired')) + msg = 'The payment request has expired'; var message = 'The transaction' + (w.isShared() ? ' proposal' : '') + ' could not be created: ' + msg; $scope.error = message; - $scope.loading = false; + + $timeout(function() { + $scope.$digest(); + }, 1); }; $scope.submitForm = function(form) { + var w = $rootScope.wallet; + var unitToSat = w.settings.unitToSatoshi; + if (form.$invalid) { $scope.error = 'Unable to send transaction proposal'; return; } $scope.loading = true; - - var address = form.address.$modelValue; - var amount = parseInt((form.amount.$modelValue * w.settings.unitToSatoshi).toFixed(0)); - var commentText = form.comment.$modelValue; - - - var payInfo; - if (address.indexOf('bitcoin:') === 0) { - payInfo = (new bitcore.BIP21(address)).data; - } else if (/^https?:\/\//.test(address)) { - payInfo = { - merchant: address - }; + var comment = form.comment.$modelValue; + var merchantData = $scope._merchantData; + var address, amount; + if (!merchantData) { + address = form.address.$modelValue; + amount = parseInt((form.amount.$modelValue * unitToSat).toFixed(0)); } - // If we're setting the domain, ignore the change. - if ($rootScope.merchant && $rootScope.merchant.domain && address === $rootScope.merchant.domain) { - payInfo = { - merchant: $rootScope.merchant.request_url - }; - } w.spend({ + merchantData: merchantData, toAddress: address, amountSat: amount, - comment: commentText, - url: (payInfo && payInfo.merchant) ? payInfo.merchant : null, + comment: comment, }, function(err, txid, status) { $scope.loading = false; - // reset fields - $scope.address = $scope.amount = $scope.commentText = null; - form.address.$pristine = form.amount.$pristine = true; - $rootScope.pendingPayment = null; - $scope.isPayUri = null; - if (err) return $scope._showError(err); - $scope.notifyStatus(status); + if (err) + return $scope.setError(err); + + $scope.resetForm(status); }); }; @@ -253,7 +249,6 @@ angular.module('copayApp.controllers').controller('SendController', qrcode.callback = function(data) { _scanStop(); - $scope.$apply(function() { $scope.sendForm.address.$setViewValue(data); $scope.sendForm.address.$render(); @@ -309,318 +304,251 @@ angular.module('copayApp.controllers').controller('SendController', } $scope.setTopAmount = function() { - $scope.amount = $rootScope.topAmount; + var w = $rootScope.wallet; + var form = $scope.sendForm; + if (form) { + form.amount.$setViewValue(w.balanceInfo.topAmount); + form.amount.$render(); + form.amount.$isValid = true; + } }; $scope.notifyStatus = function(status) { - if (status == copay.Wallet.TX_BROADCASTED) - $scope.success = 'Transaction broadcasted!'; - else if (status == copay.Wallet.TX_PROPOSAL_SENT) - $scope.success = 'Transaction proposal created'; - else if (status == copay.Wallet.TX_SIGNED) - $scope.success = 'Transaction proposal was signed'; - else if (status == copay.Wallet.TX_SIGNED_AND_BROADCASTED) - $scope.success = 'Transaction signed and broadcasted!'; - else - $scope.error = 'Unknown error occured'; + var msg; - $timeout(function() { - $scope.$digest(); - }); + if (status == copay.Wallet.TX_BROADCASTED) + msg = 'Transaction broadcasted!'; + else if (status == copay.Wallet.TX_PROPOSAL_SENT) + msg = 'Transaction proposal created'; + else if (status == copay.Wallet.TX_SIGNED) + msg = 'Transaction proposal was signed'; + else if (status == copay.Wallet.TX_SIGNED_AND_BROADCASTED) + msg = 'Transaction signed and broadcasted!'; + + if (msg) + $scope.openTxStatusModal(msg); + else + $scope.error = status; }; $scope.send = function(ntxid, cb) { + var w = $rootScope.wallet; $scope.error = $scope.success = null; $scope.loading = true; $rootScope.txAlertCount = 0; w.issueTx(ntxid, function(err, txid, status) { $scope.loading = false; - $scope.notifyStatus(status); + $scope.resetForm(status); if (cb) return cb(); }); - }; + }; - $scope.clearMerchant = function(callback) { - // TODO: Find a better way of detecting - // whether we're in the Send scope or not. - if (!$scope.sendForm || !$scope.sendForm.address) { - delete $rootScope.merchant; - $rootScope.merchantError = false; - $scope.isPayUri = false; - if (callback) callback(); - return; + $scope.setForm = function(to, amount, comment) { + var form = $scope.sendForm; + if (to) { + form.address.$setViewValue(to); + form.address.$isValid = true; + form.address.$render(); + $scope.lockAddress = true; } - var val = $scope.sendForm.address.$viewValue || ''; - var uri; - // If we're setting the domain, ignore the change. - if ($rootScope.merchant && $rootScope.merchant.domain && val === $rootScope.merchant.domain) { - uri = { - merchant: $rootScope.merchant.request_url - }; + + if (amount) { + form.amount.$setViewValue("" + amount); + form.amount.$isValid = true; + form.amount.$render(); + $scope.lockAmount = true; } - if (val.indexOf('bitcoin:') === 0) { - uri = new bitcore.BIP21(val).data; - } else if (/^https?:\/\//.test(val)) { - uri = { - merchant: val - }; - } - if (!uri || !uri.merchant) { - delete $rootScope.merchant; - $scope.sendForm.amount.$setViewValue(''); - $scope.sendForm.amount.$render(); - if (callback) callback(); - if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { - $rootScope.$apply(); - } + + if (comment) { + form.comment.$setViewValue(comment); + form.comment.$isValid = true; + form.comment.$render(); } }; - $scope.cancelSend = function(form) { - delete $rootScope.merchant; - $rootScope.merchantError = false; - $scope.isPayUri = false; - form.address.$setViewValue(''); - form.address.$render(); + $scope.resetForm = function(status) { + var form = $scope.sendForm; + + $scope.fetchingURL = null; + $scope._merchantData = $scope._domain = null; + + $scope.lockAddress = false; + $scope.lockAmount = false; + + $scope._amount = $scope._address = null; + + form.amount.$pristine = true; form.amount.$setViewValue(''); form.amount.$render(); + form.comment.$setViewValue(''); form.comment.$render(); form.$setPristine(); - }; - var _onChanged = function(pp) { - var value; - - if (pp) { - $scope.isPayUri = true; - var amount = (pp.data && pp.data.amount) ? pp.data.amount * 100000000 * satToUnit : 0; - $scope.commentText = pp.data.message; - if (pp.data.merchant) { - value = 'bitcoin:' + pp.address.data + '?amount=' + amount + '&r=' + pp.data.r; - } - else { - value = pp.address + ''; - $timeout(function() { - $scope.amount = amount; - }, 1000); - $scope.address = value; - } + if (form.address) { + form.address.$pristine = true; + form.address.$setViewValue(''); + form.address.$render(); } - value = value || $scope.address || ''; - var uri; + if (!txStatus.notify(status)) + $scope.error = status; - $scope.error = $scope.success = null; - // If we're setting the domain, ignore the change. - if ($rootScope.merchant && $rootScope.merchant.domain && value === $rootScope.merchant.domain) { - return; - } + $timeout(function() { + $rootScope.$digest(); + }, 1); + }; - if (value.indexOf('bitcoin:') === 0) { - uri = new bitcore.BIP21(value); - } else if (/^https?:\/\//.test(value)) { - uri = { - data : { - merchant: value - } + var $oscope = $scope; + $scope.openPPModal = function(merchantData) { + var ModalInstanceCtrl = function($scope, $modalInstance) { + $scope.md = merchantData; + $scope.alternative = $oscope._alternative; + $scope.alternativeIsoCode = $oscope.alternativeIsoCode; + $scope.isRateAvailable = $oscope.isRateAvailable; + + $scope.cancel = function() { + $modalInstance.dismiss('cancel'); }; - } - - if (!uri || !uri.data.merchant) { - if (uri && uri.address) { - var amount = (uri.data && uri.data.amount) ? uri.data.amount * 100000000 * satToUnit : 0; - var address = uri.address.data; - if (amount && address) { - $scope.isPayUri = true; - } - $timeout(function() { - $scope.amount = amount; - }, 1000); - $scope.commentText = uri.data.message; - $scope.address = address; - } - return; - } - - var apply = function() { - if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') { - $rootScope.$apply(); - } }; - - $scope.fetchingURL = uri.data.merchant; - $scope.loading = true; - apply(); - - var timeout = setTimeout(function() { - timeout = null; - $scope.fetchingURL = null; - $scope.loading = false; - $scope.sendForm.address.$setViewValue(''); - $scope.sendForm.address.$render(); - $scope.sendForm.address.$isValid = false; - $scope.error = 'Payment server timed out'; - apply(); - }, 10 * 1000); - - // Payment Protocol URI (BIP-72) - $scope.wallet.fetchPaymentRequest({ - url: uri.data.merchant - }, function(err, merchantData) { - if (!timeout) return; - clearTimeout(timeout); - - $scope.loading = false; - $scope.fetchingURL = null; - apply(); - - var balance = $rootScope.availableBalance; - var available = +(balance * w.settings.unitToSatoshi).toFixed(0); - if (merchantData && available < +merchantData.total) { - err = new Error('Insufficient funds.'); - err.amount = merchantData.total; - } - - if (err) { - if (err.amount) { - $scope.sendForm.amount.$setViewValue(+err.amount / w.settings.unitToSatoshi); - $scope.sendForm.amount.$render(); - $scope.sendForm.amount.$isValid = false; - $scope.notEnoughAmount = true; - $rootScope.merchantError = true; - var lastAddr = $scope.sendForm.address.$viewValue; - var unregister = $scope.$watch('address', function() { - if ($scope.sendForm.address.$viewValue !== lastAddr) { - delete $rootScope.merchantError; - $scope.isPayUri = false; - $scope.sendForm.amount.$setViewValue(''); - $scope.sendForm.amount.$render(); - unregister(); - apply(); - } - }); - } else { - $scope.sendForm.address.$setViewValue(''); - $scope.sendForm.address.$render(); - } - $scope.sendForm.address.$isValid = false; - copay.logger.error(err); - - $scope.error = 'Could not fetch payment request'; - - apply(); - return; - } - - var url = merchantData.request_url; - var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1]; - - merchantData.unitTotal = (+merchantData.total / w.settings.unitToSatoshi) + ''; - merchantData.expiration = new Date( - merchantData.pr.pd.expires * 1000); - merchantData.domain = domain; - - $rootScope.merchant = merchantData; - - $scope.sendForm.address.$setViewValue(domain); - $scope.sendForm.address.$render(); - $scope.sendForm.address.$isValid = true; - - $scope.sendForm.amount.$setViewValue(merchantData.unitTotal); - $scope.sendForm.amount.$render(); - $scope.sendForm.amount.$isValid = true; - - // If the address changes to a non-payment-protocol one, - // delete the `merchant` property from the scope. - var unregister = $rootScope.$watch(function() { - $scope.clearMerchant(unregister); - }); - - apply(); + $modal.open({ + templateUrl: 'views/modals/paypro.html', + windowClass: 'tiny', + controller: ModalInstanceCtrl, }); }; - if ($rootScope.pendingPayment) { - var value; - var pp = $rootScope.pendingPayment; - _onChanged(pp); - } + + $scope.setFromPayPro = function(uri) { + var w = $rootScope.wallet; + $scope.fetchingURL = uri; + $scope.loading = true; + + // Payment Protocol URI (BIP-72) + w.fetchPaymentRequest({ + url: uri + }, function(err, merchantData) { + $scope.loading = false; + $scope.fetchingURL = null; + + if (err) { + copay.logger.warn(err); + if (err.toString().match('TIMEOUT')) { + $scope.resetForm('Payment server timed out'); + } else { + $scope.resetForm(err.toString()); + } + } else { + $scope._merchantData = merchantData; + $scope._domain = merchantData.domain; + $scope.setForm(null, merchantData.unitTotal); + } + }); + }; + + $scope.setFromUri = function(uri) { + var form = $scope.sendForm; + + var parsed = new bitcore.BIP21(uri); + if (!parsed.isValid() || !parsed.address.isValid()) { + $scope.error = 'Invalid bitcoin URL'; + form.address.$isValid = false; + return uri; + }; + + var addr = parsed.address.toString(); + if (parsed.data.merchant) + return $scope.setFromPayPro(parsed.data.merchant); + + var amount = (parsed.data && parsed.data.amount) ? + (parsed.data.amount * 100000000).toFixed(0) * satToUnit : 0; + + $scope.setForm(addr, amount, parsed.data.message, true); + return addr; + }; + + $scope.onAddressChange = function(value) { + $scope.error = $scope.success = null; + if (!value) return ''; + + if (value.indexOf('bitcoin:') === 0) { + return $scope.setFromUri(value); + } else if (/^https?:\/\//.test(value)) { + return $scope.setFromPayPro(value); + } + + return value; + }; $scope.openAddressBook = function() { + var w = $rootScope.wallet; var modalInstance = $modal.open({ templateUrl: 'views/modals/address-book.html', - windowClass: 'large', - controller: function($scope, $modalInstance) { + windowClass: 'large', + controller: function($scope, $modalInstance) { - $scope.showForm = null; - $scope.addressBook = w.addressBook; + $scope.showForm = null; + $scope.addressBook = w.addressBook; - $scope.hasEntry = function() { - return _.keys($scope.addressBook).length > 0 ? true : false; - }; + $scope.hasEntry = function() { + return _.keys($scope.addressBook).length > 0 ? true : false; + }; - $scope.toggleAddressBookEntry = function(key) { - w.toggleAddressBookEntry(key); - }; + $scope.toggleAddressBookEntry = function(key) { + w.toggleAddressBookEntry(key); + }; - $scope.copyToSend = function(addr) { - $modalInstance.close(addr); - }; + $scope.copyToSend = function(addr) { + $modalInstance.close(addr); + }; - $scope.cancel = function() { - $scope.error = $scope.success = null; - $scope.toggleForm(); - }; + $scope.cancel = function() { + $scope.error = $scope.success = null; + $scope.toggleForm(); + }; - $scope.toggleForm = function() { - $scope.showForm = !$scope.showForm; - }; - - $scope.submitAddressBook = function(form) { - if (form.$invalid) { - return; - } - $timeout(function() { - var errorMsg; - var entry = { - "address": form.newaddress.$modelValue, - "label": form.newlabel.$modelValue - }; - try { - w.setAddressBook(entry.address, entry.label); - } catch (e) { - console.log('[send.js:583]',e); //TODO - errorMsg = e.message; - } - - if (errorMsg) { - $scope.error = errorMsg; - } else { - $scope.toggleForm(); - $scope.success = 'New entry has been created'; - } - $rootScope.$digest(); - }, 500); - - $timeout(function() { - $scope.error = $scope.success = null; - }, 5000); + $scope.toggleForm = function() { + $scope.showForm = !$scope.showForm; + }; + // TODO change to modal + $scope.submitAddressBook = function(form) { + if (form.$invalid) { return; - - }; + } + $timeout(function() { + var errorMsg; + var entry = { + "address": form.newaddress.$modelValue, + "label": form.newlabel.$modelValue + }; + try { + w.setAddressBook(entry.address, entry.label); + } catch (e) { + copay.logger.warn(e); + errorMsg = e.message; + } - $scope.close = function() { - $modalInstance.dismiss('cancel'); - }; - }, + if (errorMsg) { + $scope.error = errorMsg; + } else { + $scope.toggleForm(); + notification.success('Entry created', 'New addressbook entry created') + } + $rootScope.$digest(); + }, 1); + return; + }; + + $scope.close = function() { + $modalInstance.dismiss('cancel'); + }; + }, }); modalInstance.result.then(function(addr) { - $scope.address = addr; + $scope.setForm(addr); }); }; - }); diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js index b14d23d93..aeba65e86 100644 --- a/js/controllers/sidebar.js +++ b/js/controllers/sidebar.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $location, $timeout, identityService, isMobile) { +angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $location, $timeout, identityService, isMobile, go) { $scope.isMobile = isMobile.any() @@ -37,7 +37,7 @@ angular.module('copayApp.controllers').controller('SidebarController', function( $scope.switchWallet = function(wid) { $scope.walletSelection = false; identityService.setFocusedWallet(wid); - identityService.goWalletHome(); + go.walletHome(); }; $scope.toggleWalletSelection = function() { diff --git a/js/controllers/uriPayment.js b/js/controllers/uriPayment.js deleted file mode 100644 index 486a09be7..000000000 --- a/js/controllers/uriPayment.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -var bitcore = require('bitcore'); - -angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams, $timeout, $location) { - // Build bitcoinURI with querystring - var query = []; - angular.forEach($location.search(), function(value, key) { - query.push(key + "=" + value); - }); - var queryString = query ? "?" + query.join("&") : ""; - var bitcoinURI = $routeParams.data + queryString; - - $rootScope.pendingPayment = new bitcore.BIP21(bitcoinURI); - - $timeout(function() { - console.log('Redirecting to /paymentIntent'); - $location.path('/paymentIntent'); - }, 1000); - - -}); diff --git a/js/controllers/walletForPayment.js b/js/controllers/walletForPayment.js new file mode 100644 index 000000000..958f82946 --- /dev/null +++ b/js/controllers/walletForPayment.js @@ -0,0 +1,51 @@ +var bitcore = require('bitcore'); + +angular.module('copayApp.controllers').controller('walletForPaymentController', function($rootScope, $scope, $modal, identityService, go) { + + $scope.selectWallet = function(cb) { + var ModalInstanceCtrl = function($scope, $modalInstance, identityService) { + $scope.loading = true; + preconditions.checkState($rootScope.iden); + + var iden = $rootScope.iden; + iden.on('newWallet', function() { + $scope.setWallets(); + }); + + $scope.setWallets = function() { + $scope.wallets = $rootScope.iden.listWallets(); + }; + + $scope.ok = function(w) { + $modalInstance.close(); + return cb(w); + }; + + $scope.cancel = function() { + $modalInstance.close(); + return cb(); + }; + }; + + $modal.open({ + templateUrl: 'views/modals/walletSelection.html', + windowClass: 'tiny', + controller: ModalInstanceCtrl, + }); + }; + + + // INIT: (not it a function, since there is no associated html) + if (!$rootScope.pendingPayment) { + go.walletHome(); + } else { + $scope.selectWallet(function(w) { + if (w) { + identityService.setFocusedWallet(w); + go.send(); + } else { + go.walletHome(); + } + }); + }; +}); diff --git a/js/directives.js b/js/directives.js index ca4f76b2d..1723cd329 100644 --- a/js/directives.js +++ b/js/directives.js @@ -32,12 +32,6 @@ angular.module('copayApp.directives') link: function(scope, elem, attrs, ctrl) { var validator = function(value) { - // If we're setting the domain, ignore the change. - if ($rootScope.merchant && $rootScope.merchant.domain && value === $rootScope.merchant.domain) { - ctrl.$setValidity('validAddress', true); - return value; - } - // Regular url if (/^https?:\/\//.test(value)) { ctrl.$setValidity('validAddress', true); @@ -53,6 +47,11 @@ angular.module('copayApp.directives') return value; } + if (typeof value == 'undefined') { + ctrl.$pristine = true; + return; + } + // Regular Address var a = new Address(value); ctrl.$setValidity('validAddress', a.isValid() && a.network().name === $rootScope.wallet.getNetworkName()); @@ -111,14 +110,11 @@ angular.module('copayApp.directives') var str_value = ('' + value).substring(sep_index + 1); if (sep_index > 0 && str_value.length > decimals) { ctrl.$setValidity('validAmount', false); - scope.notValidAmount = true; } else { ctrl.$setValidity('validAmount', true); - scope.notValidAmount = null; } } else { ctrl.$setValidity('validAmount', false); - scope.notValidAmount = null; } return value; } diff --git a/js/models/Identity.js b/js/models/Identity.js index 85c568508..253af0d1c 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -509,6 +509,9 @@ Identity.prototype.bindWallet = function(w) { w.on('publicKeyRingUpdated', function() { Identity.storeWalletDebounced(self, w); }); + w.on('ready', function() { + Identity.storeWalletDebounced(self, w); + }); this.emitAndKeepAlive('newWallet', w.getId()); }; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 44543c349..853c449fc 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -1747,9 +1747,11 @@ Wallet.prototype._addOutputsToMerchantData = function(merchantData) { // If user is granted the privilege of choosing // their own amount, add it to the tx. - if (merchantData.total === 0 && options.amount) { - merchant.outs[0].amountSatStr = merchantData.total = outions.amount; + if (merchantData.total == "0" && options.amount) { + merchant.outs[0].amountSatStr = merchantData.total = options.amount; } + + merchantData.unitTotal = merchantData.total ? (+merchantData.total / this.settings.unitToSatoshi) + '' : 0; }; /** @@ -1793,6 +1795,7 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) { var payment_url = pd.get('payment_url'); var merchant_data = pd.get('merchant_data'); + var total = bignum('0', 10).toString(10); var merchantData = { pr: { payment_details_version: ver, @@ -1826,9 +1829,13 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) { }, expires: expires, request_url: options.url, - total: bignum('0', 10).toString(10), + domain: /^(?:https?)?:\/\/([^\/:]+).*$/.exec(options.url)[1], + total: total, + expirationDate: expires ? new Date(expires * 1000) : null, }; + this._addOutputsToMerchantData(merchantData, options.amount); + return merchantData; }; @@ -2173,9 +2180,21 @@ Wallet.prototype.spend = function(opts, cb) { var toAddress = opts.toAddress; var amountSat = opts.amountSat; var comment = opts.comment; - var url = opts.url; + var merchantData = opts.merchantData; + + + // PayPro? With given merchant data + if (opts.merchantData && !opts.toAddress) { + if (!merchantData.outs[0].address) + return cb(new Error('BADPAYPRO')); + + opts.toAddress = merchantData.outs[0].address; + opts.amountSat = parseInt(merchantData.outs[0].amountSatStr); + return self.spend(opts, cb); + } // PayPro? Fetch payment data and recurse + var url = opts.url; if (url && !opts.merchantData) { return self.fetchPaymentRequest({ url: url, @@ -2184,8 +2203,6 @@ Wallet.prototype.spend = function(opts, cb) { }, function(err, merchantData) { if (err) return cb(err); opts.merchantData = merchantData; - opts.toAddress = merchantData.outs[0].address; - opts.amountSat = parseInt(merchantData.outs[0].amountSatStr); return self.spend(opts, cb); }); } @@ -2203,13 +2220,13 @@ Wallet.prototype.spend = function(opts, cb) { try { txp = self._createTxProposal(toAddress, amountSat, comment, safeUnspent, opts.builderOpts); - } catch (e) { - log.error(e); - return cb(e); - } - if (opts.merchantData) { - txp.addMerchantData(opts.merchantData); + if (opts.merchantData) { + txp.addMerchantData(opts.merchantData); + } + } catch (e) { + log.warn(e); + return cb(e); } var ntxid = self.txProposals.add(txp); @@ -2546,7 +2563,7 @@ Wallet.prototype.isComplete = function() { /** * @desc Sets the version of this wallet object - * + * * @param {string} version - the new version for the wallet */ Wallet.prototype.setVersion = function(version) { @@ -2669,7 +2686,6 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) { tx.sentTs = proposal.sentTs; tx.merchant = proposal.merchant; tx.peerActions = proposal.peerActions; - tx.merchant = proposal.merchant; tx.paymentAckMemo = proposal.paymentAckMemo; tx.actionList = self._getActionList(proposal); } diff --git a/js/routes.js b/js/routes.js index c295994b4..9708f0276 100644 --- a/js/routes.js +++ b/js/routes.js @@ -22,11 +22,14 @@ angular template: " ", // just fire controller controller: 'EmailConfirmationController', }) - .when('/uri-payment/:data', { - templateUrl: 'views/uri-payment.html' - }) - .when('/paymentIntent', { - templateUrl: 'views/paymentIntent.html', + // Payment intents come here. + .when('/uri-payment/:data', { + template: " ", // just fire controller + controller: 'paymentUriController', + }) + .when('/selectWalletForPayment', { + template: " ", // just fire controller + controller: 'walletForPaymentController', logged: true }) .when('/join', { @@ -124,6 +127,7 @@ angular } $rootScope.$on('$routeChangeStart', function(event, next, current) { + if (!ls || ls.length < 1) { $location.path('unsupported'); } else { @@ -131,7 +135,8 @@ angular $idle.unwatch(); $location.path('/'); } - if ($rootScope.wallet && !$rootScope.wallet.isComplete() && next.walletShouldBeComplete) { + if ($rootScope.wallet && !$rootScope.wallet.isComplete() + && next.walletShouldBeComplete) { $location.path('/copayers'); } } diff --git a/js/services/go.js b/js/services/go.js index 550693288..4801fb111 100644 --- a/js/services/go.js +++ b/js/services/go.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.services').factory('go', function($window, $location) { +angular.module('copayApp.services').factory('go', function($window, $rootScope, $location) { var root = {}; var hideSidebars = function() { @@ -44,7 +44,7 @@ angular.module('copayApp.services').factory('go', function($window, $location) { var ref = window.open(url, '_blank', 'location=no'); }; - root.go = function(path) { + root.path = function(path) { var parts = path.split('#'); $location.path(parts[0]); if (parts[1]) @@ -56,5 +56,33 @@ angular.module('copayApp.services').factory('go', function($window, $location) { toggleSidebar(invert); }; + root.walletHome = function() { + var w = $rootScope.wallet; + preconditions.checkState(w); + $rootScope.starting = false; + + if (!w.isComplete()) { + root.path('copayers'); + } else { + if ($rootScope.pendingPayment) { + root.path('selectWalletForPayment'); + } else { + root.path('homeWallet'); + } + } + }; + + root.home = function() { + if ($rootScope.iden) + root.walletHome(); + else + root.path('/'); + }; + + + root.send = function() { + $location.path('send'); + }; + return root; }); diff --git a/js/services/identityService.js b/js/services/identityService.js index 711d15301..2a8cedf46 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -27,23 +27,6 @@ angular.module('copayApp.services') }); }; - // TODO should be on 'walletService' or 'go' - root.goWalletHome = function() { - var w = $rootScope.wallet; - if (w) { - $rootScope.starting = false; - if (!w.isComplete()) { - go.go('copayers'); - } else { - if ($rootScope.pendingPayment) { - go.go('paymentIntent'); - } else { - go.go('homeWallet'); - } - } - } - }; - root.create = function(email, password, cb) { copay.Identity.create({ email: email, @@ -133,10 +116,6 @@ angular.module('copayApp.services') $rootScope.iden = iden; }; - root.setPaymentWallet = function(w) { - root.setFocusedWallet(w); - $location.path('/send'); - }; root.noFocusedWallet = function() { $rootScope.wallet = null; @@ -296,7 +275,7 @@ angular.module('copayApp.services') if (wid == iden.getLastFocusedWalletId()) { copay.logger.debug('GOT Focused wallet:', w.getName()); root.setFocusedWallet(w, true); - root.goWalletHome(); + go.walletHome(); } // At the end (after all handlers are in place)...start the wallet. diff --git a/js/services/txStatus.js b/js/services/txStatus.js new file mode 100644 index 000000000..a5a539345 --- /dev/null +++ b/js/services/txStatus.js @@ -0,0 +1,39 @@ +'use strict'; + +angular.module('copayApp.services').factory('txStatus', function($modal) { + var root = {}; + + root.notify = function(status) { + var msg; + if (status == copay.Wallet.TX_BROADCASTED) + msg = 'Transaction broadcasted!'; + else if (status == copay.Wallet.TX_PROPOSAL_SENT) + msg = 'Transaction proposal created'; + else if (status == copay.Wallet.TX_SIGNED) + msg = 'Transaction proposal was signed'; + else if (status == copay.Wallet.TX_SIGNED_AND_BROADCASTED) + msg = 'Transaction signed and broadcasted!'; + else if (status == 'txRejected') + msg = 'Transaction was rejected!'; + + if (msg) + root.openModal(msg); + return msg ? true : false; + }; + + root.openModal = function(statusStr) { + var ModalInstanceCtrl = function($scope, $modalInstance) { + $scope.statusStr = statusStr; + $scope.cancel = function() { + $modalInstance.dismiss('cancel'); + }; + }; + $modal.open({ + templateUrl: 'views/modals/tx-status.html', + windowClass: 'tiny', + controller: ModalInstanceCtrl, + }); + }; + + return root; +}); diff --git a/package.json b/package.json index b53ed4117..1a1edbae9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "copay", "description": "A multisignature wallet", "author": "BitPay", - "version": "0.8.3", + "version": "0.8.4", "keywords": [ "wallet", "copay", diff --git a/test/mocks/FakeBlockchainSocket.js b/test/mocks/FakeBlockchainSocket.js index 941013ae6..f888c8150 100644 --- a/test/mocks/FakeBlockchainSocket.js +++ b/test/mocks/FakeBlockchainSocket.js @@ -26,7 +26,7 @@ var inherits = function(ctor, superCtor) { inherits(FakeSocket, EventEmitter); -FakeSocket.prototype.removeEventListener = function() { +FakeSocket.prototype.removeListener = function() { return; } diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 606d9c1f3..07df361de 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -195,16 +195,16 @@ describe("Unit: Controllers", function() { scope.model = { newaddress: null, newlabel: null, - address: null, - amount: null + _address: null, + _amount: null }; $compile(element)(scope); var element2 = angular.element( '
' ); @@ -213,7 +213,7 @@ describe("Unit: Controllers", function() { $scope: scope, $modal: {}, }); - + scope.init(); scope.$digest(); form = scope.form; sendForm = scope.form2; @@ -221,11 +221,11 @@ describe("Unit: Controllers", function() { })); it('should have a SendController controller', function() { - expect(scope.loading).equal(false); + should.exist(scope.submitForm); }); it('should have a title', function() { - expect(scope.title).equal('Create Transaction Proposal'); + expect(scope.title); }); it('should validate address with network', function() { @@ -302,7 +302,8 @@ describe("Unit: Controllers", function() { scope.rateService.whenAvailable(function() { sendForm.amount.$setViewValue(1e6); scope.$digest(); - expect(scope.alternative).to.equal(2); + expect(scope._amount).to.equal(1e6); + expect(scope.__alternative).to.equal(2); done(); }); }); @@ -310,7 +311,8 @@ describe("Unit: Controllers", function() { scope.rateService.whenAvailable(function() { sendForm.alternative.$setViewValue(2); scope.$digest(); - expect(scope.amount).to.equal(1e6); + expect(scope.__alternative).to.equal(2); + expect(scope._amount).to.equal(1e6); done(); }); }); @@ -517,7 +519,7 @@ describe("Unit: Controllers", function() { }); }); - describe('UriPayment Controller', function() { + describe('paymentUriController Controller', function() { var what; beforeEach(inject(function($controller, $rootScope, $location) { scope = $rootScope.$new(); @@ -528,7 +530,7 @@ describe("Unit: Controllers", function() { amount: 0.1, message: "a bitcoin donation" }; - what = $controller('UriPaymentController', { + what = $controller('paymentUriController', { $scope: scope, $routeParams: routeParams, $location: { @@ -546,9 +548,7 @@ describe("Unit: Controllers", function() { it('should parse url correctly', function() { should.exist(what); should.exist(scope.pendingPayment); - scope.pendingPayment.address.data.should.equal('19mP9FKrXqL46Si58pHdhGKow88SUPy1V8'); - scope.pendingPayment.data.amount.should.equal(0.1); - scope.pendingPayment.data.message.should.equal('a bitcoin donation'); + scope.pendingPayment.should.equal('bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8?amount=0.1&message=a bitcoin donation'); }); }); diff --git a/test/unit/directives/directivesSpec.js b/test/unit/directives/directivesSpec.js index 65c9091de..d8d60c304 100644 --- a/test/unit/directives/directivesSpec.js +++ b/test/unit/directives/directivesSpec.js @@ -94,9 +94,6 @@ describe("Unit: Testing Directives", function() { form.amount.$setViewValue(800); expect(form.amount.$invalid).to.equal(false); form.amount.$setViewValue(900); - expect($scope.notValidAmount).to.equal(null); - form.amount.$setViewValue(0.44); - expect($scope.notValidAmount).to.equal(null); }); @@ -105,8 +102,6 @@ describe("Unit: Testing Directives", function() { expect(form.amount.$invalid).to.equal(true); form.amount.$setViewValue(999999999999); expect(form.amount.$invalid).to.equal(true); - form.amount.$setViewValue(0.333); - expect($scope.notValidAmount).to.equal(true); }); }); @@ -134,19 +129,15 @@ describe("Unit: Testing Directives", function() { it('should validate', function() { form.amount.$setViewValue(0.01); - expect($scope.notValidAmount).to.equal(null); expect(form.amount.$invalid).to.equal(false); form.amount.$setViewValue(0.039); - expect($scope.notValidAmount).to.equal(null); expect(form.amount.$invalid).to.equal(false); form.amount.$setViewValue(100292.039); - expect($scope.notValidAmount).to.equal(null); expect(form.amount.$invalid).to.equal(false); }); it('should not validate', function() { form.amount.$setViewValue(0.039998888888888); - expect($scope.notValidAmount).to.equal(true); expect(form.amount.$invalid).to.equal(true); form.amount.$setViewValue(0); expect(form.amount.$invalid).to.equal(true); diff --git a/views/copayers.html b/views/copayers.html index 12c4a6a04..efea1ce53 100644 --- a/views/copayers.html +++ b/views/copayers.html @@ -1,6 +1,4 @@ -