From 16876691491302bfd8e1196a056edba330c60cd3 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 23 Nov 2016 11:23:19 -0300 Subject: [PATCH] set send max --- src/js/controllers/amount.js | 81 ++----- src/js/controllers/confirm.js | 212 +++++++++++++------ src/js/directives/sendMaxSelector.js | 26 +++ src/js/routes.js | 2 +- src/js/services/onGoingProcess.js | 2 +- src/js/services/walletService.js | 24 +-- src/sass/views/includes/sendMaxSelector.scss | 21 ++ src/sass/views/views.scss | 1 + www/views/amount.html | 16 +- www/views/confirm.html | 25 +-- www/views/includes/sendMaxSelector.html | 12 ++ 11 files changed, 250 insertions(+), 172 deletions(-) create mode 100644 src/js/directives/sendMaxSelector.js create mode 100644 src/sass/views/includes/sendMaxSelector.scss create mode 100644 www/views/includes/sendMaxSelector.html diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index faa745891..b2b5e54ae 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('amountController', function($rootScope, $scope, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, bitpayCardService, popupService, bwcError, payproService, amazonService, profileService, bitcore, walletService, feeService) { +angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, bitpayCardService, popupService, bwcError, payproService, profileService, bitcore, amazonService) { var unitToSatoshi; var satToUnit; var unitDecimals; @@ -53,8 +53,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ $timeout(function() { $scope.$apply(); - }, 10); - + }); }); var config = configService.getSync().wallet.settings; @@ -78,74 +77,22 @@ angular.module('copayApp.controllers').controller('amountController', function($ $timeout(function() { $ionicScrollDelegate.resize(); - }, 10); + }); }); - $scope.getSendMaxInfo = function(wallet) { - ongoingProcess.set('gettingFeeLevels', true); - feeService.getCurrentFeeValue($scope.network, function(err, fee) { - ongoingProcess.set('gettingFeeLevels', false); - if (err) { - popupService.showAlert(gettextCatalog.getString('Error'), err.message); - return; - } - - var config = configService.getSync(); - - ongoingProcess.set('retrivingInputs', true); - walletService.getSendMaxInfo(wallet, { - feePerKb: fee, - excludeUnconfirmedUtxos: !config.wallet.spendUnconfirmed, - returnInputs: true, - }, function(err, resp) { - ongoingProcess.set('retrivingInputs', false); - if (err) { - popupService.showAlert(gettextCatalog.getString('Error'), err); - return; - } - - if (resp.amount == 0) { - popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); - return; - } - - var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees", { - fee: txFormatService.formatAmount(resp.fee) + ' ' + $scope.unitName - }); - var warningMsg = verifyExcludedUtxos(); - - if (!lodash.isEmpty(warningMsg)) - msg += '. \n' + warningMsg; - - popupService.showConfirm(null, msg, 'Ok', gettextCatalog.getString('Cancel'), function(result) { - if (!result) return; - var amount = (resp.amount * satToUnit).toFixed(0); - - $scope.amount = amount; - processAmount(amount); - }); - - function verifyExcludedUtxos() { - var warningMsg = []; - if (resp.utxosBelowFee > 0) { - warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", { - amountBelowFeeStr: txFormatService.formatAmount(resp.amountBelowFee) + ' ' + $scope.unitName - })); - } - - if (resp.utxosAboveMaxSize > 0) { - warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded", { - amountAboveMaxSizeStr: txFormatService.formatAmount(resp.amountAboveMaxSize) + ' ' + $scope.unitName - })); - } - return warningMsg.join('\n'); - }; - }); - }); + $scope.showSendMaxSelector = function() { + $scope.sendMax = true; }; - $scope.showWalletSelector = function() { - $scope.showWallets = true; + $scope.setSendMax = function() { + $state.transitionTo('tabs.send.confirm', { + isWallet: $scope.isWallet, + toAmount: null, + toAddress: $scope.toAddress, + toName: $scope.toName, + toEmail: $scope.toEmail, + useSendMax: true, + }); }; $scope.toggleAlternative = function() { diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index 4919263d8..6ccdb9981 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -1,7 +1,9 @@ 'use strict'; -angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, profileService, bitcore, gettext, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, amazonService) { +angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, profileService, bitcore, gettext, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, amazonService) { var cachedTxp = {}; + var amountStr; + var toAmount; var isChromeApp = platformInfo.isChromeApp; var countDown = null; var giftCardAmountUSD; @@ -12,7 +14,6 @@ angular.module('copayApp.controllers').controller('confirmController', function( $ionicConfig.views.swipeBackEnabled(false); $scope.$on("$ionicView.beforeEnter", function(event, data) { - // Amazon.com Gift Card parameters $scope.isGiftCard = data.stateParams.isGiftCard; giftCardAmountUSD = data.stateParams.giftCardAmountUSD; @@ -20,102 +21,175 @@ angular.module('copayApp.controllers').controller('confirmController', function( giftCardInvoiceTime = data.stateParams.giftCardInvoiceTime; giftCardUUID = data.stateParams.giftCardUUID; + toAmount = data.stateParams.toAmount; $scope.isWallet = data.stateParams.isWallet; $scope.cardId = data.stateParams.cardId; - $scope.toAmount = data.stateParams.toAmount; $scope.toAddress = data.stateParams.toAddress; $scope.toName = data.stateParams.toName; $scope.toEmail = data.stateParams.toEmail; $scope.description = data.stateParams.description; $scope.paypro = data.stateParams.paypro; + $scope.useSendMax = data.stateParams.useSendMax; + $scope.insuffientFunds = false; + $scope.noMatchingWallet = false; $scope.paymentExpired = { value: false }; $scope.remainingTimeStr = { value: null }; - initConfirm(); - }); - - var initConfirm = function() { - // TODO (URL , etc) - if (!$scope.toAddress || !$scope.toAmount) { - $log.error('Bad params at amount'); - throw ('bad params'); - } var config = configService.getSync().wallet; $scope.feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal'; - $scope.toAmount = parseInt($scope.toAmount); - $scope.amountStr = txFormatService.formatAmountStr($scope.toAmount); - $scope.displayAmount = getDisplayAmount($scope.amountStr); - $scope.displayUnit = getDisplayUnit($scope.amountStr); + $scope.network = (new bitcore.Address($scope.toAddress)).network.name; - var networkName = (new bitcore.Address($scope.toAddress)).network.name; - $scope.network = networkName; - - $scope.insuffientFunds = false; - $scope.noMatchingWallet = false; - - var wallets = profileService.getWallets({ + $scope.wallets = profileService.getWallets({ onlyComplete: true, - network: networkName, + network: $scope.network, n: $scope.isGiftCard ? true : false }); - if (!wallets || !wallets.length) { + if (!$scope.wallets || !$scope.wallets.length) { $scope.noMatchingWallet = true; + } else { + $scope.wallet = $scope.wallets[0]; } + if (!$scope.useSendMax) initConfirm(); + else $scope.getSendMaxInfo(); + }); + + var initConfirm = function() { + toAmount = parseInt(toAmount); + amountStr = txFormatService.formatAmountStr(toAmount); + $scope.displayAmount = getDisplayAmount(amountStr); + $scope.displayUnit = getDisplayUnit(amountStr); + var filteredWallets = []; var index = 0; var enoughFunds = false; - lodash.each(wallets, function(w) { + lodash.each($scope.wallets, function(w) { walletService.getStatus(w, {}, function(err, status) { if (err || !status) { $log.error(err); } else { w.status = status; if (!status.availableBalanceSat) $log.debug('No balance available in: ' + w.name); - if (status.availableBalanceSat > $scope.toAmount) { + if (status.availableBalanceSat > toAmount) { filteredWallets.push(w); enoughFunds = true; } } - if (++index == wallets.length) { + if (++index == $scope.wallets.length) { if (!lodash.isEmpty(filteredWallets)) { $scope.wallets = lodash.clone(filteredWallets); setWallet($scope.wallets[0]); } else { - - if (!enoughFunds) - $scope.insuffientFunds = true; - + if (!enoughFunds) $scope.insuffientFunds = true; $log.warn('No wallet available to make the payment'); - $timeout(function() { - $scope.$apply(); - }); } } }); }); - txFormatService.formatAlternativeStr($scope.toAmount, function(v) { + txFormatService.formatAlternativeStr(toAmount, function(v) { $scope.alternativeAmountStr = v; }); - if($scope.paypro) { - _paymentTimeControl($scope.paypro.expires); - } + if ($scope.paypro) _paymentTimeControl($scope.paypro.expires); $timeout(function() { $scope.$apply(); }); }; + $scope.getSendMaxInfo = function() { + ongoingProcess.set('gettingFeeLevels', true); + feeService.getCurrentFeeValue($scope.network, function(err, feePerKb) { + ongoingProcess.set('gettingFeeLevels', false); + if (err) { + popupService.showAlert(gettextCatalog.getString('Error'), err.message); + return; + } + + var config = configService.getSync().wallet; + var unitName = config.settings.unitName; + var unitToSatoshi = config.settings.unitToSatoshi; + var satToUnit = 1 / unitToSatoshi; + var unitDecimals = config.settings.unitDecimals; + + ongoingProcess.set('retrievingInputs', true); + walletService.getSendMaxInfo($scope.wallet, { + feePerKb: feePerKb, + excludeUnconfirmedUtxos: !config.spendUnconfirmed, + returnInputs: true, + }, function(err, resp) { + ongoingProcess.set('retrievingInputs', false); + if (err) { + $scope.insuffientFunds = true; + popupService.showAlert(gettextCatalog.getString('Error'), err); + return; + } + + if (resp.amount == 0) { + $scope.insuffientFunds = true; + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); + return; + } + + $scope.sendMaxInfo = { + sendMax: true, + inputs: resp.inputs, + fee: resp.fee, + feePerKb: feePerKb, + }; + toAmount = parseFloat((resp.amount * satToUnit).toFixed(unitDecimals)); + + var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees", { + fee: txFormatService.formatAmount(resp.fee) + ' ' + unitName + }); + var warningMsg = verifyExcludedUtxos(); + + if (!lodash.isEmpty(warningMsg)) + msg += '. \n' + warningMsg; + + popupService.showConfirm(null, msg, 'Ok', gettextCatalog.getString('Cancel'), function(result) { + if (!result) return; + + var amount = txFormatService.formatAmount(resp.amount, true); + $scope.displayAmount = amount; + $scope.displayUnit = unitName; + $scope.fee = txFormatService.formatAmount($scope.sendMaxInfo.fee) + ' ' + unitName; + + createTx($scope.wallet, true, function(err, txp) { + if (err) return; + cachedTxp[$scope.wallet.id] = txp; + apply(txp); + }); + }); + + function verifyExcludedUtxos() { + var warningMsg = []; + if (resp.utxosBelowFee > 0) { + warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", { + amountBelowFeeStr: txFormatService.formatAmount(resp.amountBelowFee) + ' ' + unitName + })); + } + + if (resp.utxosAboveMaxSize > 0) { + warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded", { + amountAboveMaxSizeStr: txFormatService.formatAmount(resp.amountAboveMaxSize) + ' ' + unitName + })); + } + return warningMsg.join('\n'); + }; + }); + }); + }; + $scope.$on('accepted', function(event) { $scope.approve(); }); @@ -134,10 +208,10 @@ angular.module('copayApp.controllers').controller('confirmController', function( }; $scope.onWalletSelect = function(wallet) { - setWallet(wallet); + if (!$scope.useSendMax) setWallet(wallet); + else $scope.getSendMaxInfo(); }; - $scope.showDescriptionPopup = function() { var message = gettextCatalog.getString('Add description'); var opts = { @@ -154,11 +228,11 @@ angular.module('copayApp.controllers').controller('confirmController', function( function getDisplayAmount(amountStr) { return amountStr.split(' ')[0]; - } + }; function getDisplayUnit(amountStr) { return amountStr.split(' ')[1]; - } + }; function _paymentTimeControl(expirationTime) { $scope.paymentExpired.value = false; @@ -180,7 +254,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( var m = Math.floor(totalSecs / 60); var s = totalSecs % 60; $scope.remainingTimeStr.value = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2); - } + }; function setExpiredValues() { $scope.paymentExpired.value = true; @@ -189,8 +263,8 @@ angular.module('copayApp.controllers').controller('confirmController', function( $timeout(function() { $scope.$apply(); }); - } - } + }; + }; function setWallet(wallet, delayed) { var stop; @@ -200,7 +274,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( $timeout(function() { $ionicScrollDelegate.resize(); $scope.$apply(); - }, 10); + }); if (stop) { $timeout.cancel(stop); @@ -218,7 +292,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( }); }, delayed ? 2000 : 1); } - } + }; var setSendError = function(msg) { $scope.sendStatus = ''; @@ -232,17 +306,16 @@ angular.module('copayApp.controllers').controller('confirmController', function( $scope.fee = txFormatService.formatAmountStr(txp.fee); $scope.txp = txp; $scope.$apply(); - } + }; var createTx = function(wallet, dryRun, cb) { var config = configService.getSync().wallet; var currentSpendUnconfirmed = config.spendUnconfirmed; - var outputs = []; - var paypro = $scope.paypro; var toAddress = $scope.toAddress; - var toAmount = $scope.toAmount; var description = $scope.description; + var unitToSatoshi = config.settings.unitToSatoshi; + var unitDecimals = config.settings.unitDecimals; // ToDo: use a credential's (or fc's) function for this if (description && !wallet.credentials.sharedEncryptingKey) { @@ -257,28 +330,29 @@ angular.module('copayApp.controllers').controller('confirmController', function( return setSendError(msg); } - outputs.push({ - 'toAddress': toAddress, - 'amount': toAmount, - 'message': description - }); - var txp = {}; + var amount; + if ($scope.useSendMax) amount = parseFloat((toAmount * unitToSatoshi)); + else amount = toAmount; - // TODO - if (!lodash.isEmpty($scope.sendMaxInfo)) { - txp.sendMax = true; + txp.outputs = [{ + 'toAddress': toAddress, + 'amount': amount, + 'message': description + }]; + + if ($scope.sendMaxInfo) { txp.inputs = $scope.sendMaxInfo.inputs; - txp.fee = $scope.sendMaxInfo.fee; - } + txp.feePerKb = $scope.sendMaxInfo.feePerKb; + } else + txp.feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal'; - txp.outputs = outputs; txp.message = description; - if(paypro) { + + if (paypro) { txp.payProUrl = paypro.url; } - txp.excludeUnconfirmedUtxos = config.spendUnconfirmed ? false : true; - txp.feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal'; + txp.excludeUnconfirmedUtxos = !currentSpendUnconfirmed; txp.dryRun = dryRun; walletService.createTx(wallet, txp, function(err, ctxp) { @@ -334,7 +408,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( var isCordova = $scope.isCordova; var bigAmount = parseFloat(txFormatService.formatToUSD(txp.amount)) > 20; var message = gettextCatalog.getString('Sending {{amountStr}} from your {{name}} wallet', { - amountStr: $scope.amountStr, + amountStr: amountStr, name: wallet.name }); var okText = gettextCatalog.getString('Confirm'); @@ -375,7 +449,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( } else if (showName) { $scope.sendStatus = showName; } - } + }; $scope.statusChangeHandler = statusChangeHandler; diff --git a/src/js/directives/sendMaxSelector.js b/src/js/directives/sendMaxSelector.js new file mode 100644 index 000000000..8cb7f397d --- /dev/null +++ b/src/js/directives/sendMaxSelector.js @@ -0,0 +1,26 @@ +'use strict'; + +angular.module('copayApp.directives') + .directive('sendMaxSelector', function($timeout) { + return { + restrict: 'E', + templateUrl: 'views/includes/sendMaxSelector.html', + transclude: true, + scope: { + show: '=sendMaxSelectorShow', + wallet: '=sendMaxSelectorWallet', + onSelect: '=sendMaxSelectorOnSelect' + }, + link: function(scope, element, attrs) { + scope.hide = function() { + scope.show = false; + }; + scope.setSendMax = function() { + $timeout(function() { + scope.hide(); + }, 100); + scope.onSelect(); + }; + } + }; + }); diff --git a/src/js/routes.js b/src/js/routes.js index d86d0c8ab..4ef27d0a8 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -286,7 +286,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.confirm', { - url: '/confirm/:isWallet/:toAddress/:toName/:toAmount/:toEmail/:description', + url: '/confirm/:isWallet/:toAddress/:toName/:toAmount/:toEmail/:description/:useSendMax', views: { 'tab-send@tabs': { controller: 'confirmController', diff --git a/src/js/services/onGoingProcess.js b/src/js/services/onGoingProcess.js index 0d6977173..cdcb501d7 100644 --- a/src/js/services/onGoingProcess.js +++ b/src/js/services/onGoingProcess.js @@ -25,7 +25,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti 'recreating': gettext('Recreating Wallet...'), 'rejectTx': gettext('Rejecting payment proposal'), 'removeTx': gettext('Deleting payment proposal'), - 'retrivingInputs': gettext('Retrieving inputs information'), + 'retrievingInputs': gettext('Retrieving inputs information'), 'scanning': gettext('Scanning Wallet funds...'), 'sendingTx': gettext('Sending transaction'), 'signingTx': gettext('Signing transaction'), diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 01c8f849e..773c8542e 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -562,20 +562,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (lodash.isEmpty(txp) || lodash.isEmpty(wallet)) return cb('MISSING_PARAMETER'); - if (txp.sendMax) { - wallet.createTxProposal(txp, function(err, createdTxp) { - if (err) return cb(err); - else return cb(null, createdTxp); - }); - } else { - wallet.createTxProposal(txp, function(err, createdTxp) { - if (err) return cb(err); - else { - $log.debug('Transaction created'); - return cb(null, createdTxp); - } - }); - } + wallet.createTxProposal(txp, function(err, createdTxp) { + if (err) return cb(err); + else { + $log.debug('Transaction created'); + return cb(null, createdTxp); + } + }); }; root.publishTx = function(wallet, txp, cb) { @@ -1086,8 +1079,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim root.getSendMaxInfo = function(wallet, opts, cb) { opts = opts || {}; wallet.getSendMaxInfo(opts, function(err, res) { - if (err) return cb(err); - return cb(null, res); + return cb(err, res); }); }; diff --git a/src/sass/views/includes/sendMaxSelector.scss b/src/sass/views/includes/sendMaxSelector.scss new file mode 100644 index 000000000..6463fdb93 --- /dev/null +++ b/src/sass/views/includes/sendMaxSelector.scss @@ -0,0 +1,21 @@ +send-max-selector { + + .bp-action-sheet__sheet { + padding-left: 2rem; + padding-right: .75rem; + } + + .max-selector { + a.item { + border: none; + padding-top: 20px; + padding-bottom: 20px; + span { + &.item-note { + color: #3A3A3A; + font-family: "Roboto-Light"; + } + } + } + } +} diff --git a/src/sass/views/views.scss b/src/sass/views/views.scss index 1e88f8af0..af863d518 100644 --- a/src/sass/views/views.scss +++ b/src/sass/views/views.scss @@ -38,6 +38,7 @@ @import "includes/tx-details"; @import "includes/txp-details"; @import "includes/tx-status"; +@import "includes/sendMaxSelector"; @import "includes/walletSelector"; @import "integrations/coinbase"; @import "integrations/glidera"; diff --git a/www/views/amount.html b/www/views/amount.html index 8ba5f03ba..24df99096 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -5,6 +5,11 @@ + + + @@ -93,10 +98,9 @@ - - + + diff --git a/www/views/confirm.html b/www/views/confirm.html index 85d9006d4..3eaa5b5c3 100644 --- a/www/views/confirm.html +++ b/www/views/confirm.html @@ -12,10 +12,11 @@
- Sending + Sending + Sending maximum amount
-
{{displayAmount}} {{displayUnit}}
+
{{displayAmount || '...'}} {{displayUnit}}
{{alternativeAmountStr}}
@@ -49,12 +50,6 @@ Multiple recipients --> -
- No wallets available -
-
- Insufficient funds -
From
@@ -65,30 +60,36 @@
- + Add Memo {{description}} -
+
Fee: {{feeLevel}} {{fee || '...'}}
+
+ No wallets available +
+
+ Insufficient funds +
Click to pay Slide to pay diff --git a/www/views/includes/sendMaxSelector.html b/www/views/includes/sendMaxSelector.html new file mode 100644 index 000000000..29241b5b1 --- /dev/null +++ b/www/views/includes/sendMaxSelector.html @@ -0,0 +1,12 @@ + +
Shortcuts
+ + + Send max amount + {{wallet.status.availableBalanceStr}} + + +
+ Cancel +
+