Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/apps/system/js/notifications.js
diff options
context:
space:
mode:
Diffstat (limited to 'apps/system/js/notifications.js')
-rw-r--r--apps/system/js/notifications.js410
1 files changed, 410 insertions, 0 deletions
diff --git a/apps/system/js/notifications.js b/apps/system/js/notifications.js
new file mode 100644
index 0000000..dae4ab4
--- /dev/null
+++ b/apps/system/js/notifications.js
@@ -0,0 +1,410 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+(function appCacheIcons() {
+ // Caching the icon for notification if appCache is in effect
+ var appCache = window.applicationCache;
+ if (!appCache)
+ return;
+
+ var addIcons = function addIcons(app) {
+ var icons = app.manifest.icons;
+ if (icons) {
+ Object.keys(icons).forEach(function iconIterator(key) {
+ var url = app.origin + icons[key];
+ appCache.mozAdd(url);
+ });
+ }
+ };
+
+ var removeIcons = function removeIcons(app) {
+ var icons = app.manifest.icons;
+ if (icons) {
+ Object.keys(icons).forEach(function iconIterator(key) {
+ var url = app.origin + icons[key];
+ appCache.mozRemove(url);
+ });
+ }
+ };
+
+ window.addEventListener('applicationinstall', function bsm_oninstall(evt) {
+ addIcons(evt.detail.application);
+ });
+
+ window.addEventListener('applicationuninstall', function bsm_oninstall(evt) {
+ removeIcons(evt.detail.application);
+ });
+}());
+
+var NotificationScreen = {
+ TOASTER_TIMEOUT: 5000,
+ TRANSITION_SPEED: 1.8,
+ TRANSITION_FRACTION: 0.30,
+
+ _notification: null,
+ _containerWidth: null,
+ _toasterTimeout: null,
+ _toasterGD: null,
+
+ lockscreenPreview: true,
+ silent: false,
+ alerts: true,
+ vibrates: true,
+
+ init: function ns_init() {
+ window.addEventListener('mozChromeEvent', this);
+ this.container =
+ document.getElementById('desktop-notifications-container');
+ this.lockScreenContainer =
+ document.getElementById('notifications-lockscreen-container');
+ this.toaster = document.getElementById('notification-toaster');
+ this.toasterIcon = document.getElementById('toaster-icon');
+ this.toasterTitle = document.getElementById('toaster-title');
+ this.toasterDetail = document.getElementById('toaster-detail');
+ this.clearAllButton = document.getElementById('notification-clear');
+
+ this._toasterGD = new GestureDetector(this.toaster);
+ ['tap', 'mousedown', 'swipe'].forEach(function(evt) {
+ this.container.addEventListener(evt, this);
+ this.toaster.addEventListener(evt, this);
+ }, this);
+
+ this.clearAllButton.addEventListener('click', this.clearAll.bind(this));
+
+ // will hold the count of external contributors to the notification
+ // screen
+ this.externalNotificationsCount = 0;
+
+ window.addEventListener('utilitytrayshow', this);
+ window.addEventListener('unlock', this.clearLockScreen.bind(this));
+ window.addEventListener('mozvisibilitychange', this);
+ window.addEventListener('appopen', this.handleAppopen.bind(this));
+
+ this._sound = 'style/notifications/ringtones/notifier_exclamation.ogg';
+
+ var self = this;
+ SettingsListener.observe('notification.ringtone', '', function(value) {
+ self._sound = value;
+ });
+ },
+
+ handleEvent: function ns_handleEvent(evt) {
+ switch (evt.type) {
+ case 'mozChromeEvent':
+ var detail = evt.detail;
+ if (detail.type !== 'desktop-notification')
+ return;
+
+ this.addNotification(detail);
+ break;
+ case 'tap':
+ var target = evt.target;
+ this.tap(target);
+ break;
+ case 'mousedown':
+ this.mousedown(evt);
+ break;
+ case 'swipe':
+ this.swipe(evt);
+ break;
+ case 'utilitytrayshow':
+ this.updateTimestamps();
+ StatusBar.updateNotificationUnread(false);
+ break;
+ case 'mozvisibilitychange':
+ //update timestamps in lockscreen notifications
+ if (!document.mozHidden) {
+ this.updateTimestamps();
+ }
+ break;
+ }
+ },
+
+ handleAppopen: function ns_handleAppopen(evt) {
+ var manifestURL = evt.detail.manifestURL,
+ selector = '[data-manifest-u-r-l="' + manifestURL + '"]';
+
+ var nodes = this.container.querySelectorAll(selector);
+
+ for (var i = nodes.length - 1; i >= 0; i--) {
+ this.closeNotification(nodes[i]);
+ }
+ },
+
+ // Swipe handling
+ mousedown: function ns_mousedown(evt) {
+ if (!evt.target.dataset.notificationID)
+ return;
+
+ evt.preventDefault();
+ this._notification = evt.target;
+ this._containerWidth = this.container.clientWidth;
+ },
+
+ swipe: function ns_swipe(evt) {
+ var detail = evt.detail;
+ var distance = detail.start.screenX - detail.end.screenX;
+ var fastEnough = Math.abs(detail.vx) > this.TRANSITION_SPEED;
+ var farEnough = Math.abs(distance) >
+ this._containerWidth * this.TRANSITION_FRACTION;
+
+ // We only remove the notification if the swipe was
+ // - left to right
+ // - far or fast enough
+ if ((distance > 0) ||
+ !(farEnough || fastEnough)) {
+ // Werent far or fast enough to delete, restore
+ delete this._notification;
+ return;
+ }
+
+ this._notification.classList.add('disappearing');
+
+ var notification = this._notification;
+ this._notification = null;
+
+ var toaster = this.toaster;
+ var self = this;
+ notification.addEventListener('transitionend', function trListener() {
+ notification.removeEventListener('transitionend', trListener);
+
+ self.closeNotification(notification);
+
+ if (notification != toaster)
+ return;
+
+ // Putting back the toaster in a clean state for the next notification
+ toaster.style.display = 'none';
+ setTimeout(function nextLoop() {
+ toaster.style.MozTransition = '';
+ toaster.style.MozTransform = '';
+ toaster.classList.remove('displayed');
+ toaster.classList.remove('disappearing');
+
+ setTimeout(function nextLoop() {
+ toaster.style.display = 'block';
+ });
+ });
+ });
+ },
+
+ tap: function ns_tap(notificationNode) {
+ var notificationID = notificationNode.dataset.notificationID;
+
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('mozContentEvent', true, true, {
+ type: 'desktop-notification-click',
+ id: notificationID
+ });
+ window.dispatchEvent(event);
+
+ this.removeNotification(notificationNode.dataset.notificationID, false);
+
+ if (notificationNode == this.toaster) {
+ this.toaster.classList.remove('displayed');
+ } else {
+ UtilityTray.hide();
+ }
+ },
+
+ updateTimestamps: function ns_updateTimestamps() {
+ var timestamps = document.getElementsByClassName('timestamp');
+ for (var i = 0, l = timestamps.length; i < l; i++) {
+ timestamps[i].textContent =
+ this.prettyDate(new Date(timestamps[i].dataset.timestamp));
+ }
+ },
+
+ /**
+ * Display a human-readable relative timestamp.
+ */
+ prettyDate: function prettyDate(time) {
+ var date;
+ if (navigator.mozL10n) {
+ date = navigator.mozL10n.DateTimeFormat().fromNow(time, true);
+ } else {
+ date = time.toLocaleFormat();
+ }
+ return date;
+ },
+
+ addNotification: function ns_addNotification(detail) {
+ var notificationNode = document.createElement('div');
+ notificationNode.className = 'notification';
+
+ notificationNode.dataset.notificationID = detail.id;
+ notificationNode.dataset.manifestURL = detail.manifestURL;
+
+ if (detail.icon) {
+ var icon = document.createElement('img');
+ icon.src = detail.icon;
+ notificationNode.appendChild(icon);
+ this.toasterIcon.src = detail.icon;
+ this.toasterIcon.hidden = false;
+ } else {
+ this.toasterIcon.hidden = true
+ }
+
+ var time = document.createElement('span');
+ var timestamp = new Date();
+ time.classList.add('timestamp');
+ time.dataset.timestamp = timestamp;
+ time.textContent = this.prettyDate(timestamp);
+ notificationNode.appendChild(time);
+
+ var title = document.createElement('div');
+ title.textContent = detail.title;
+ notificationNode.appendChild(title);
+
+ this.toasterTitle.textContent = detail.title;
+
+ var message = document.createElement('div');
+ message.classList.add('detail');
+ message.textContent = detail.text;
+ notificationNode.appendChild(message);
+
+ this.toasterDetail.textContent = detail.text;
+
+ this.container.insertBefore(notificationNode,
+ this.container.firstElementChild);
+ new GestureDetector(notificationNode).startDetecting();
+
+ // We turn the screen on if needed in order to let
+ // the user see the notification toaster
+ if (typeof(ScreenManager) !== 'undefined' &&
+ !ScreenManager.screenEnabled) {
+ ScreenManager.turnScreenOn();
+ }
+
+ this.updateStatusBarIcon(true);
+
+ // Notification toaster
+ if (this.lockscreenPreview || !LockScreen.locked) {
+ this.toaster.dataset.notificationID = detail.id;
+
+ this.toaster.classList.add('displayed');
+ this._toasterGD.startDetecting();
+
+ if (this._toasterTimeout)
+ clearTimeout(this._toasterTimeout);
+
+ this._toasterTimeout = setTimeout((function() {
+ this.toaster.classList.remove('displayed');
+ this._toasterTimeout = null;
+ this._toasterGD.stopDetecting();
+ }).bind(this), this.TOASTER_TIMEOUT);
+ }
+
+ // Adding it to the lockscreen if locked and the privacy setting
+ // does not prevent it.
+ if (typeof(LockScreen) !== 'undefined' &&
+ LockScreen.locked && this.lockscreenPreview) {
+ var lockScreenNode = notificationNode.cloneNode(true);
+ this.lockScreenContainer.insertBefore(lockScreenNode,
+ this.lockScreenContainer.firstElementChild);
+ }
+
+ if (this.alerts && !this.silent) {
+ var ringtonePlayer = new Audio();
+ ringtonePlayer.src = this._sound;
+ ringtonePlayer.mozAudioChannelType = 'notification';
+ ringtonePlayer.play();
+ window.setTimeout(function smsRingtoneEnder() {
+ ringtonePlayer.pause();
+ ringtonePlayer.src = '';
+ }, 2000);
+ }
+
+ if (this.vibrates) {
+ if (document.mozHidden) {
+ window.addEventListener('mozvisibilitychange', function waitOn() {
+ window.removeEventListener('mozvisibilitychange', waitOn);
+ navigator.vibrate([200, 200, 200, 200]);
+ });
+ } else {
+ navigator.vibrate([200, 200, 200, 200]);
+ }
+ }
+
+ return notificationNode;
+ },
+
+ closeNotification: function ns_closeNotification(notificationNode) {
+ var notificationID = notificationNode.dataset.notificationID;
+
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('mozContentEvent', true, true, {
+ type: 'desktop-notification-close',
+ id: notificationID
+ });
+ window.dispatchEvent(event);
+
+ this.removeNotification(notificationNode.dataset.notificationID);
+ },
+
+ removeNotification: function ns_removeNotification(notificationID) {
+ var notifSelector = '[data-notification-i-d="' + notificationID + '"]';
+ var notificationNode = this.container.querySelector(notifSelector);
+
+ notificationNode.parentNode.removeChild(notificationNode);
+ this.updateStatusBarIcon();
+ },
+
+ clearAll: function ns_clearAll() {
+ while (this.container.firstElementChild) {
+ this.closeNotification(this.container.firstElementChild);
+ }
+ },
+
+ clearLockScreen: function ns_clearLockScreen() {
+ while (this.lockScreenContainer.firstElementChild) {
+ var element = this.lockScreenContainer.firstElementChild;
+ this.lockScreenContainer.removeChild(element);
+ }
+ },
+
+ updateStatusBarIcon: function ns_updateStatusBarIcon(unread) {
+ var nbTotalNotif = this.container.children.length +
+ this.externalNotificationsCount;
+ StatusBar.updateNotification(nbTotalNotif);
+
+ if (unread)
+ StatusBar.updateNotificationUnread(true);
+ },
+
+ incExternalNotifications: function ns_incExternalNotifications() {
+ this.externalNotificationsCount++;
+ this.updateStatusBarIcon(true);
+ },
+
+ decExternalNotifications: function ns_decExternalNotifications() {
+ this.externalNotificationsCount--;
+ if (this.externalNotificationsCount < 0) {
+ this.externalNotificationsCount = 0;
+ }
+ this.updateStatusBarIcon();
+ }
+
+};
+
+NotificationScreen.init();
+
+SettingsListener.observe(
+ 'lockscreen.notifications-preview.enabled', true, function(value) {
+
+ NotificationScreen.lockscreenPreview = value;
+});
+
+SettingsListener.observe('alert-sound.enabled', true, function(value) {
+ NotificationScreen.alerts = value;
+});
+
+SettingsListener.observe('ring.enabled', true, function(value) {
+ NotificationScreen.silent = !value;
+});
+
+SettingsListener.observe('alert-vibration.enabled', true, function(value) {
+ NotificationScreen.vibrates = value;
+});