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);
+ };
+ }
+ ]
+
+ };
+ }]);