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