Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
834 lines
27 KiB
JavaScript
834 lines
27 KiB
JavaScript
/* global LazyLoader, MediaPlaybackWidget, Service,
|
|
SettingsListener, SettingsURL, toneUpgrader, mozIntl */
|
|
|
|
'use strict';
|
|
|
|
var NotificationScreen = {
|
|
name: 'NotificationScreen',
|
|
TOASTER_TIMEOUT: 3500,
|
|
TRANSITION_FRACTION: 0.30,
|
|
TRANSITION_DURATION: 200,
|
|
SWIPE_INERTIA: 100,
|
|
TAP_THRESHOLD: 10,
|
|
SCROLL_THRESHOLD: 10,
|
|
CLEAR_DELAY: 80,
|
|
|
|
_notification: null,
|
|
_containerWidth: null,
|
|
_touchStartX: 0,
|
|
_touchStartY: 0,
|
|
_touchPosX: 0,
|
|
_touching: false,
|
|
_isTap: false,
|
|
_toasterTimeout: null,
|
|
|
|
lockscreenPreview: true,
|
|
silent: false,
|
|
vibrates: true,
|
|
isResending: false,
|
|
resendReceived: 0,
|
|
resendExpecting: 0,
|
|
|
|
/* These applications' notifications will be added in the "priority"
|
|
* notification group
|
|
*/
|
|
PRIORITY_APPLICATIONS: [
|
|
window.location.origin.replace('system.', 'network-alerts.') +
|
|
'/manifest.webapp'
|
|
],
|
|
|
|
/* These applications' notifications will not be notified, it means we won't
|
|
* have:
|
|
* - the banner
|
|
* - the vibration
|
|
* - the sound
|
|
*/
|
|
SILENT_APPLICATIONS: [
|
|
window.location.origin.replace('system.', 'network-alerts.') +
|
|
'/manifest.webapp'
|
|
],
|
|
|
|
getLockScreenContainer: function ns_getLockScreenContainer() {
|
|
// XXX: Bug 1057198 add this as a workaround before we truly
|
|
// make LockScreen as an app.
|
|
var lw = Service.query('LockScreenWindowManager.getInstance');
|
|
if (lw && lw.getNotificationContainer()) {
|
|
return lw.getNotificationContainer();
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
start: function() {
|
|
window.addEventListener('mozChromeNotificationEvent', this);
|
|
this.notificationsContainer =
|
|
document.getElementById('notifications-container');
|
|
this.container =
|
|
document.getElementById('desktop-notifications-container');
|
|
this.lockScreenContainer = this.getLockScreenContainer();
|
|
this.toaster = document.getElementById('notification-toaster');
|
|
this.ambientIndicator = document.getElementById('ambient-indicator');
|
|
this.toasterIcon = document.getElementById('toaster-icon');
|
|
this.toasterTitle = document.getElementById('toaster-title');
|
|
this.toasterDetail = document.getElementById('toaster-detail');
|
|
this.clearAllButton = document.getElementById('notification-clear');
|
|
|
|
['tap', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'wheel'].
|
|
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;
|
|
this.unreadNotifications = [];
|
|
|
|
window.addEventListener('utilitytrayshow', this);
|
|
// Since UI expect there is a slight delay for the opened notification.
|
|
window.addEventListener('lockscreen-appclosed', this);
|
|
window.addEventListener('visibilitychange', this);
|
|
window.addEventListener('ftuopen', this);
|
|
window.addEventListener('ftudone', this);
|
|
window.addEventListener('desktop-notification-resend', this);
|
|
|
|
this._sound = 'style/notifications/ringtones/notifier_firefox.opus';
|
|
|
|
// set up the media playback widget, but only if |MediaPlaybackWidget| is
|
|
// defined (we don't define it in tests)
|
|
LazyLoader.load(['js/media_playback.js']).then(function() {
|
|
this.mediaPlaybackWidget = new MediaPlaybackWidget(
|
|
document.getElementById('media-playback-container'),
|
|
{nowPlayingAction: 'openapp'});
|
|
}.bind(this))['catch'](function(err) {
|
|
console.error(err);
|
|
});
|
|
|
|
var self = this;
|
|
SettingsListener.observe('notification.ringtone', '', function(value) {
|
|
LazyLoader.load(['shared/js/settings_url.js']).then(function() {
|
|
if (!self.ringtoneURL) {
|
|
self.ringtoneURL = new SettingsURL();
|
|
}
|
|
self._sound = self.ringtoneURL.set(value);
|
|
})['catch'](function(err) {
|
|
console.error(err);
|
|
});
|
|
});
|
|
|
|
// We have new default ringtones in 2.0, so check if the version is upgraded
|
|
// then execute the necessary migration.
|
|
if (Service.query('justUpgraded')) {
|
|
LazyLoader.load('js/tone_upgrader.js').then(function() {
|
|
toneUpgrader.perform('alerttone');
|
|
})['catch'](function(err) {
|
|
console.error(err);
|
|
});
|
|
}
|
|
Service.register('clearAll', this);
|
|
Service.register('addUnreadNotification', this);
|
|
Service.register('removeUnreadNotification', this);
|
|
|
|
this.resendStoredNotifications();
|
|
},
|
|
|
|
handleEvent: function ns_handleEvent(evt) {
|
|
switch (evt.type) {
|
|
case 'mozChromeNotificationEvent':
|
|
var detail = evt.detail;
|
|
switch (detail.type) {
|
|
case 'desktop-notification':
|
|
this.addNotification(detail);
|
|
if (this.isResending) {
|
|
this.resendReceived++;
|
|
this.isResending = (this.resendReceived < this.resendExpecting);
|
|
}
|
|
break;
|
|
case 'desktop-notification-close':
|
|
this.removeNotification(detail.id);
|
|
break;
|
|
}
|
|
break;
|
|
case 'tap':
|
|
var target = evt.target;
|
|
this.tap(target);
|
|
break;
|
|
case 'touchstart':
|
|
this.touchstart(evt);
|
|
break;
|
|
case 'touchmove':
|
|
this.touchmove(evt);
|
|
break;
|
|
case 'touchend':
|
|
this.touchend(evt);
|
|
break;
|
|
case 'touchcancel':
|
|
this.touchcancel(evt);
|
|
break;
|
|
case 'wheel':
|
|
this.wheel(evt);
|
|
break;
|
|
case 'utilitytrayshow':
|
|
this.updateTimestamps();
|
|
this.hideNotificationIndicator();
|
|
break;
|
|
case 'visibilitychange':
|
|
//update timestamps in lockscreen notifications
|
|
if (!document.hidden) {
|
|
this.updateTimestamps();
|
|
}
|
|
break;
|
|
case 'ftuopen':
|
|
this.toaster.removeEventListener('tap', this);
|
|
break;
|
|
case 'ftudone':
|
|
this.toaster.addEventListener('tap', this);
|
|
this.updateNotificationIndicator();
|
|
break;
|
|
case 'desktop-notification-resend':
|
|
this.resendExpecting = evt.detail.number;
|
|
if (this.resendExpecting) {
|
|
this.isResending = true;
|
|
}
|
|
break;
|
|
case 'lockscreen-appclosed':
|
|
// UX require to delay to clear notifications from
|
|
// LockScreen for the actionable LockScreen notifications.
|
|
setTimeout((function() {
|
|
this.clearLockScreen();
|
|
}).bind(this), 400);
|
|
break;
|
|
}
|
|
},
|
|
|
|
cancelSwipe: function ns_cancelSwipe() {
|
|
var notification = this._notification;
|
|
this._notification = null;
|
|
|
|
// If the notification has been moved, animate it back to its original
|
|
// position.
|
|
if (this._touchPosX) {
|
|
notification.addEventListener('transitionend',
|
|
function trListener() {
|
|
notification.removeEventListener('transitionend', trListener);
|
|
notification.classList.remove('snapback');
|
|
});
|
|
notification.classList.add('snapback');
|
|
}
|
|
|
|
notification.style.transform = '';
|
|
},
|
|
|
|
// Swipe handling
|
|
touchstart: function ns_touchstart(evt) {
|
|
if (evt.touches.length !== 1) {
|
|
if (this._touching) {
|
|
this._touching = false;
|
|
this.cancelSwipe();
|
|
}
|
|
return;
|
|
}
|
|
|
|
var target = evt.touches[0].target;
|
|
if (!target.dataset.notificationId) {
|
|
return;
|
|
}
|
|
|
|
this._notification = target;
|
|
this._containerWidth = this.container.clientWidth;
|
|
this._touchStartX = evt.touches[0].pageX;
|
|
this._touchStartY = evt.touches[0].pageY;
|
|
this._touchPosX = 0;
|
|
this._touchStartTime = evt.timeStamp;
|
|
this._touching = true;
|
|
this._isTap = true;
|
|
},
|
|
|
|
touchmove: function ns_touchmove(evt) {
|
|
if (!this._touching) {
|
|
return;
|
|
}
|
|
|
|
var touchDiffY = evt.touches[0].pageY - this._touchStartY;
|
|
|
|
// The notification being touched is the toast
|
|
if (this._notification.classList.contains('displayed')) {
|
|
if (touchDiffY < 0) {
|
|
this.closeToast();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we're swiping on a notification, which should only be allowed
|
|
// when the utility tray is fully open.
|
|
if (evt.touches.length !== 1 ||
|
|
!Service.query('UtilityTray.shown') ||
|
|
(this._isTap && Math.abs(touchDiffY) >= this.SCROLL_THRESHOLD)) {
|
|
this._touching = false;
|
|
this.cancelSwipe();
|
|
return;
|
|
}
|
|
|
|
this._touchPosX = evt.touches[0].pageX - this._touchStartX;
|
|
if (Math.abs(this._touchPosX) >= this.TAP_THRESHOLD) {
|
|
this._isTap = false;
|
|
this.notificationsContainer.style.overflow = 'hidden';
|
|
}
|
|
if (!this._isTap) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation(); // Prevent this event from bubbling up to the tray.
|
|
this._notification.style.transform =
|
|
'translateX(' + this._touchPosX + 'px)';
|
|
}
|
|
},
|
|
|
|
touchend: function ns_touchend(evt) {
|
|
if (!this._touching) {
|
|
return;
|
|
}
|
|
|
|
evt.preventDefault();
|
|
this._touching = false;
|
|
|
|
this.notificationsContainer.style.overflow = '';
|
|
|
|
if (this._isTap) {
|
|
var event = new CustomEvent('tap', {
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
this._notification.dispatchEvent(event);
|
|
this._notification = null;
|
|
return;
|
|
}
|
|
|
|
// Taking speed into account to detect swipes
|
|
var deltaT = evt.timeStamp - this._touchStartTime;
|
|
var speed = this._touchPosX / deltaT;
|
|
var inertia = speed * this.SWIPE_INERTIA;
|
|
var finalDelta = Math.abs(this._touchPosX + inertia);
|
|
|
|
if (finalDelta >
|
|
this._containerWidth * this.TRANSITION_FRACTION) {
|
|
if (this._touchPosX < 0) {
|
|
this._notification.classList.add('left');
|
|
}
|
|
|
|
var durationLeft = (1 - (finalDelta / this._containerWidth)) *
|
|
this.TRANSITION_DURATION;
|
|
this.swipeCloseNotification(durationLeft);
|
|
} else {
|
|
this.cancelSwipe();
|
|
}
|
|
},
|
|
|
|
touchcancel: function ns_touchcancel(evt) {
|
|
if (this._touching) {
|
|
evt.preventDefault();
|
|
this._touching = false;
|
|
this.cancelSwipe();
|
|
}
|
|
},
|
|
|
|
wheel: function ns_wheel(evt) {
|
|
if (evt.deltaMode === evt.DOM_DELTA_PAGE && evt.deltaX) {
|
|
this._notification = evt.target;
|
|
this.swipeCloseNotification();
|
|
}
|
|
},
|
|
|
|
tap: function ns_tap(node) {
|
|
var notificationId = node.dataset.notificationId;
|
|
var notificationNode = this.container.querySelector(
|
|
'[data-notification-id="' + notificationId + '"]');
|
|
|
|
var event = document.createEvent('CustomEvent');
|
|
event.initCustomEvent('mozContentNotificationEvent', true, true, {
|
|
type: 'desktop-notification-click',
|
|
id: notificationId
|
|
});
|
|
window.dispatchEvent(event);
|
|
|
|
window.dispatchEvent(new CustomEvent('notification-clicked', {
|
|
detail: {
|
|
id: notificationId
|
|
}
|
|
}));
|
|
|
|
// Desktop notifications are removed when they are clicked (see bug 890440)
|
|
if (notificationNode &&
|
|
notificationNode.dataset.type === 'desktop-notification' &&
|
|
notificationNode.dataset.obsoleteAPI === 'true') {
|
|
this.closeNotification(notificationNode);
|
|
}
|
|
|
|
if (node == this.toaster) {
|
|
this.closeToast();
|
|
} else {
|
|
Service.request('UtilityTray:hide');
|
|
}
|
|
},
|
|
|
|
updateTimestamps: function ns_updateTimestamps() {
|
|
var timestamps = [...document.querySelectorAll('.timestamp')];
|
|
var formatter = mozIntl._gaia.RelativeDate(navigator.languages, {
|
|
style: 'short'
|
|
});
|
|
|
|
timestamps.forEach(timestamp => {
|
|
formatter.formatElement(
|
|
timestamp, new Date(timestamp.dataset.timestamp));
|
|
});
|
|
},
|
|
|
|
updateToaster: function ns_updateToaster(detail, type, dir) {
|
|
if (detail.icon) {
|
|
this.toasterIcon.src = detail.icon;
|
|
this.toasterIcon.hidden = false;
|
|
} else {
|
|
this.toasterIcon.hidden = true;
|
|
}
|
|
|
|
this.toaster.dataset.notificationId = detail.id;
|
|
this.toaster.dataset.type = type;
|
|
this.toaster.lang = detail.lang;
|
|
|
|
this.toasterTitle.textContent = detail.title;
|
|
this.toasterTitle.dir = dir;
|
|
this.toasterDetail.textContent = detail.text;
|
|
this.toasterDetail.dir = dir;
|
|
},
|
|
|
|
addNotification: function ns_addNotification(detail) {
|
|
// LockScreen window may not opened while this singleton got initialized.
|
|
this.lockScreenContainer = this.getLockScreenContainer();
|
|
|
|
var manifestURL = detail.manifestURL || '';
|
|
var behavior = detail.mozbehavior || {};
|
|
var isPriorityNotification =
|
|
this.PRIORITY_APPLICATIONS.indexOf(manifestURL) !== -1;
|
|
var dir = detail.dir === 'ltr' || detail.dir === 'rtl' ?
|
|
detail.dir : 'auto';
|
|
|
|
var notificationContainer =
|
|
(isPriorityNotification) ?
|
|
this.container.querySelector('.priority-notifications') :
|
|
this.container.querySelector('.other-notifications');
|
|
|
|
// We need to animate the ambient indicator when the toast
|
|
// timesout, so we skip updating it here, by passing a skip bool
|
|
this.addUnreadNotification(detail.id, true);
|
|
|
|
var notificationNode = document.createElement('div');
|
|
notificationNode.classList.add('notification');
|
|
notificationNode.setAttribute('role', 'link');
|
|
notificationNode.setAttribute('tabindex', 0);
|
|
|
|
notificationNode.dataset.notificationId = detail.id;
|
|
notificationNode.dataset.noClear = behavior.noclear ? 'true' : 'false';
|
|
notificationNode.lang = detail.lang;
|
|
|
|
notificationNode.dataset.obsoleteAPI = 'false';
|
|
if (typeof detail.id === 'string' &&
|
|
detail.id.indexOf('app-notif-') === 0) {
|
|
notificationNode.dataset.obsoleteAPI = 'true';
|
|
}
|
|
var type = detail.type || 'desktop-notification';
|
|
notificationNode.dataset.type = type;
|
|
notificationNode.dataset.manifestURL = manifestURL;
|
|
var icon;
|
|
if (detail.icon) {
|
|
icon = document.createElement('img');
|
|
icon.src = detail.icon;
|
|
icon.setAttribute('role', 'presentation');
|
|
notificationNode.appendChild(icon);
|
|
}
|
|
|
|
var titleContainer = document.createElement('div');
|
|
titleContainer.classList.add('title-container');
|
|
|
|
var title = document.createElement('div');
|
|
title.classList.add('title');
|
|
title.textContent = detail.title;
|
|
title.dir = dir;
|
|
|
|
titleContainer.appendChild(title);
|
|
|
|
var time = document.createElement('span');
|
|
var timestamp = detail.timestamp ? new Date(detail.timestamp) : new Date();
|
|
time.classList.add('timestamp');
|
|
time.dataset.timestamp = timestamp;
|
|
var formatter = mozIntl._gaia.RelativeDate(navigator.languages, {
|
|
style: 'short'
|
|
});
|
|
formatter.formatElement(time, timestamp);
|
|
titleContainer.appendChild(time);
|
|
|
|
notificationNode.appendChild(titleContainer);
|
|
|
|
var message = document.createElement('div');
|
|
message.classList.add('detail');
|
|
var messageContent = document.createElement('div');
|
|
messageContent.classList.add('detail-content');
|
|
messageContent.textContent = detail.text;
|
|
messageContent.dir = dir;
|
|
message.appendChild(messageContent);
|
|
notificationNode.appendChild(message);
|
|
|
|
var notifSelector = '[data-notification-id="' + detail.id + '"]';
|
|
var oldNotif = notificationContainer.querySelector(notifSelector);
|
|
if (oldNotif) {
|
|
// The whole node cannot be replaced because CSS animations are re-started
|
|
oldNotif.replaceChild(titleContainer,
|
|
oldNotif.querySelector('.title-container'));
|
|
oldNotif.replaceChild(message, oldNotif.querySelector('.detail'));
|
|
var oldIcon = oldNotif.querySelector('img');
|
|
if (icon) {
|
|
oldIcon ? oldIcon.src = icon.src : oldNotif.insertBefore(icon,
|
|
oldNotif.firstChild);
|
|
} else if (oldIcon) {
|
|
oldNotif.removeChild(oldIcon);
|
|
}
|
|
// but we still need to update type and lang.
|
|
oldNotif.dataset.type = type;
|
|
oldNotif.lang = detail.lang;
|
|
|
|
notificationNode = oldNotif;
|
|
} else {
|
|
notificationContainer.insertBefore(notificationNode,
|
|
notificationContainer.firstElementChild);
|
|
}
|
|
|
|
var event = document.createEvent('CustomEvent');
|
|
event.initCustomEvent('mozContentNotificationEvent', true, true, {
|
|
type: 'desktop-notification-show',
|
|
id: detail.id
|
|
});
|
|
window.dispatchEvent(event);
|
|
|
|
// We turn the screen on if needed in order to let
|
|
// the user see the notification toaster unless we're on a call
|
|
if (!Service.query('screenEnabled') &&
|
|
!behavior.noscreen &&
|
|
!Service.query('onCall') &&
|
|
(!Service.query('locked') || this.lockscreenPreview)) {
|
|
Service.request('turnScreenOn');
|
|
}
|
|
|
|
var notify = !('noNotify' in detail) &&
|
|
// don't notify for network-alerts notifications
|
|
(this.SILENT_APPLICATIONS.indexOf(manifestURL) === -1);
|
|
|
|
// Notification toaster
|
|
if (notify) {
|
|
this.updateToaster(detail, type, dir);
|
|
if (this.lockscreenPreview || !window.Service.query('locked')) {
|
|
this.toaster.classList.add('displayed');
|
|
|
|
if (this._toasterTimeout) {
|
|
clearTimeout(this._toasterTimeout);
|
|
}
|
|
|
|
this._toasterTimeout = setTimeout((function() {
|
|
this.closeToast();
|
|
this._toasterTimeout = null;
|
|
}).bind(this), this.TOASTER_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
// Adding it to the lockscreen if locked and the privacy setting
|
|
// does not prevent it.
|
|
if (Service.query('locked') && this.lockscreenPreview) {
|
|
this.addLockScreenNotification(detail.id,
|
|
notificationNode.cloneNode(true));
|
|
}
|
|
|
|
if (notify && !this.isResending &&
|
|
(!Service.query('locked') || this.lockscreenPreview)) {
|
|
if (!this.silent) {
|
|
var ringtonePlayer = new Audio();
|
|
var telephony = window.navigator.mozTelephony;
|
|
|
|
ringtonePlayer.src = behavior.soundFile || this._sound;
|
|
|
|
if (telephony && telephony.active) {
|
|
ringtonePlayer.mozAudioChannelType = 'telephony';
|
|
ringtonePlayer.volume = 0.3;
|
|
} else {
|
|
ringtonePlayer.mozAudioChannelType = 'notification';
|
|
}
|
|
ringtonePlayer.play();
|
|
window.setTimeout(function smsRingtoneEnder() {
|
|
ringtonePlayer.pause();
|
|
ringtonePlayer.removeAttribute('src');
|
|
ringtonePlayer.load();
|
|
}, 4000);
|
|
}
|
|
|
|
if (this.vibrates) {
|
|
var pattern = [200, 200, 200];
|
|
if (behavior.vibrationPattern && behavior.vibrationPattern.length &&
|
|
behavior.vibrationPattern[0] > 0) {
|
|
pattern = behavior.vibrationPattern;
|
|
}
|
|
|
|
// bug 1030310: disable vibration for the email app when asleep
|
|
// bug 1050023: disable vibration for downloads when asleep
|
|
var vibrateWhenAsleep =
|
|
type.indexOf('download-notification-downloading') === -1 &&
|
|
manifestURL.indexOf('email.gaiamobile.org') === -1;
|
|
|
|
if (vibrateWhenAsleep || !document.hidden) {
|
|
navigator.vibrate(pattern);
|
|
}
|
|
}
|
|
}
|
|
|
|
// must be at least one notification now
|
|
this.clearAllButton.disabled = false;
|
|
|
|
return notificationNode;
|
|
},
|
|
|
|
/**
|
|
* Give a notification node and add it to LockScreen via event.
|
|
*/
|
|
addLockScreenNotification: function ns_addLockScreenNotification(id, node) {
|
|
window.dispatchEvent(
|
|
new window.CustomEvent('lockscreen-notification-request-append', { detail:
|
|
{
|
|
id: id,
|
|
node: node
|
|
}
|
|
}));
|
|
},
|
|
|
|
swipeCloseNotification: function ns_swipeCloseNotification(duration) {
|
|
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';
|
|
});
|
|
});
|
|
});
|
|
|
|
notification.classList.add('disappearing');
|
|
duration = duration || this.TRANSITION_DURATION;
|
|
notification.style.transitionDuration = duration + 'ms';
|
|
notification.style.transform = '';
|
|
},
|
|
|
|
addUnreadNotification: function ns_addUnreadNotification(id, skipUpdate) {
|
|
if (Service.query('UtilityTray.shown')) {
|
|
return;
|
|
}
|
|
this.unreadNotifications.push(id);
|
|
if (!skipUpdate) {
|
|
this.updateNotificationIndicator();
|
|
}
|
|
},
|
|
|
|
removeUnreadNotification: function ns_removeUnreadNotification(id) {
|
|
var notifIndex = this.unreadNotifications.indexOf(id);
|
|
if (notifIndex > -1) {
|
|
this.unreadNotifications.splice(notifIndex, 1);
|
|
}
|
|
this.updateNotificationIndicator();
|
|
},
|
|
|
|
hideNotificationIndicator: function ns_hideNotificationIndicator() {
|
|
if (this.unreadNotifications.length > 0) {
|
|
this.unreadNotifications = [];
|
|
}
|
|
this.updateNotificationIndicator();
|
|
},
|
|
|
|
updateNotificationIndicator: function ns_updateNotificationIndicator() {
|
|
if (this.unreadNotifications.length) {
|
|
// If the ftu is running we should not show the ambient indicator
|
|
// because the statusbar is not accessible
|
|
if (Service.query('isFtuRunning')) {
|
|
return;
|
|
}
|
|
this.ambientIndicator.classList.add('unread');
|
|
navigator.mozL10n.setAttributes(
|
|
this.ambientIndicator,
|
|
'statusbarNotifications-unread',
|
|
{n: this.unreadNotifications.length}
|
|
);
|
|
} else {
|
|
this.ambientIndicator.classList.remove('unread');
|
|
this.ambientIndicator.removeAttribute('aria-label');
|
|
}
|
|
|
|
Service.request('UtilityTray:updateNotificationCount');
|
|
},
|
|
|
|
closeToast: function ns_closeToast() {
|
|
this.toaster.classList.remove('displayed');
|
|
this.updateNotificationIndicator();
|
|
},
|
|
|
|
closeNotification: function ns_closeNotification(notificationNode) {
|
|
var notificationId = notificationNode.dataset.notificationId;
|
|
this.removeNotification(notificationId);
|
|
},
|
|
|
|
removeLockScreenNotification:
|
|
function ns_removeLockScreenNotification(notificationId) {
|
|
var lockScreenNotificationNode;
|
|
var notifSelector = '[data-notification-id="' + notificationId + '"]';
|
|
this.lockScreenContainer = this.getLockScreenContainer();
|
|
if (this.lockScreenContainer) {
|
|
lockScreenNotificationNode =
|
|
this.lockScreenContainer.querySelector(notifSelector);
|
|
}
|
|
|
|
if (lockScreenNotificationNode) {
|
|
var lockScreenNotificationParentNode =
|
|
lockScreenNotificationNode.parentNode;
|
|
lockScreenNotificationParentNode.removeChild(lockScreenNotificationNode);
|
|
window.dispatchEvent(
|
|
new window.CustomEvent('lockscreen-notification-request-remove', {
|
|
containerEmpty: !lockScreenNotificationParentNode.firstElementChild
|
|
}));
|
|
}
|
|
},
|
|
|
|
removeNotification: function ns_removeNotification(notificationId) {
|
|
var notifSelector = '[data-notification-id="' + notificationId + '"]';
|
|
var notificationNode = this.container.querySelector(notifSelector);
|
|
if (notificationNode) {
|
|
notificationNode.remove();
|
|
}
|
|
var event = document.createEvent('CustomEvent');
|
|
event.initCustomEvent('mozContentNotificationEvent', true, true, {
|
|
type: 'desktop-notification-close',
|
|
id: notificationId
|
|
});
|
|
window.dispatchEvent(event);
|
|
// UX require to give a tiny delay for actionable notification on
|
|
// LockScreen.
|
|
setTimeout((function() {
|
|
this.removeLockScreenNotification(notificationId);
|
|
}).bind(this), 400);
|
|
|
|
this.removeUnreadNotification(notificationId);
|
|
if (!this.container.querySelector('.notification')) {
|
|
// no notifications left
|
|
this.clearAllButton.disabled = true;
|
|
}
|
|
},
|
|
|
|
clearAll: function ns_clearAll() {
|
|
var notifications = this.container.querySelectorAll('.notification');
|
|
var clearable = [].slice.apply(notifications)
|
|
.filter(function isClearable(notification) {
|
|
return notification.dataset.noClear !== 'true';
|
|
});
|
|
var notification;
|
|
this.clearAllButton.disabled = true;
|
|
// When focus is on a disabled element, Gecko will not dispatch any keyboard
|
|
// events to it (the disabled element) and its parent.
|
|
// The clearAll Button is focused but disabled right away after
|
|
// user click it. We need to blur the focus immediately, otherwise System
|
|
// app could not receive any keyboard events. See more detail on
|
|
// http://bugzil.la/1106844
|
|
this.clearAllButton.blur();
|
|
if (!clearable.length) {
|
|
return;
|
|
}
|
|
// Adding a callback to the last cleared notification to defer
|
|
// the destroying of the notifications after the last disappear
|
|
var lastClearable = clearable[clearable.length - 1];
|
|
var removeAll = (function removeAll() {
|
|
lastClearable.removeEventListener('transitionend', removeAll);
|
|
for (var notification of clearable) {
|
|
this.closeNotification(notification);
|
|
}
|
|
}).bind(this);
|
|
lastClearable.addEventListener('transitionend', removeAll);
|
|
|
|
var disappearingClass = document.dir === 'ltr' ?
|
|
'disappearing-via-clear-all' : 'disappearing-via-clear-all-left';
|
|
for (var index = 0, max = clearable.length; index < max; index++) {
|
|
notification = clearable[index];
|
|
notification.style.transitionDelay = (this.CLEAR_DELAY * index) + 'ms';
|
|
notification.classList.add(disappearingClass);
|
|
notification.style.transform = '';
|
|
}
|
|
},
|
|
|
|
clearLockScreen: function ns_clearLockScreen() {
|
|
// The LockScreenWindow may not be instantiated yet.
|
|
if (!this.lockScreenContainer) {
|
|
return;
|
|
}
|
|
while (this.lockScreenContainer.firstElementChild) {
|
|
var element = this.lockScreenContainer.firstElementChild;
|
|
this.lockScreenContainer.removeChild(element);
|
|
}
|
|
window.dispatchEvent(
|
|
new window.CustomEvent('lockscreen-notification-request-clear'));
|
|
},
|
|
|
|
resendStoredNotifications: function() {
|
|
if ('mozSettings' in navigator && navigator.mozSettings) {
|
|
var key = 'notifications.resend';
|
|
var req = navigator.mozSettings.createLock().get(key);
|
|
req.onsuccess = req.onerror = function onsuccess() {
|
|
var resendEnabled = req.result[key] || false;
|
|
if (!resendEnabled) {
|
|
return;
|
|
}
|
|
|
|
var resendCallback = (function(number) {
|
|
window.dispatchEvent(
|
|
new CustomEvent('desktop-notification-resend',
|
|
{ detail: { number: number } }));
|
|
}).bind(this);
|
|
|
|
if ('mozChromeNotifications' in navigator) {
|
|
navigator.mozChromeNotifications.
|
|
mozResendAllNotifications(resendCallback);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
SettingsListener.observe(
|
|
'lockscreen.notifications-preview.enabled', true, function(value) {
|
|
|
|
NotificationScreen.lockscreenPreview = value;
|
|
});
|
|
|
|
SettingsListener.observe('audio.volume.notification', 7, function(value) {
|
|
NotificationScreen.silent = (value === 0);
|
|
});
|
|
|
|
SettingsListener.observe('vibration.enabled', true, function(value) {
|
|
NotificationScreen.vibrates = value;
|
|
});
|