diff --git a/public/views/modals/addressbook.html b/public/views/modals/addressbook.html index 6c83ae286..d10731fa1 100644 --- a/public/views/modals/addressbook.html +++ b/public/views/modals/addressbook.html @@ -1,6 +1,6 @@ - + diff --git a/public/views/modals/paypro.html b/public/views/modals/paypro.html index 11d977f61..393deb8b2 100644 --- a/public/views/modals/paypro.html +++ b/public/views/modals/paypro.html @@ -12,7 +12,7 @@ - + {{unitTotal}} {{unitName}} diff --git a/public/views/modals/tx-details.html b/public/views/modals/tx-details.html index 8ce4dd4ed..181500889 100644 --- a/public/views/modals/tx-details.html +++ b/public/views/modals/tx-details.html @@ -1,20 +1,19 @@ - - - - - - - Back - - - - - Transaction - - - + + + + + Close + + + + + Transaction + + + - + + diff --git a/public/views/modals/txp-details.html b/public/views/modals/txp-details.html index 847585874..1310923b6 100644 --- a/public/views/modals/txp-details.html +++ b/public/views/modals/txp-details.html @@ -1,193 +1,181 @@ - - - - - Back - - - - - Payment Proposal - - - - - - {{tx.amountStr}} - {{tx.alternativeAmountStr}} - - - - - - Multiple recipients - - - + + + + + Close + + - - - - {{error|translate}} - - + + Payment Proposal + + - - - The payment was removed by creator - - - - - - - - Reject - - - - - - Accept - - - - - - - Payment accepted, but not yet broadcasted - - - Broadcast Payment - - - - Payment accepted. It will be broadcasted by Glidera. In case there is a problem, it can be deleted 6 hours after it was created. - - - Payment Sent - - - Payment Rejected - + + + + {{loading}} - Details - - - Note - {{tx.message}} - + + + + {{tx.amountStr}} + {{tx.alternativeAmountStr}} + + + + + Multiple recipients + + - - Recipients - {{tx.recipientCount}} - - - - + + + {{error}} + - - + + + The payment was removed by creator + + - - Fee - {{tx.feeStr}} - - - Time - - {{ (tx.ts || tx.createdOn ) * 1000 | amTimeAgo}} - - - - Created by - {{tx.creatorName}} - - - - Warning: this transaction has unconfirmed inputs - + + + + + Reject + + + + + + Accept + + + - - Payment details - - - To - - - {{tx.paypro.domain}} - {{tx.paypro.domain}} + + + Payment accepted, but not yet broadcasted + + + + Broadcast Payment + + + + Payment accepted. It will be broadcasted by Glidera. In case there is a problem, it can be deleted 6 hours after it was created. + + Payment Sent + Payment Rejected + + + + Details + + + + Note + {{tx.message}} + + + + Recipients + {{tx.recipientCount}} + + - - - - - Expired - - {{tx.paypro.expires * 1000 | amTimeAgo }} - - - - Expires - - {{expires}} - - - - Merchant Message - {{tx.paypro.pr.pd.memo}} - - - + - - - - {{tx.requiredSignatures}}/{{tx.walletN}} + + + + + Fee + {{tx.feeStr}} + + + + Time + + {{ (tx.ts || tx.createdOn ) * 1000 | amTimeAgo}} + + + + + Created by + {{tx.creatorName}} + + + + + Warning: this transaction has unconfirmed inputs - Participants - - - - - - - - - {{ac.copayerName}} ({{'Me'|translate}}) - - - - - - * A payment proposal can be deleted if 1) you are the creator, and no other copayer has signed, or 2) 24 hours have passed since the proposal was created. + + Payment details + + + To + + + {{tx.paypro.domain}} + {{tx.paypro.domain}} + + + + + + Expired + + {{tx.paypro.expires * 1000 | amTimeAgo }} + + + + Expires + + {{expires}} + + + + Merchant Message + {{tx.paypro.pr.pd.memo}} + + + + + + + + {{tx.requiredSignatures}}/{{tx.walletN}} + + Participants + + + + + + + + + {{ac.copayerName}} ({{'Me'}}) + + + + + + + * A payment proposal can be deleted if 1) you are the creator, and no other copayer has signed, or 2) 24 hours have passed since the proposal was created. + + + + Delete Payment Proposal + + - - - Delete Payment Proposal - - - - - + + diff --git a/public/views/walletHome.html b/public/views/walletHome.html index f97a6cf48..36cf65d27 100644 --- a/public/views/walletHome.html +++ b/public/views/walletHome.html @@ -77,7 +77,7 @@ {{index.totalBalanceStr}} {{index.totalBalanceAlternative}} {{index.alternativeIsoCode}} - Pending Confirmation:{{index.pendingAmountStr}} + Pending Confirmation: {{index.pendingAmountStr}} diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js index f6c8dd754..e35fb6ba9 100644 --- a/src/js/controllers/index.js +++ b/src/js/controllers/index.js @@ -460,7 +460,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r } $rootScope.$apply(); }); - }); + }, 300); }; // This handles errors from BWS/index which normally diff --git a/src/js/controllers/modals/txpDetails.js b/src/js/controllers/modals/txpDetails.js new file mode 100644 index 000000000..ea021b758 --- /dev/null +++ b/src/js/controllers/modals/txpDetails.js @@ -0,0 +1,240 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, txStatus, $ionicScrollDelegate, txFormatService, fingerprintService, bwsError, isChromeApp, gettextCatalog, lodash, profileService, walletService) { + var self = $scope.self; + var tx = $scope.tx; + var copayers = $scope.copayers; + var isGlidera = $scope.isGlidera; + var now = Math.floor(Date.now() / 1000); + var fc = profileService.focusedClient; + $scope.loading = null; + $scope.copayerId = fc.credentials.copayerId; + $scope.isShared = fc.credentials.n > 1; + $scope.canSign = fc.canSign() || fc.isPrivKeyExternal(); + $scope.color = fc.backgroundColor; + + checkPaypro(); + + // ToDo: use tx.customData instead of tx.message + if (tx.message === 'Glidera transaction' && isGlidera) { + tx.isGlidera = true; + if (tx.canBeRemoved) { + tx.canBeRemoved = (Date.now() / 1000 - (tx.ts || tx.createdOn)) > GLIDERA_LOCK_TIME; + } + } + + $scope.sign = function(txp) { + $scope.error = null; + $scope.loading = 'Signing Transaction'; + + fingerprintService.check(fc, function(err) { + if (err) { + $scope.error = err; + $scope.loading = null; + return; + } + + handleEncryptedWallet(function(err) { + if (err) { + $scope.error = err; + $scope.loading = null; + return; + } + + walletService.signTx(fc, txp, function(err, signedTxp) { + walletService.lock(fc); + if (err) { + $scope.error = err; + $scope.loading = null; + return; + } + + if (signedTxp.status == 'accepted') { + $scope.loading = 'Broadcasting Transaction'; + walletService.broadcastTx(fc, signedTxp, function(err, broadcastedTxp) { + $scope.loading = null; + $scope.$emit('UpdateTx'); + $scope.close(broadcastedTxp); + if (err) { + $scope.error = err; + } + }); + } else { + $scope.loading = null; + $scope.$emit('UpdateTx'); + $scope.close(signedTxp); + } + }); + }); + }); + }; + + $scope.reject = function(txp) { + $scope.loading = 'Rejecting payment'; + $scope.error = null; + + $timeout(function() { + walletService.rejectTx(fc, txp, function(err, txpr) { + $scope.loading = null; + + if (err) { + $scope.$emit('UpdateTx'); + $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not reject payment')); + $scope.$digest(); + } else { + $scope.close(txpr); + } + }); + }, 10); + }; + + $scope.remove = function(txp) { + $scope.loading = 'Deleting Payment'; + $scope.error = null; + + $timeout(function() { + walletService.removeTx(fc, txp, function(err) { + $scope.loading = null; + + // Hacky: request tries to parse an empty response + if (err && !(err.message && err.message.match(/Unexpected/))) { + $scope.$emit('UpdateTx'); + $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not delete payment proposal')); + $scope.$digest(); + return; + } + $scope.close(); + }); + }, 10); + }; + + $scope.broadcast = function(txp) { + $scope.loading = 'Broadcasting Payment'; + $scope.error = null; + + $timeout(function() { + walletService.broadcastTx(fc, txp, function(err, txpb) { + $scope.loading = null; + + if (err) { + $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment')); + $scope.$digest(); + return; + } + $scope.close(txpb); + }); + }, 10); + }; + + $scope.getShortNetworkName = function() { + return fc.credentials.networkName.substring(0, 4); + }; + + function checkPaypro() { + if (tx.payProUrl && !isChromeApp) { + fc.fetchPayPro({ + payProUrl: tx.payProUrl, + }, function(err, paypro) { + if (err) return; + tx.paypro = paypro; + paymentTimeControl(tx.paypro.expires); + $timeout(function() { + $ionicScrollDelegate.resize(); + }, 100); + }); + } + }; + + function paymentTimeControl(expirationTime) { + $scope.paymentExpired = false; + setExpirationTime(); + + self.countDown = $interval(function() { + setExpirationTime(); + }, 1000); + + function setExpirationTime() { + var now = Math.floor(Date.now() / 1000); + if (now > expirationTime) { + $scope.paymentExpired = true; + if (self.countDown) $interval.cancel(self.countDown); + return; + } + var totalSecs = expirationTime - now; + var m = Math.floor(totalSecs / 60); + var s = totalSecs % 60; + $scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2); + }; + }; + + lodash.each(['TxProposalRejectedBy', 'TxProposalAcceptedBy', 'transactionProposalRemoved', 'TxProposalRemoved', 'NewOutgoingTx', 'UpdateTx'], function(eventName) { + $scope.$on(eventName, function() { + fc.getTx($scope.tx.id, function(err, tx) { + if (err) { + if (err.message && err.message == 'TX_NOT_FOUND' && + (eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) { + $scope.tx.removed = true; + $scope.tx.canBeRemoved = false; + $scope.tx.pendingForUs = false; + $scope.$apply(); + return; + } + return; + } + + var action = lodash.find(tx.actions, { + copayerId: fc.credentials.copayerId + }); + + $scope.tx = txFormatService.processTx(tx); + + if (!action && tx.status == 'pending') + $scope.tx.pendingForUs = true; + + $scope.updateCopayerList(); + $scope.$apply(); + }); + }); + }); + + $scope.updateCopayerList = function() { + lodash.map($scope.copayers, function(cp) { + lodash.each($scope.tx.actions, function(ac) { + if (cp.id == ac.copayerId) { + cp.action = ac.type; + } + }); + }); + }; + + function handleEncryptedWallet(cb) { + if (!walletService.isEncrypted(fc)) return cb(); + $rootScope.$emit('Local/NeedsPassword', false, function(err, password) { + if (err) return cb(err); + return cb(null, walletService.unlock(fc, password)); + }); + }; + + $scope.copyToClipboard = function(addr) { + if (!addr) return; + self.copyToClipboard(addr); + }; + + $scope.close = function(txp) { + $scope.loading = null; + if (txp) { + txStatus.notify(txp, function() { + $scope.$emit('Local/TxProposalAction', txp.status == 'broadcasted'); + }); + } else { + $timeout(function() { + $scope.$emit('Local/TxProposalAction'); + }, 100); + } + $scope.cancel(); + }; + + $scope.cancel = function() { + $scope.txpDetailsModal.hide(); + }; +}); diff --git a/src/js/controllers/walletHome.js b/src/js/controllers/walletHome.js index 37ae2531f..24ec4997d 100644 --- a/src/js/controllers/walletHome.js +++ b/src/js/controllers/walletHome.js @@ -175,260 +175,21 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi // isGlidera flag is a security measure so glidera status is not // only determined by the tx.message this.openTxpModal = function(tx, copayers, isGlidera) { - $rootScope.modalOpened = true; - var self = this; - var fc = profileService.focusedClient; - var currentSpendUnconfirmed = configWallet.spendUnconfirmed; - var ModalInstanceCtrl = function($scope, $modalInstance) { - $scope.paymentExpired = null; - checkPaypro(); - $scope.error = null; - $scope.copayers = copayers - $scope.copayerId = fc.credentials.copayerId; - $scope.canSign = fc.canSign() || fc.isPrivKeyExternal(); - $scope.loading = null; - $scope.color = fc.backgroundColor; - $scope.isShared = fc.credentials.n > 1; - var now = Math.floor(Date.now() / 1000); + $scope.self = self; + $scope.tx = tx; + $scope.copayers = copayers; + $scope.isGlidera = isGlidera; + $scope.error = null; + $scope.loading = null; + $scope.paymentExpired = null; + $scope.currentSpendUnconfirmed = configWallet.spendUnconfirmed; - // ToDo: use tx.customData instead of tx.message - if (tx.message === 'Glidera transaction' && isGlidera) { - tx.isGlidera = true; - if (tx.canBeRemoved) { - tx.canBeRemoved = (Date.now() / 1000 - (tx.ts || tx.createdOn)) > GLIDERA_LOCK_TIME; - } - } - $scope.tx = tx; - $scope.currentSpendUnconfirmed = currentSpendUnconfirmed; - - $scope.getShortNetworkName = function() { - return fc.credentials.networkName.substring(0, 4); - }; - - function checkPaypro() { - if (tx.payProUrl && !isChromeApp) { - fc.fetchPayPro({ - payProUrl: tx.payProUrl, - }, function(err, paypro) { - if (err) return; - tx.paypro = paypro; - paymentTimeControl(tx.paypro.expires); - }); - } - }; - - function paymentTimeControl(expirationTime) { - $scope.paymentExpired = false; - setExpirationTime(); - - self.countDown = $interval(function() { - setExpirationTime(); - }, 1000); - - function setExpirationTime() { - var now = Math.floor(Date.now() / 1000); - if (now > expirationTime) { - $scope.paymentExpired = true; - if (self.countDown) $interval.cancel(self.countDown); - return; - } - var totalSecs = expirationTime - now; - var m = Math.floor(totalSecs / 60); - var s = totalSecs % 60; - $scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2); - }; - }; - - lodash.each(['TxProposalRejectedBy', 'TxProposalAcceptedBy', 'transactionProposalRemoved', 'TxProposalRemoved', 'NewOutgoingTx', 'UpdateTx'], function(eventName) { - $rootScope.$on(eventName, function() { - fc.getTx($scope.tx.id, function(err, tx) { - if (err) { - if (err.message && err.message == 'TX_NOT_FOUND' && - (eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) { - $scope.tx.removed = true; - $scope.tx.canBeRemoved = false; - $scope.tx.pendingForUs = false; - $scope.$apply(); - return; - } - return; - } - - var action = lodash.find(tx.actions, { - copayerId: fc.credentials.copayerId - }); - - $scope.tx = txFormatService.processTx(tx); - - if (!action && tx.status == 'pending') - $scope.tx.pendingForUs = true; - - $scope.updateCopayerList(); - $scope.$apply(); - }); - }); - }); - - $scope.updateCopayerList = function() { - lodash.map($scope.copayers, function(cp) { - lodash.each($scope.tx.actions, function(ac) { - if (cp.id == ac.copayerId) { - cp.action = ac.type; - } - }); - }); - }; - - $scope.sign = function(txp) { - var client = profileService.focusedClient; - $scope.error = null; - $scope.loading = true; - - fingerprintService.check(client, function(err) { - if (err) { - $scope.loading = false; - $scope.error = err; - return; - } - - handleEncryptedWallet(client, function(err) { - if (err) { - $scope.loading = false; - $scope.error = err; - return; - } - - walletService.signTx(client, txp, function(err, signedTxp) { - walletService.lock(client); - if (err) { - $scope.loading = false; - $scope.error = err; - return; - } - - if (signedTxp.status == 'accepted') { - walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) { - $scope.loading = false; - $scope.$emit('UpdateTx'); - $modalInstance.close(broadcastedTxp); - if (err) { - $scope.loading = false; - $scope.error = err; - } - }); - } else { - $scope.loading = false; - $scope.$emit('UpdateTx'); - $modalInstance.close(signedTxp); - } - }); - }); - }); - }; - - $scope.reject = function(txp) { - var client = profileService.focusedClient; - self.setOngoingProcess(gettextCatalog.getString('Rejecting payment')); - $scope.loading = true; - $scope.error = null; - - $timeout(function() { - walletService.rejectTx(client, txp, function(err, txpr) { - self.setOngoingProcess(); - $scope.loading = false; - if (err) { - $scope.$emit('UpdateTx'); - $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not reject payment')); - $scope.$digest(); - } else { - $modalInstance.close(txpr); - } - }); - }, 100); - }; - - - $scope.remove = function(txp) { - var client = profileService.focusedClient; - self.setOngoingProcess(gettextCatalog.getString('Deleting payment')); - $scope.loading = true; - $scope.error = null; - $timeout(function() { - walletService.removeTx(client, txp, function(err) { - self.setOngoingProcess(); - $scope.loading = false; - - // Hacky: request tries to parse an empty response - if (err && !(err.message && err.message.match(/Unexpected/))) { - $scope.$emit('UpdateTx'); - $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not delete payment proposal')); - $scope.$digest(); - return; - } - $modalInstance.close(); - }); - }, 100); - }; - - $scope.broadcast = function(txp) { - var client = profileService.focusedClient; - self.setOngoingProcess(gettextCatalog.getString('Broadcasting Payment')); - $scope.loading = true; - $scope.error = null; - $timeout(function() { - walletService.broadcastTx(client, txp, function(err, txpb) { - self.setOngoingProcess(); - $scope.loading = false; - if (err) { - $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment')); - $scope.$digest(); - return; - } - $modalInstance.close(txpb); - }); - }, 100); - }; - - $scope.copyToClipboard = function(addr) { - if (!addr) return; - self.copyToClipboard(addr); - }; - - $scope.cancel = lodash.debounce(function() { - $modalInstance.dismiss('cancel'); - }, 0, 1000); - }; - - var modalInstance = $modal.open({ - templateUrl: 'views/modals/txp-details.html', - windowClass: animationService.modalAnimated.slideRight, - controller: ModalInstanceCtrl, + $ionicModal.fromTemplateUrl('views/modals/txp-details.html', { + scope: $scope + }).then(function(modal) { + $scope.txpDetailsModal = modal; + $scope.txpDetailsModal.show(); }); - - var disableCloseModal = $rootScope.$on('closeModal', function() { - modalInstance.dismiss('cancel'); - }); - - modalInstance.result.finally(function() { - $rootScope.modalOpened = false; - disableCloseModal(); - var m = angular.element(document.getElementsByClassName('reveal-modal')); - m.addClass(animationService.modalAnimated.slideOutRight); - }); - - modalInstance.result.then(function(txp) { - self.setOngoingProcess(); - if (txp) { - txStatus.notify(txp, function() { - $scope.$emit('Local/TxProposalAction', txp.status == 'broadcasted'); - }); - } else { - $timeout(function() { - $scope.$emit('Local/TxProposalAction'); - }, 100); - } - }); - }; this.setAddress = function(forceNew) { @@ -1069,6 +830,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi var self = this; $scope.btx = btx; + $scope.color = profileService.focusedClient.backgroundColor; $scope.self = self; $ionicModal.fromTemplateUrl('views/modals/tx-details.html', { diff --git a/src/sass/main.scss b/src/sass/main.scss index 3b3b51e4e..41989f2d8 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -697,6 +697,10 @@ ul.manage li { padding-top: 20px; } +.p50t { + padding-top: 50px; +} + .p10 { padding: 10px; } @@ -864,6 +868,7 @@ ul.manage li { background: #fff; width: 100%; padding-top: 60px; + padding-bottom: 20px; position: relative; } @@ -1975,10 +1980,8 @@ to prevent collapsing during animation*/ .modal-content { position: relative; - overflow-y: auto; height: 100%; width: 100%; - padding-bottom: 50px; -webkit-transform: translate3d(0, 0, 0); background: #f6f7f9; } @@ -2055,7 +2058,7 @@ body.modal-open { .payment-proposal-head { color: #fff; - padding: 0 10px 20px 10px; + padding: 40px 10px 20px 10px; text-align: center; }