From 19573dc3276c4baa4cb403ccb3f503517dbd0d29 Mon Sep 17 00:00:00 2001 From: Gustavo Cortez Date: Thu, 15 May 2014 02:12:14 -0300 Subject: [PATCH 1/6] Improve tx counter notification: not show if you sign, reject or if it is your tx proposal. --- js/services/controllerUtils.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 0b0f7361f..9a76287ca 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -128,6 +128,8 @@ angular.module('copay.controllerUtils') var w = $rootScope.wallet; if (!w) return; + var myCopayerId = w.getMyCopayerId(); + var pending = 0; var inT = w.getTxProposals(); var txs = []; @@ -149,13 +151,16 @@ angular.module('copay.controllerUtils') i.fee = i.builder.feeSat/bitcore.util.COIN; i.missingSignatures = tx.countInputMissingSignatures(0); txs.push(i); - }); - $rootScope.txs = txs; - var pending = 0; - for(var i=0; i Date: Thu, 15 May 2014 02:13:25 -0300 Subject: [PATCH 2/6] HTML5 Notification support on chrome --- css/main.css | 135 ++++++++++++++++++ index.html | 3 + js/app.js | 1 + js/controllers/header.js | 11 +- js/services/notifications.js | 259 +++++++++++++++++++++++++++++++++++ 5 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 js/services/notifications.js diff --git a/css/main.css b/css/main.css index e6e793f09..18ee8a8c4 100644 --- a/css/main.css +++ b/css/main.css @@ -496,3 +496,138 @@ a.loading { vertical-align:middle } +/* notifications */ + +.dr-notification-container { + position: absolute; + z-index: 10000; +} + +.dr-notification-container.bottom { + bottom: 20px; +} + +.dr-notification-container.right { + right: 20px; +} + +.dr-notification-container.left { + left: 20px; +} + +.dr-notification-container.top { + top: 20px; +} + +.dr-notification-container.center { + left: 50%; + margin-left: -190px; +} + +.dr-notification-wrapper { + width: 380px; + position: relative; + margin: 10px 0; +} + +.dr-notification { + width: 380px; + background-color: #2C3E50; + clear: both; + min-height: 80px; + max-height: 90px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + border-radius: 5px; + color: #bfe2de; + border: 1px solid rgba(4, 94, 123, 0.85); + overflow: hidden; +} + +.dr-notification-close-btn { + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + -ms-border-radius: 20px; + border-radius: 20px; + display: inline-block; + padding: 3px; + background-color: #2C3E50; + font-size: 14px; + color: #adfaff; + border: 1px solid rgba(4, 94, 123, 0.85); + position: absolute; + right: -11px; + top: 5px; + -webkit-transition: all 0.35s cubic-bezier(0.31, 0.39, 0.21, 1.65); + -moz-transition: all 0.35s cubic-bezier(0.31, 0.39, 0.21, 1.65); + transition: all 0.35s cubic-bezier(0.31, 0.39, 0.21, 1.65); + cursor: pointer; +} +.dr-notification-close-btn i { + padding-left: 3px; +} +.dr-notification-close-btn:hover { + -webkit-transform: scale3d(1.25, 1.25, 1); + -moz-transform: scale3d(1.25, 1.25, 1); + -ms-transform: scale3d(1.25, 1.25, 1); + transform: scale3d(1.25, 1.25, 1); +} + +.dr-notification-image { + width: 80px; + height: 80px; + border-right: 1px solid rgba(4, 94, 123, 0.85); + float: left; + display: block; + font-size: 40px; + color: white; + text-align: center; +} +.dr-notification-image i { + display: block; + width: 100%; + padding-top: 25px; +} +.dr-notification-image img { + margin: 15px; + max-width: 70px; + min-width: 48px; +} + +.dr-notification-image.dr-notification-type-info { + color: #FFF; +} + +.dr-notification-image.dr-notification-type-warning { + color: #FFA226; +} + +.dr-notification-image.dr-notification-type-error { + color: #FF4B4F; +} + +.dr-notification-image.dr-notification-type-success { + color: #B4D455; +} + +.dr-notification-image.success { + color: #B4D455; +} + +.dr-notification-content { + padding-left: 100px; + padding-right: 15px; + padding-top: 10px; +} + +.dr-notification-title { + color: white; + padding: 0px; + font-size: 20px; +} + +p.dr-notification-text { + margin-top: -5px; + font-size: 12px; +} diff --git a/index.html b/index.html index 57a600cda..5c8116cb7 100644 --- a/index.html +++ b/index.html @@ -111,6 +111,8 @@ +
+
@@ -680,6 +682,7 @@ on supported browsers please check http://www.w + diff --git a/js/app.js b/js/app.js index 30a55782e..d048c71b2 100644 --- a/js/app.js +++ b/js/app.js @@ -7,6 +7,7 @@ var copayApp = window.copayApp = angular.module('copay',[ 'ngRoute', 'mm.foundation', 'monospaced.qrcode', + 'notifications', 'copay.header', 'copay.footer', 'copay.addresses', diff --git a/js/controllers/header.js b/js/controllers/header.js index 3ceb1d865..523d55955 100644 --- a/js/controllers/header.js +++ b/js/controllers/header.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copay.header').controller('HeaderController', - function($scope, $rootScope, $location, walletFactory, controllerUtils) { + function($scope, $rootScope, $location, $notification, walletFactory, controllerUtils) { $scope.menu = [ { 'title': 'Addresses', @@ -27,6 +27,15 @@ angular.module('copay.header').controller('HeaderController', } }); + // Initialize alert notification (not show when init wallet) + $rootScope.showTxAlert = 0; + $notification.enableHtml5Mode(); // for chrome: if support, enable it + $rootScope.$watch('showTxAlert', function(showTxAlert) { + if (showTxAlert && showTxAlert > 0) { + $notification.info('New Transaction', ($rootScope.showTxAlert == 1) ? 'You have pending a transaction proposal' : 'You have pending ' + $rootScope.showTxAlert + ' transaction proposals', showTxAlert); + } + }); + $scope.isActive = function(item) { if (item.link && item.link.replace('#','') == $location.path()) { return true; diff --git a/js/services/notifications.js b/js/services/notifications.js new file mode 100644 index 000000000..7f428d989 --- /dev/null +++ b/js/services/notifications.js @@ -0,0 +1,259 @@ +'use strict'; + +angular.module('notifications', []). + factory('$notification', ['$timeout',function($timeout){ + + console.log('notification service online'); + var notifications = JSON.parse(localStorage.getItem('$notifications')) || [], + queue = []; + + var settings = { + info: { duration: 5000, enabled: true }, + warning: { duration: 5000, enabled: true }, + error: { duration: 5000, enabled: true }, + success: { duration: 5000, enabled: true }, + progress: { duration: 0, enabled: true }, + custom: { duration: 35000, enabled: true }, + details: true, + localStorage: false, + html5Mode: false, + html5DefaultIcon: '../img/satoshi.gif' + }; + + function html5Notify(icon, title, content, ondisplay, onclose){ + if(window.webkitNotifications.checkPermission() === 0){ + if(!icon){ + icon = '../img/favicon.ico'; + } + var noti = window.webkitNotifications.createNotification(icon, title, content); + if(typeof ondisplay === 'function'){ + noti.ondisplay = ondisplay; + } + if(typeof onclose === 'function'){ + noti.onclose = onclose; + } + noti.show(); + } + else { + settings.html5Mode = false; + } + } + + + return { + + /* ========== SETTINGS RELATED METHODS =============*/ + + disableHtml5Mode: function(){ + settings.html5Mode = false; + }, + + disableType: function(notificationType){ + settings[notificationType].enabled = false; + }, + + enableHtml5Mode: function(){ + // settings.html5Mode = true; + settings.html5Mode = this.requestHtml5ModePermissions(); + }, + + enableType: function(notificationType){ + settings[notificationType].enabled = true; + }, + + getSettings: function(){ + return settings; + }, + + toggleType: function(notificationType){ + settings[notificationType].enabled = !settings[notificationType].enabled; + }, + + toggleHtml5Mode: function(){ + settings.html5Mode = !settings.html5Mode; + }, + + requestHtml5ModePermissions: function(){ + if (window.webkitNotifications){ + console.log('notifications are available'); + if (window.webkitNotifications.checkPermission() === 0) { + return true; + } + else{ + window.webkitNotifications.requestPermission(function(){ + if(window.webkitNotifications.checkPermission() === 0){ + settings.html5Mode = true; + } + else{ + settings.html5Mode = false; + } + }); + return false; + } + } + else{ + console.log('notifications are not supported'); + return false; + } + }, + + + /* ============ QUERYING RELATED METHODS ============*/ + + getAll: function(){ + // Returns all notifications that are currently stored + return notifications; + }, + + getQueue: function(){ + return queue; + }, + + /* ============== NOTIFICATION METHODS ==============*/ + + info: function(title, content, userData){ + console.log(title, content); + return this.awesomeNotify('info','info', title, content, userData); + }, + + error: function(title, content, userData){ + return this.awesomeNotify('error', 'remove', title, content, userData); + }, + + success: function(title, content, userData){ + return this.awesomeNotify('success', 'ok', title, content, userData); + }, + + warning: function(title, content, userData){ + return this.awesomeNotify('warning', 'exclamation', title, content, userData); + }, + + awesomeNotify: function(type, icon, title, content, userData){ + /** + * Supposed to wrap the makeNotification method for drawing icons using font-awesome + * rather than an image. + * + * Need to find out how I'm going to make the API take either an image + * resource, or a font-awesome icon and then display either of them. + * Also should probably provide some bits of color, could do the coloring + * through classes. + */ + // image = ''; + return this.makeNotification(type, false, icon, title, content, userData); + }, + + notify: function(image, title, content, userData){ + // Wraps the makeNotification method for displaying notifications with images + // rather than icons + return this.makeNotification('custom', image, true, title, content, userData); + }, + + makeNotification: function(type, image, icon, title, content, userData){ + var notification = { + 'type': type, + 'image': image, + 'icon': icon, + 'title': title, + 'content': content, + 'timestamp': +new Date(), + 'userData': userData + }; + notifications.push(notification); + + if(settings.html5Mode){ + html5Notify(image, title, content, function(){ + console.log("inner on display function"); + }, function(){ + console.log("inner on close function"); + }); + } + else{ + queue.push(notification); + $timeout(function removeFromQueueTimeout(){ + queue.splice(queue.indexOf(notification), 1); + }, settings[type].duration); + + } + + this.save(); + return notification; + }, + + + /* ============ PERSISTENCE METHODS ============ */ + + save: function(){ + // Save all the notifications into localStorage + // console.log(JSON); + if(settings.localStorage){ + localStorage.setItem('$notifications', JSON.stringify(notifications)); + } + // console.log(localStorage.getItem('$notifications')); + }, + + restore: function(){ + // Load all notifications from localStorage + }, + + clear: function(){ + notifications = []; + this.save(); + } + + }; + }]). + directive('notifications', ['$notification', '$compile', function($notification, $compile){ + /** + * + * It should also parse the arguments passed to it that specify + * its position on the screen like "bottom right" and apply those + * positions as a class to the container element + * + * Finally, the directive should have its own controller for + * handling all of the notifications from the notification service + */ +// console.log('this is a new directive'); + var html = + '
' + + '
' + + '' + + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '

{{noti.title}}

' + + '

{{noti.content}}

' + + '
' + + '
' + + '
'; + + + function link(scope, element, attrs){ + var position = attrs.notifications; + position = position.split(' '); + element.addClass('dr-notification-container'); + for(var i = 0; i < position.length ; i++){ + element.addClass(position[i]); + } + } + + + return { + restrict: 'A', + scope: {}, + template: html, + link: link, + controller: ['$scope', function NotificationsCtrl( $scope ){ + $scope.queue = $notification.getQueue(); + + $scope.removeNotification = function(noti){ + $scope.queue.splice($scope.queue.indexOf(noti), 1); + }; + } + ] + + }; + }]); From 89b2788e39a8ae92235565fed2050e9b7c3b7198 Mon Sep 17 00:00:00 2001 From: Gustavo Cortez Date: Thu, 15 May 2014 10:41:42 -0300 Subject: [PATCH 3/6] Fixes: suggestions by @matiu --- js/controllers/header.js | 8 ++++---- js/services/controllerUtils.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/controllers/header.js b/js/controllers/header.js index 523d55955..331fa668d 100644 --- a/js/controllers/header.js +++ b/js/controllers/header.js @@ -28,11 +28,11 @@ angular.module('copay.header').controller('HeaderController', }); // Initialize alert notification (not show when init wallet) - $rootScope.showTxAlert = 0; + $rootScope.txAlertCount = 0; $notification.enableHtml5Mode(); // for chrome: if support, enable it - $rootScope.$watch('showTxAlert', function(showTxAlert) { - if (showTxAlert && showTxAlert > 0) { - $notification.info('New Transaction', ($rootScope.showTxAlert == 1) ? 'You have pending a transaction proposal' : 'You have pending ' + $rootScope.showTxAlert + ' transaction proposals', showTxAlert); + $rootScope.$watch('txAlertCount', function(txAlertCount) { + if (txAlertCount && txAlertCount > 0) { + $notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount); } }); diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 9a76287ca..a4abebd72 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -160,7 +160,7 @@ angular.module('copay.controllerUtils') $rootScope.txs = txs; if ($rootScope.pendingTxCount < pending) { - $rootScope.showTxAlert = pending; + $rootScope.txAlertCount = pending; } $rootScope.pendingTxCount = pending; w.removeListener('txProposalsUpdated',root.updateTxs) From c958b8e7a80923cd7c42a8994536cc7cc3666bd3 Mon Sep 17 00:00:00 2001 From: Gustavo Cortez Date: Thu, 15 May 2014 12:51:52 -0300 Subject: [PATCH 4/6] fixes html5 alert: if you have 1 tx pending and you reject or sign it, then does not appear the alert of the next one. --- js/controllers/transactions.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/controllers/transactions.js b/js/controllers/transactions.js index 26f948cbc..0a66e1dec 100644 --- a/js/controllers/transactions.js +++ b/js/controllers/transactions.js @@ -8,6 +8,7 @@ angular.module('copay.transactions').controller('TransactionsController', $scope.send = function (ntxid) { $scope.loading = true; + $rootScope.txAlertCount = 0; var w = $rootScope.wallet; w.sendTx(ntxid, function(txid) { console.log('[transactions.js.68:txid:] SENTTX CALLBACK',txid); //TODO @@ -65,6 +66,7 @@ angular.module('copay.transactions').controller('TransactionsController', $scope.reject = function (ntxid) { $scope.loading = true; + $rootScope.txAlertCount = 0; var w = $rootScope.wallet; w.reject(ntxid); $rootScope.flashMessage = {type:'warning', message: 'Transaction rejected by you'}; From c2fb4217ce18b53b5b7e0c8d5f329b4c0bfb8f85 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 15 May 2014 11:22:53 -0700 Subject: [PATCH 5/6] sort wallet list and fix display for unnamed wallets --- js/controllers/signin.js | 7 ++++++- js/models/storage/LocalEncrypted.js | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/js/controllers/signin.js b/js/controllers/signin.js index 5971a1ce2..685e176d5 100644 --- a/js/controllers/signin.js +++ b/js/controllers/signin.js @@ -2,8 +2,13 @@ angular.module('copay.signin').controller('SigninController', function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase) { + var cmp = function(o1, o2){ + var v1 = o1.show.toLowerCase(), v2 = o2.show.toLowerCase(); + return v1 > v2 ? 1 : ( v1 < v2 ) ? -1 : 0; + }; + $scope.loading = false; - $scope.wallets = walletFactory.getWallets(); + $scope.wallets = walletFactory.getWallets().sort(cmp); $scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null; $scope.openPassword = ''; diff --git a/js/models/storage/LocalEncrypted.js b/js/models/storage/LocalEncrypted.js index 42509ab6b..18fc83dee 100644 --- a/js/models/storage/LocalEncrypted.js +++ b/js/models/storage/LocalEncrypted.js @@ -73,7 +73,8 @@ Storage.prototype._write = function(k,v) { // get value by key Storage.prototype.getGlobal = function(k) { - return localStorage.getItem(k); + var item = localStorage.getItem(k); + return item == 'undefined' ? undefined : item; }; // set value for key From 0dfac71bfef3362a96ad0b99e8906cff73ee12c1 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 15 May 2014 16:12:43 -0300 Subject: [PATCH 6/6] always show all copayers in tx proposal list --- index.html | 4 ++-- js/models/core/TxProposals.js | 14 +++++++++++--- js/models/core/Wallet.js | 16 ++++++++++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 186c92917..f6ad82170 100644 --- a/index.html +++ b/index.html @@ -342,7 +342,7 @@
-

Transactions proposals ({{txs.length}})

+

Transaction proposals ({{txs.length}})

@@ -364,7 +364,7 @@
- cId + {{cId}}
diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index bbef94908..0d291a962 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -180,8 +180,8 @@ TxProposals.prototype._mergeBuilder = function(myTxps, theirTxps, mergeInfo) { }; TxProposals.prototype.add = function(data) { - var id = data.builder.build().getNormalizedHash().toString('hex'); - this.txps[id] = new TxProposal(data); + var ntxid = data.builder.build().getNormalizedHash().toString('hex'); + this.txps[ntxid] = new TxProposal(data); }; @@ -191,12 +191,20 @@ TxProposals.prototype.setSent = function(ntxid,txid) { }; -TxProposals.prototype.getTxProposal = function(ntxid) { +TxProposals.prototype.getTxProposal = function(ntxid, copayers) { var txp = this.txps[ntxid]; var i = JSON.parse(JSON.stringify(txp)); i.builder = txp.builder; i.ntxid = ntxid; i.peerActions = {}; + + if (copayers) { + for(var j=0; j < copayers.length; j++) { + var p = copayers[j]; + i.peerActions[p] = {}; + } + } + for(var p in txp.seenBy){ i.peerActions[p]={seen: txp.seenBy[p]}; } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 3eda3de05..76a3314b2 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -222,12 +222,23 @@ Wallet.prototype.getOnlinePeerIDs = function() { return this.network.getOnlinePeerIDs(); }; +Wallet.prototype.getRegisteredCopayerIds = function() { + var l = this.publicKeyRing.registeredCopayers(); + var copayers = []; + for (var i = 0; i < l; i++) { + var cid = this.getCopayerId(i); + copayers.push(cid); + } + return copayers; +}; + Wallet.prototype.getRegisteredPeerIds = function() { var l = this.publicKeyRing.registeredCopayers(); if (this.registeredPeerIds.length !== l) { this.registeredPeerIds = []; + var copayers = this.getRegisteredCopayerIds(); for (var i = 0; i < l; i++) { - var cid = this.getCopayerId(i); + var cid = copayers[i]; var pid = this.network.peerFromCopayer(cid); this.registeredPeerIds.push({ peerId: pid, @@ -325,8 +336,9 @@ Wallet.prototype.generateAddress = function(isChange) { Wallet.prototype.getTxProposals = function() { var ret = []; + var copayers = this.getRegisteredCopayerIds(); for (var k in this.txProposals.txps) { - var i = this.txProposals.getTxProposal(k); + var i = this.txProposals.getTxProposal(k, copayers); i.signedByUs = i.signedBy[this.getMyCopayerId()] ? true : false; i.rejectedByUs = i.rejectedBy[this.getMyCopayerId()] ? true : false; if (this.totalCopayers - i.rejectCount < this.requiredCopayers)