diff options
Diffstat (limited to 'apps/system/js/notifications.js')
-rw-r--r-- | apps/system/js/notifications.js | 410 |
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; +}); |