Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
86
assets/webapps/system.gaiamobile.org/lockscreen/js/clock.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* Creates an object used for refreshing the clock UI element. Handles all
|
||||
* related timer manipulation (start/stop/cancel).
|
||||
* @class Clock
|
||||
*/
|
||||
function Clock() {
|
||||
/**
|
||||
* One-shot timer used to refresh the clock at a minute's turn
|
||||
* @memberOf Clock
|
||||
*/
|
||||
this.timeoutID = null;
|
||||
|
||||
/**
|
||||
* Timer used to refresh the clock every minute
|
||||
* @memberOf Clock
|
||||
*/
|
||||
this.timerID = null;
|
||||
|
||||
/**
|
||||
* Start the timer used to refresh the clock, will call the specified
|
||||
* callback at every timer tick to refresh the UI. The callback used to
|
||||
* refresh the UI will also be called immediately to ensure the UI is
|
||||
* consistent.
|
||||
*
|
||||
* @param {Function} refresh Function used to refresh the UI at every timer
|
||||
* tick, should accept a date object as its only argument.
|
||||
* @memberOf Clock
|
||||
*/
|
||||
this.start = function cl_start(refresh) {
|
||||
var date = new Date();
|
||||
var self = this;
|
||||
|
||||
refresh(date);
|
||||
|
||||
if (this.timeoutID == null) {
|
||||
this.timeoutID = window.setTimeout(function cl_setClockInterval() {
|
||||
|
||||
if (self.timerID == null) {
|
||||
self.timerID = window.setInterval(function cl_clockInterval() {
|
||||
refresh(new Date());
|
||||
}, 60000);
|
||||
}
|
||||
}, (60 - date.getSeconds()) * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops the timer used to refresh the clock
|
||||
* @memberOf Clock
|
||||
*/
|
||||
this.stop = function cl_stop() {
|
||||
if (this.timeoutID != null) {
|
||||
window.clearTimeout(this.timeoutID);
|
||||
this.timeoutID = null;
|
||||
}
|
||||
|
||||
if (this.timerID != null) {
|
||||
window.clearInterval(this.timerID);
|
||||
this.timerID = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @exports Clock */
|
||||
exports.Clock = Clock;
|
||||
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/* global LockScreenStateLogger */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Component provides:
|
||||
*
|
||||
* 1. Resource keeper: to let all states share the same resources (cache).
|
||||
* 2. Reference to the current activate state: so that parent component can
|
||||
* command and wait the sub-components to do things without tracking the
|
||||
* actual active state.
|
||||
*
|
||||
* Every states of this component would receive the Component instance as
|
||||
* a way to access these common resources & properties. And every state
|
||||
* transferring would done by the 'transferTo' method in this component,
|
||||
* so that the component can update the active state correctly.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* View is the only thing parent component needs to manage.
|
||||
*/
|
||||
var LockScreenBasicComponent = function(view) {
|
||||
this._subcomponents = null;
|
||||
this._activeState = null;
|
||||
// Concrete components should extend these to let States access them.
|
||||
// The first state component kick off should take responsibility for
|
||||
// initializing these things.
|
||||
//
|
||||
// Resources is for external resources like settings value or DOM elements.
|
||||
this.resources = {
|
||||
elements: {
|
||||
view: view
|
||||
}
|
||||
};
|
||||
this.configs = {
|
||||
logger: {
|
||||
debug: false // turn on it when we're debugging this component
|
||||
}
|
||||
};
|
||||
this.logger = new LockScreenStateLogger();
|
||||
};
|
||||
|
||||
/**
|
||||
* State' phase is the component's phase.
|
||||
*/
|
||||
LockScreenBasicComponent.prototype.phase =
|
||||
function() {
|
||||
return this._activeState.phase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Every state of this component should call the method to do transferring,
|
||||
* so that the component can update the 'activeState' correctly.
|
||||
*
|
||||
* The order of transferring is:
|
||||
*
|
||||
* [current.stop] -> [next.start] -> (call)[previous.destroy]
|
||||
*
|
||||
*/
|
||||
LockScreenBasicComponent.prototype.transferTo = function(clazz) {
|
||||
var nextState = new clazz(this);
|
||||
var currentState = this._activeState;
|
||||
this._previousState = currentState;
|
||||
this._activeState = nextState;
|
||||
this.logger.transfer(currentState.configs.name, nextState.configs.name);
|
||||
return currentState.stop()
|
||||
.next(() => nextState.start());
|
||||
};
|
||||
|
||||
/**
|
||||
* Would receive resources from parent and *extends* the default one.
|
||||
* After that, transfer to the next state, which is usually an initialization
|
||||
* state, that would do lots of sync/async things to update the
|
||||
* resources & properties.
|
||||
*
|
||||
* However, since basic component couldn't know what is the
|
||||
* initialization state, so that the concrete component should
|
||||
* implement the setup function, which would return the state after
|
||||
* receive the component instance.
|
||||
*/
|
||||
LockScreenBasicComponent.prototype.start = function(resources) {
|
||||
this.logger.start(this.configs.logger);
|
||||
if (resources) {
|
||||
for (var key in this.resources) {
|
||||
if ('undefined' !== resources[key]) {
|
||||
this.resources[key] = resources[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get the initialization state and let it fetch & set all.
|
||||
// 'initializeStateMachine', if Java doomed the world.
|
||||
// (and this is ECMAScript, a language (partially) inspired by Scheme!).
|
||||
this._activeState = this.setup();
|
||||
return this._activeState.start();
|
||||
};
|
||||
|
||||
/**
|
||||
* Receive the component instance and return a initialization state
|
||||
* which would fetch resources and set properties for this component.
|
||||
*
|
||||
* The concrete component should implement this.
|
||||
*/
|
||||
LockScreenBasicComponent.prototype.setup = function(component) {};
|
||||
|
||||
LockScreenBasicComponent.prototype.stop = function() {
|
||||
return this._activeState.stop();
|
||||
};
|
||||
|
||||
LockScreenBasicComponent.prototype.destroy = function() {
|
||||
return this._activeState.destroy()
|
||||
.next(() => { this.logger.stop(); });
|
||||
};
|
||||
|
||||
LockScreenBasicComponent.prototype.live = function() {
|
||||
return this._activeState.until('stop');
|
||||
};
|
||||
|
||||
LockScreenBasicComponent.prototype.exist = function() {
|
||||
return this._activeState.until('destroy');
|
||||
};
|
||||
|
||||
exports.LockScreenBasicComponent = LockScreenBasicComponent;
|
||||
})(window);
|
||||
|
||||
1009
assets/webapps/system.gaiamobile.org/lockscreen/js/lockscreen.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This is the basic prototype of LockScreen states.
|
||||
* Extend this prototype to implement your state.
|
||||
*/
|
||||
(function(exports) {
|
||||
var LockScreenBaseState = function() {};
|
||||
LockScreenBaseState.prototype.start = function() {
|
||||
return this;
|
||||
};
|
||||
LockScreenBaseState.prototype.transferOut = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
LockScreenBaseState.prototype.transferTo = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenBaseState = LockScreenBaseState;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/* global LockScreen, LockScreenNotifications, LockScreenFrameAgent */
|
||||
'use strict';
|
||||
(function() {
|
||||
window.addEventListener('lockscreen-frame-bootstrap', function startup() {
|
||||
// XXX: Because we don't have real app bootstraping yet.
|
||||
window.lockScreenFrameAgent = new LockScreenFrameAgent(window.parent);
|
||||
window.lockScreenFrameAgent.start();
|
||||
this.lockscreen = new LockScreen();
|
||||
window.lockScreen = this.lockscreen;
|
||||
window.lockScreenStateManager = new window.LockScreenStateManager();
|
||||
window.lockScreenStateManager.start(window.lockScreen);
|
||||
window.lockScreenNotifications = new LockScreenNotifications();
|
||||
// After Bug 1094759, screen locker initialises itself asynchronously,
|
||||
// so we need to make sure everthing is ready for the components depends
|
||||
// on it.
|
||||
this.lockscreen.bootstrapping.then(() => {
|
||||
window.lockScreenNotifications.start(this.lockscreen,
|
||||
this.lockscreen.notificationsContainer);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
'use strict';
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* Creates an object used for refreshing the charging string UI element.
|
||||
* @class LockScreenChargingStatus
|
||||
*/
|
||||
function LockScreenChargingStatus() {
|
||||
this.elements = {
|
||||
charging: document.getElementById('lockscreen-charging')
|
||||
};
|
||||
|
||||
// Create a text node so we can access its data later for updating the status.
|
||||
if (!this.elements.charging.firstChild) {
|
||||
this.elements.charging.appendChild(document.createTextNode(''));
|
||||
}
|
||||
}
|
||||
|
||||
LockScreenChargingStatus.listenEvents = [
|
||||
'chargingchange',
|
||||
'levelchange',
|
||||
'chargingtimechange'
|
||||
];
|
||||
|
||||
LockScreenChargingStatus.prototype.start = function cs_start() {
|
||||
this.refresh();
|
||||
|
||||
LockScreenChargingStatus.listenEvents.forEach(
|
||||
ename => navigator.battery.addEventListener(ename, this));
|
||||
};
|
||||
|
||||
LockScreenChargingStatus.prototype.stop = function cs_stop() {
|
||||
LockScreenChargingStatus.listenEvents.forEach(
|
||||
ename => navigator.battery.removeEventListener(ename, this));
|
||||
};
|
||||
|
||||
LockScreenChargingStatus.prototype.handleEvent = function cs_handleEvent(evt) {
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
LockScreenChargingStatus.prototype.refresh = function cs_refresh() {
|
||||
|
||||
if (!navigator.battery.charging) {
|
||||
if (!this.elements.charging.hasAttribute('hidden')) {
|
||||
this.elements.charging.setAttribute('hidden', '');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var l10nAttrs = {
|
||||
id: null,
|
||||
args: {
|
||||
level: parseInt(navigator.battery.level * 100)
|
||||
}
|
||||
};
|
||||
|
||||
var chargingTime = navigator.battery.chargingTime;
|
||||
|
||||
if (chargingTime === Infinity ||
|
||||
chargingTime < 60) { // less than a minute remaining
|
||||
l10nAttrs.id = 'charging-no-time';
|
||||
} else {
|
||||
var timeLeft = new Date(0, 0, 0, 0, 0, chargingTime);
|
||||
l10nAttrs.args.hours = timeLeft.getHours();
|
||||
l10nAttrs.args.minutes = timeLeft.getMinutes();
|
||||
|
||||
if (l10nAttrs.args.hours > 0) {
|
||||
l10nAttrs.id = 'charging-hours';
|
||||
} else {
|
||||
l10nAttrs.id = 'charging-minutes';
|
||||
}
|
||||
}
|
||||
|
||||
// Update data here since changing textContent will replace the text node and
|
||||
// could cause flickering while transitioning docshell.isActive to true.
|
||||
navigator.mozL10n.formatValue(l10nAttrs.id, l10nAttrs.args).then((value) => {
|
||||
this.elements.charging.firstChild.data = value;
|
||||
});
|
||||
|
||||
if (this.elements.charging.hasAttribute('hidden')) {
|
||||
this.elements.charging.removeAttribute('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
/** @exports LockScreenChargingStatus */
|
||||
exports.LockScreenChargingStatus = LockScreenChargingStatus;
|
||||
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* XXX: The name here is a temporary name. If we're in a LockScreen app,
|
||||
* it should be 'SystemAgent' as a counterpart of 'LockScreenAgent'
|
||||
* in System app. However, to put a 'SystemAgent' in System app
|
||||
* would confuse people.
|
||||
*
|
||||
* Forward all public events inside the LockScreen frame to System.
|
||||
* LockScreen --> System
|
||||
*/
|
||||
'use strict';
|
||||
(function(exports) {
|
||||
var LockScreenFrameAgent = function(targetWindow) {
|
||||
this.configs = {
|
||||
listens: [
|
||||
'unlocking-start',
|
||||
'unlocking-stop',
|
||||
'secure-killapps',
|
||||
'secure-closeapps',
|
||||
'secure-launchapp',
|
||||
'secure-modeoff',
|
||||
'secure-modeon',
|
||||
'lockpanelchange',
|
||||
'lockscreen-request-unlock',
|
||||
'lockscreen-request-mediacommand'
|
||||
]
|
||||
};
|
||||
this.targetWindow = targetWindow;
|
||||
};
|
||||
LockScreenFrameAgent.prototype.start = function() {
|
||||
this.configs.listens.forEach((ename) => {
|
||||
window.addEventListener(ename, this);
|
||||
});
|
||||
};
|
||||
|
||||
LockScreenFrameAgent.prototype.handleEvent = function(event) {
|
||||
// XXX: Before we make LockScreen really as an iframe,
|
||||
// do nothing and let events pass through.
|
||||
// After we put LockScreen in an iframe, re-create the event and
|
||||
// dispatch it to System's window.
|
||||
};
|
||||
|
||||
LockScreenFrameAgent.prototype.stop = function() {
|
||||
this.configs.listens.forEach((ename) => {
|
||||
window.removeEventListener(ename, this);
|
||||
});
|
||||
};
|
||||
exports.LockScreenFrameAgent = LockScreenFrameAgent;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,379 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* XXX: Before we use real keyboard as input pad, we need this to
|
||||
* control the custom input pad. See Bug 1053680.
|
||||
**/
|
||||
(function(exports) {
|
||||
/**
|
||||
* We still need the interface to notify the LockScreen.
|
||||
*
|
||||
* @param LockScreenFaçade
|
||||
**/
|
||||
var LockScreenInputpad = function(lockScreen) {
|
||||
this.lockScreen = lockScreen;
|
||||
this.configs = {
|
||||
padVibrationDuration: 50
|
||||
};
|
||||
this.states = {
|
||||
// Keep in sync with Dialer and Keyboard vibration
|
||||
padVibrationEnabled: false,
|
||||
passCodeEntered: '',
|
||||
passCodeErrorTimeoutPending: false
|
||||
};
|
||||
};
|
||||
LockScreenInputpad.prototype.start = function() {
|
||||
this.addEventListener('lockscreen-notify-passcode-validationfailed');
|
||||
this.addEventListener('lockscreen-notify-passcode-validationreset');
|
||||
this.addEventListener('lockscreen-notify-passcode-validationsuccess');
|
||||
this.addEventListener('lockscreen-inputappclosed', this);
|
||||
this.addEventListener('lockscreen-inputappopened', this);
|
||||
|
||||
this.passcodeCode = document.getElementById('lockscreen-passcode-code');
|
||||
this.passcodePad = document.getElementById('lockscreen-passcode-pad');
|
||||
this.emergencyCallBtn = this.passcodePad.querySelector('a[data-key=e]');
|
||||
|
||||
/* lastTouchedKey
|
||||
* Keeps track of the key that was under the finger during
|
||||
* the previous 'touchmove' event. UI must be redrawn only if
|
||||
* the finger moved to a different key.
|
||||
*/
|
||||
this.lastTouchedKey = null;
|
||||
|
||||
this.passcodePad.addEventListener('click', this);
|
||||
this.passcodePad.addEventListener('touchstart', this);
|
||||
this.passcodePad.addEventListener('touchmove', this);
|
||||
this.passcodePad.addEventListener('touchend', this);
|
||||
|
||||
window.SettingsListener.observe('keyboard.vibration',
|
||||
false, (function(value) {
|
||||
this.states.padVibrationEnabled = !!value;
|
||||
}).bind(this));
|
||||
|
||||
this.renderUI();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Rendering the whole UI, including waiting all necessary conditions.
|
||||
* So rendering functions all should be Promised.
|
||||
*/
|
||||
LockScreenInputpad.prototype.renderUI = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.toggleEmergencyButton();
|
||||
this.updatePassCodeUI();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.toggleEmergencyButton = function() {
|
||||
if ('undefined' === typeof navigator.mozTelephony ||
|
||||
!navigator.mozTelephony) {
|
||||
this.disableEmergencyButton();
|
||||
} else {
|
||||
this.enableEmergencyButton();
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.disableEmergencyButton = function() {
|
||||
this.emergencyCallBtn.classList.add('disabled');
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.enableEmergencyButton = function() {
|
||||
this.emergencyCallBtn.classList.remove('disabled');
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.handleEvent = function(evt) {
|
||||
/* The event flow from lockscreen.js is:
|
||||
* on pass code fail:
|
||||
* - 'validationfailed' -> (validation timeout) -> 'validationreset'
|
||||
* on pass code success:
|
||||
* - 'validationsuccess'
|
||||
*/
|
||||
switch (evt.type) {
|
||||
case 'lockscreen-notify-passcode-validationfailed':
|
||||
this.states.passCodeErrorTimeoutPending = true;
|
||||
this.updatePassCodeUI();
|
||||
break;
|
||||
case 'lockscreen-notify-passcode-validationreset':
|
||||
case 'lockscreen-notify-passcode-validationsuccess':
|
||||
// Currently both validationreset and validationsuccess
|
||||
// just need to reset Inputpad's internal state.
|
||||
this.states.passCodeEntered = '';
|
||||
this.states.passCodeErrorTimeoutPending = false;
|
||||
this.updatePassCodeUI();
|
||||
break;
|
||||
case 'lockscreen-inputappopened':
|
||||
case 'lockscreen-inputappclosed':
|
||||
this.updatePassCodeUI();
|
||||
break;
|
||||
case 'touchstart':
|
||||
this.onTouchStart(evt);
|
||||
break;
|
||||
case 'touchmove':
|
||||
this.onTouchMove(evt);
|
||||
break;
|
||||
case 'touchend':
|
||||
evt.preventDefault(); // prevent the 'click'
|
||||
this.onTouchEnd(evt);
|
||||
break;
|
||||
case 'click':
|
||||
this.onClick(evt);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* On touchstart, mark key under finger as active
|
||||
* and vibrate if vibration is enabled.
|
||||
*/
|
||||
LockScreenInputpad.prototype.onTouchStart = function(evt) {
|
||||
var target = this._touchTarget(evt);
|
||||
// Only act if touch landed on a key
|
||||
var key = this._keyForTarget(target);
|
||||
if (key) {
|
||||
this._visualizeKeypress(target);
|
||||
if (this.states.padVibrationEnabled &&
|
||||
!this.states.passCodeErrorTimeoutPending) {
|
||||
navigator.vibrate(this.configs.padVibrationDuration);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* On touchmove, update keypad display if and only if
|
||||
* finger moves between keys.
|
||||
*/
|
||||
LockScreenInputpad.prototype.onTouchMove = function(evt) {
|
||||
// State update is only required if the touch location moved
|
||||
// onto a different key than in the last round.
|
||||
// _visualizeKeypress keeps track of this.
|
||||
var target = this._touchTarget(evt);
|
||||
this._visualizeKeypress(target);
|
||||
};
|
||||
|
||||
/* On touchend, handle input from the key over which the
|
||||
* finger was released.
|
||||
*/
|
||||
LockScreenInputpad.prototype.onTouchEnd = function(evt) {
|
||||
var target = this._touchTarget(evt);
|
||||
this.handlePassCodeInput(target);
|
||||
this._visualizeKeypress(null); // deactivates all keys
|
||||
};
|
||||
|
||||
/* Handler for traditional click events. Only required on
|
||||
* platforms that don't support touch events.
|
||||
*/
|
||||
LockScreenInputpad.prototype.onClick = function(evt) {
|
||||
var target = evt.target;
|
||||
this._visualizeKeypress(target);
|
||||
this.handlePassCodeInput(target);
|
||||
setTimeout(() => {
|
||||
this._visualizeKeypress(null); // deactivates all keys
|
||||
}, 200); // after 200ms
|
||||
};
|
||||
|
||||
/* Visualize the current key as active, and the previous
|
||||
* key as inactive, and keep track of which key was active
|
||||
* for the next round, and only update if there was a key
|
||||
* change.
|
||||
*/
|
||||
LockScreenInputpad.prototype._visualizeKeypress = function(target) {
|
||||
// State update is only required if the touch location moved
|
||||
// onto a different key than in the last round or if target is
|
||||
// null, requesting internal reset.
|
||||
// Comparing DOM elements is tricky. .isEqualNode() and friends
|
||||
// do not always work as expected. Comparing textContent instead.
|
||||
if (!target || !this.lastTouchedKey ||
|
||||
target.textContent !== this.lastTouchedKey.textContent) {
|
||||
// Make old key inactive if there was one
|
||||
if (this.lastTouchedKey) {
|
||||
this._makeKeyInactive(this.lastTouchedKey);
|
||||
}
|
||||
// Make new key active if touch is actually on a key
|
||||
var key = this._keyForTarget(target);
|
||||
if (key) {
|
||||
this._makeKeyActive(target);
|
||||
// Remember new key for the next event
|
||||
this.lastTouchedKey = target;
|
||||
} else {
|
||||
// If there's no new key, touch moved beyond the keypad
|
||||
// or target was null
|
||||
this.lastTouchedKey = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._makeKeyActive = function(target) {
|
||||
var anchor = this._anchorForTarget(target);
|
||||
if (anchor) {
|
||||
anchor.classList.add('active-key');
|
||||
} else {
|
||||
throw Error('lsip_makeKeyActive called with non-key node');
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._makeKeyInactive = function(target) {
|
||||
var anchor = this._anchorForTarget(target);
|
||||
if (anchor) {
|
||||
anchor.classList.remove('active-key');
|
||||
} else {
|
||||
throw Error('lsip_makeKeyInactive called with non-key node');
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._keyForTarget = function(target) {
|
||||
var anchor = this._anchorForTarget(target);
|
||||
if (anchor) {
|
||||
return anchor.dataset.key;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._anchorForTarget = function(target) {
|
||||
// Find ancestorial anchor that has info on the key
|
||||
// Current structure: a > div > span
|
||||
try {
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (target.tagName === 'A') {
|
||||
return target;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._touchTarget = function(event) {
|
||||
// Determine from touch coordinates in the event
|
||||
// which key the finger is on.
|
||||
// Can't use evt.target, because it remains
|
||||
// on where the touch started.
|
||||
// CAVE: .elementFromPoint() is experimental and may change
|
||||
var touch = event.changedTouches[0];
|
||||
return document.elementFromPoint(touch.clientX, touch.clientY);
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.dispatchEvent = function(evt) {
|
||||
window.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.addEventListener = function(name, cb) {
|
||||
cb = cb || this;
|
||||
window.addEventListener(name, cb);
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.removeEventListener = function(name, cb) {
|
||||
cb = cb || this;
|
||||
window.removeEventListener(name, cb);
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.updatePassCodeUI =
|
||||
function() {
|
||||
if (this.states.passCodeEntered) {
|
||||
this.passcodePad.classList.add('passcode-entered');
|
||||
} else {
|
||||
this.passcodePad.classList.remove('passcode-entered');
|
||||
}
|
||||
if (this.states.passCodeEntered.length !== 4 &&
|
||||
this.passcodePad.classList.contains('passcode-fulfilled')) {
|
||||
this.passcodePad.classList.remove('passcode-fulfilled');
|
||||
}
|
||||
if (this.states.passCodeErrorTimeoutPending) {
|
||||
this.passcodeCode.classList.add('error');
|
||||
} else {
|
||||
this.passcodeCode.classList.remove('error');
|
||||
}
|
||||
var i = 4;
|
||||
while (i--) {
|
||||
var span = this.passcodeCode.childNodes[i];
|
||||
if (span) {
|
||||
if (this.states.passCodeEntered.length > i) {
|
||||
span.dataset.dot = true;
|
||||
} else {
|
||||
delete span.dataset.dot;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.handlePassCodeInput =
|
||||
function(target) {
|
||||
var key = this._keyForTarget(target);
|
||||
switch (key) {
|
||||
case 'e': // 'E'mergency Call
|
||||
// Cancel the notification clicked to activate.
|
||||
if (this.lockScreen._unlockingMessage.notificationId) {
|
||||
delete this.lockScreen._unlockingMessage.notificationId;
|
||||
}
|
||||
this.lockScreen.invokeSecureApp('emergency-call');
|
||||
break;
|
||||
|
||||
case 'c': // 'C'ancel
|
||||
// Cancel the notification clicked to activate.
|
||||
if (this.lockScreen._unlockingMessage.notificationId) {
|
||||
delete this.lockScreen._unlockingMessage.notificationId;
|
||||
}
|
||||
this.dispatchEvent(new window.CustomEvent(
|
||||
'lockscreen-keypad-input', { detail: {
|
||||
key: key
|
||||
}
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'b': // 'B'ackspace for correction
|
||||
if (this.states.passCodeErrorTimeoutPending) {
|
||||
break;
|
||||
}
|
||||
this.states.passCodeEntered =
|
||||
this.states.passCodeEntered.substr(0,
|
||||
this.states.passCodeEntered.length - 1);
|
||||
this.updatePassCodeUI();
|
||||
break;
|
||||
|
||||
case null: // Ignore non-keys
|
||||
break;
|
||||
|
||||
default:
|
||||
if (this.states.passCodeErrorTimeoutPending) {
|
||||
break;
|
||||
}
|
||||
// If it's already 4 digits and this is the > 5th one,
|
||||
// don't do anything.
|
||||
if (this.states.passCodeEntered.length === 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.states.passCodeEntered += key;
|
||||
this.updatePassCodeUI();
|
||||
|
||||
if (this.states.passCodeEntered.length === 4) {
|
||||
this.passcodePad.classList.add('passcode-fulfilled');
|
||||
this.lockScreen.checkPassCode(this.states.passCodeEntered);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
exports.LockScreenInputpad = LockScreenInputpad;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/* global LockScreenInputpad */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* XXX: Bootstrap LockScreen. Note this is NOT flexible, so we need to
|
||||
* rewrite it with some dependency managing bootstraping component.
|
||||
**/
|
||||
(function(exports) {
|
||||
window.addEventListener('lockscreen-inputpad-frame-bootstrap',
|
||||
function startup() {
|
||||
// XXX: We assume LockScreenWindow is before us and it registered
|
||||
// the 'lockscreen' in 'window' This should be eliminated once
|
||||
// we moved LockScreen as an iframe and killed LockScreen dependencies.
|
||||
window.lockScreenInputpad = (new LockScreenInputpad(window.lockScreen))
|
||||
.start();
|
||||
});
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
/* global Service */
|
||||
'use strict';
|
||||
|
||||
function LockScreenMediaPlaybackWidget(container, options) {
|
||||
this.container = container;
|
||||
this.nowPlaying = container.querySelector('.media-playback-nowplaying');
|
||||
this.controls = container.querySelector('.media-playback-controls');
|
||||
|
||||
this.track = container.querySelector('.track');
|
||||
|
||||
this.previousButton = container.querySelector('.previous');
|
||||
this.playPauseButton = container.querySelector('.play-pause');
|
||||
this.nextButton = container.querySelector('.next');
|
||||
|
||||
this.previousButton.addEventListener(
|
||||
'contextmenu', this.handleContextmenu.bind(this)
|
||||
);
|
||||
this.nextButton.addEventListener(
|
||||
'contextmenu', this.handleContextmenu.bind(this)
|
||||
);
|
||||
this.container.addEventListener('touchend', this.handleTouchend.bind(this));
|
||||
|
||||
// When prev/next button releases, we need to know user simply clicking on it
|
||||
// or holding on and perform fastseeking. Keeping this state to know which
|
||||
// command should issue.
|
||||
this.isFastSeeking = false;
|
||||
|
||||
window.addEventListener('iac-mediacomms', this.handleMessage.bind(this));
|
||||
// When SCO status changes, we need to adjust the ui of the playback controls
|
||||
window.addEventListener(
|
||||
'bluetoothprofileconnectionchange', this.handleSCOChange.bind(this)
|
||||
);
|
||||
|
||||
if (options && options.nowPlayingAction === 'openapp') {
|
||||
this.nowPlaying.addEventListener('click', this.openMediaApp.bind(this));
|
||||
}
|
||||
|
||||
// Listen for when the music app is terminated. We know which app to look
|
||||
// for because we got it from the "appinfo" message. Then we hide the Now
|
||||
// Playing container. XXX: This is a gigantic hack, stemming from
|
||||
// <https://bugzilla.mozilla.org/show_bug.cgi?id=915880>. If you're thinking
|
||||
// about doing something similar, step away from your keyboard immediately.
|
||||
window.addEventListener('appterminated', function(event) {
|
||||
if (event.detail.origin === this.origin) {
|
||||
this.hidden = true;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// Requesting the current data (if any)
|
||||
this.sendCommandEvent('updateplaystatus');
|
||||
this.sendCommandEvent('updatemetadata');
|
||||
}
|
||||
|
||||
LockScreenMediaPlaybackWidget.prototype = {
|
||||
get hidden() {
|
||||
return this.container.hidden;
|
||||
},
|
||||
|
||||
set hidden(value) {
|
||||
this.container.hidden = value;
|
||||
},
|
||||
|
||||
handleMessage: function mpw_handleMessage(event) {
|
||||
var message = event.detail;
|
||||
switch (message.type) {
|
||||
case 'appinfo':
|
||||
this.updateAppInfo(message.data);
|
||||
break;
|
||||
case 'nowplaying':
|
||||
this.updateNowPlaying(message.data);
|
||||
break;
|
||||
case 'status':
|
||||
this.updatePlaybackStatus(message.data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleSCOChange: function mpw_handleSCOChange(event) {
|
||||
var name = event.detail.name;
|
||||
var connected = event.detail.connected;
|
||||
|
||||
if (name === 'sco') {
|
||||
this.container.classList.toggle('disabled', connected);
|
||||
}
|
||||
},
|
||||
|
||||
updateAppInfo: function mpw_updateAppInfo(info) {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.origin = info.origin;
|
||||
},
|
||||
|
||||
updateNowPlaying: function mpw_updateNowPlaying(metadata) {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
var title = metadata.title.trim();
|
||||
var artist = metadata.artist.trim();
|
||||
|
||||
if (title || artist) {
|
||||
this.track.removeAttribute('data-l10n-id');
|
||||
this.track.textContent= '';
|
||||
|
||||
if (title) {
|
||||
var titleNode = document.createTextNode(title);
|
||||
this.track.appendChild(titleNode);
|
||||
}
|
||||
|
||||
if (title && artist) {
|
||||
var emDashNode = document.createTextNode(' — '); // Using a —
|
||||
var artistNode = document.createTextNode(artist);
|
||||
var artistBdiNode = document.createElement('bdi');
|
||||
this.track.appendChild(emDashNode);
|
||||
artistBdiNode.appendChild(artistNode);
|
||||
this.track.appendChild(artistBdiNode);
|
||||
}
|
||||
else if (artist) { // but no title
|
||||
this.track.appendChild(document.createTextNode(artist));
|
||||
}
|
||||
} else {
|
||||
this.track.setAttribute('data-l10n-id', 'UnknownTrack');
|
||||
}
|
||||
},
|
||||
|
||||
updatePlaybackStatus: function mp_updatePlaybackStatus(status) {
|
||||
switch (status.playStatus) {
|
||||
case 'PLAYING':
|
||||
// WORKAROUND: Sometimes we can still get the playing event after music
|
||||
// app terminated, so we check the music app existence when playback is
|
||||
// hidden initially before showing. Remove it once Bug 915880 fixed.
|
||||
var origin = this.origin;
|
||||
if (this.hidden && !Service.query('AppWindowManager.getApp', origin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hidden = false;
|
||||
this.playPauseButton.dataset.icon = 'pause';
|
||||
this.playPauseButton.setAttribute('data-l10n-id', 'mediaPlaybackPause');
|
||||
break;
|
||||
case 'PAUSED':
|
||||
this.hidden = false;
|
||||
this.playPauseButton.dataset.icon = 'play';
|
||||
this.playPauseButton.setAttribute('data-l10n-id', 'mediaPlaybackPlay');
|
||||
break;
|
||||
case 'STOPPED':
|
||||
this.hidden = true;
|
||||
break;
|
||||
case 'mozinterruptbegin':
|
||||
this.hidden = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
openMediaApp: function mp_openMediaApp(event) {
|
||||
if (this.origin) {
|
||||
var evt = new CustomEvent('displayapp', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: Service.query('AppWindowManager.getApp', this.origin)
|
||||
});
|
||||
window.dispatchEvent(evt);
|
||||
}
|
||||
},
|
||||
|
||||
sendCommandEvent: function mp_sendCommandEvent(command) {
|
||||
// XXX: Before we implement LockScreen as a standalone app,
|
||||
// we make it inside a iframe, which would block this function
|
||||
// to get the IAC port. So these commands need to be send outside
|
||||
// the frame to System app, and System app would forward them via IAC.
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-request-mediacommand', {
|
||||
detail: command
|
||||
}));
|
||||
},
|
||||
|
||||
handleContextmenu: function mp_handleContextmenu(event) {
|
||||
var command = null;
|
||||
|
||||
switch (event.target) {
|
||||
case this.previousButton:
|
||||
command = 'rewindstart';
|
||||
break;
|
||||
case this.nextButton:
|
||||
command = 'fastforwardstart';
|
||||
break;
|
||||
}
|
||||
|
||||
if (command) {
|
||||
this.isFastSeeking = true;
|
||||
this.sendCommandEvent(command);
|
||||
}
|
||||
},
|
||||
|
||||
handleTouchend: function mp_handleTouchend(event) {
|
||||
var command = null;
|
||||
|
||||
switch (event.target) {
|
||||
case this.previousButton:
|
||||
if (this.isFastSeeking) {
|
||||
this.isFastSeeking = false;
|
||||
command = 'rewindend';
|
||||
} else {
|
||||
command = 'prevtrack';
|
||||
}
|
||||
break;
|
||||
case this.nextButton:
|
||||
if (this.isFastSeeking) {
|
||||
this.isFastSeeking = false;
|
||||
command = 'fastforwardend';
|
||||
} else {
|
||||
command = 'nexttrack';
|
||||
}
|
||||
break;
|
||||
case this.playPauseButton:
|
||||
// The play/pause indicator will get set once the music app replies
|
||||
// with its "mode" message, but this will make us appear speedier.
|
||||
event.target.classList.toggle('is-paused');
|
||||
command = 'playpause';
|
||||
break;
|
||||
}
|
||||
|
||||
if (command) {
|
||||
this.sendCommandEvent(command);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* To build an LockScreen notification and prevent to add such building
|
||||
* code in the manager components.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenNotificationBuilder = function(container) {
|
||||
this.container = container;
|
||||
};
|
||||
|
||||
LockScreenNotificationBuilder.prototype.start =
|
||||
function() {
|
||||
this.selector = {
|
||||
highlighted: '.notification.actionable',
|
||||
notification: '.notification'
|
||||
};
|
||||
|
||||
// UX require to cancel the highlighted state after
|
||||
// idle 3 seconds.
|
||||
this.timeoutHighlighted = 3000;
|
||||
};
|
||||
|
||||
/**
|
||||
* To make new LockScreen notification from an existing one.
|
||||
* This is to keep the compatibility with the existing code.
|
||||
*
|
||||
* XXX: A better way should be create the notifiction of LockScreen
|
||||
* from this component only, but to refactor the old notifications
|
||||
* is the perquisite.
|
||||
*
|
||||
* @param node {DOMElement} - the notification node
|
||||
* @return {DOMElement}
|
||||
*/
|
||||
LockScreenNotificationBuilder.prototype.decorate =
|
||||
function lsnb_decorate(node) {
|
||||
// If this is the previous node before the activated one,
|
||||
// it should hide the bottom border.
|
||||
var next = node.nextElementSibling;
|
||||
if (null !== next && next.classList.contains('actionable')) {
|
||||
node.classList.add('previous-actionable');
|
||||
}
|
||||
|
||||
node.addEventListener('click',
|
||||
this.requestHighlight.bind(this, node));
|
||||
return node;
|
||||
};
|
||||
|
||||
LockScreenNotificationBuilder.prototype.requestHighlight =
|
||||
function lsnb_requestHighlight(node) {
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-notification-request-highlight', {
|
||||
detail: {
|
||||
node: node,
|
||||
highlighter: this.highlight.bind(this, node)
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* When it get clicked, do the UI change.
|
||||
*/
|
||||
LockScreenNotificationBuilder.prototype.highlight =
|
||||
function lsnb_highlight(node) {
|
||||
// Cancel other's highlight first.
|
||||
var highlighted = this.container.querySelector(this.selector.highlighted);
|
||||
if (highlighted) {
|
||||
this.removeHighlight(highlighted);
|
||||
}
|
||||
var button = this.createActionButton(node.dataset.notificationId);
|
||||
node.appendChild(button);
|
||||
node.classList.add('actionable');
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-notification-notify-highlighted', {
|
||||
detail: {
|
||||
id: node.dataset.notificationId,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}));
|
||||
setTimeout(() => {
|
||||
// UX require to clean idle and highlighted notification
|
||||
// after sevaral seconds.
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-notification-request-clean-highlighted', {
|
||||
detail: {
|
||||
id: node.dataset.notificationId,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}));
|
||||
}, this.timeoutHighlighted);
|
||||
|
||||
// Find previous one and cancel it's bottom border.
|
||||
var previous = node.previousElementSibling;
|
||||
var oldPrevious = document.querySelector(
|
||||
'#notifications-lockscreen-container '+
|
||||
'.notification.previous-actionable');
|
||||
if (null !== oldPrevious) {
|
||||
oldPrevious.classList.remove('previous-actionable');
|
||||
}
|
||||
if (null !== previous) {
|
||||
previous.classList.add('previous-actionable');
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotificationBuilder.prototype.removeHighlight =
|
||||
function lsnb_removeHighlight(node) {
|
||||
node.classList.remove('actionable');
|
||||
node.querySelector('.button-actionable').remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the button and make the notification actionalbe.
|
||||
*
|
||||
* @param notificationId {string} - the ID need to fire the event
|
||||
* @return {DOMElement}
|
||||
*/
|
||||
LockScreenNotificationBuilder.prototype.createActionButton =
|
||||
function lsnb_createActionButton(notificationId) {
|
||||
var button = document.createElement('span');
|
||||
button.classList.add('button-actionable');
|
||||
button.dataset.l10nId = 'open-notification';
|
||||
button.addEventListener('touchstart', (evt) => {
|
||||
button.parentElement.classList.add('activated');
|
||||
});
|
||||
|
||||
button.addEventListener('touchend', (evt) => {
|
||||
button.parentElement.classList.remove('activated');
|
||||
button.classList.add('activated');
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-notification-request-activate', {
|
||||
detail: { 'notificationId': notificationId }
|
||||
}));
|
||||
});
|
||||
return button;
|
||||
};
|
||||
|
||||
exports.LockScreenNotificationBuilder = LockScreenNotificationBuilder;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,593 @@
|
|||
'use strict';
|
||||
|
||||
/* global LockScreenNotificationBuilder, LazyLoader, mozIntl */
|
||||
|
||||
(function(exports) {
|
||||
/**
|
||||
* Manage Notifications in Lockscreen:
|
||||
* Handle notification changes from Notifications, and
|
||||
* update Lockscreen visuals accordingly.
|
||||
*
|
||||
* @constructor LockScreenNotifications
|
||||
*/
|
||||
var LockScreenNotifications = function() {};
|
||||
|
||||
LockScreenNotifications.prototype.start =
|
||||
function lsn_start(lockScreen, container){
|
||||
this._lockScreen = lockScreen;
|
||||
this.container = container;
|
||||
LazyLoader.load(['lockscreen/js/lockscreen_notification_builder.js'])
|
||||
.then(() => {
|
||||
this._lockScreenNotificationBuilder =
|
||||
new LockScreenNotificationBuilder(this.container);
|
||||
this._lockScreenNotificationBuilder.start();
|
||||
});
|
||||
this.arrow =
|
||||
document.getElementById('lockscreen-notification-arrow');
|
||||
// The 'scroll' event can't be forwarded via 'window'.
|
||||
this.container.addEventListener('scroll', this);
|
||||
this.configs = {
|
||||
listens: [
|
||||
'lockscreen-notification-request-highlight',
|
||||
'lockscreen-notification-notify-highlighted',
|
||||
'lockscreen-notification-request-clean-highlighted',
|
||||
'lockscreen-notification-request-activate',
|
||||
'lockscreen-notification-request-append',
|
||||
'lockscreen-notification-request-remove',
|
||||
'lockscreen-notification-request-clear',
|
||||
'lockscreen-appopened',
|
||||
'lockscreen-appclosed',
|
||||
'touchstart',
|
||||
'visibilitychange',
|
||||
'scroll'
|
||||
],
|
||||
// UX require to cancel the highlighted state after
|
||||
// idle 3 seconds.
|
||||
timeoutHighlighted: 3000
|
||||
};
|
||||
this.configs.listens.forEach((ename) => {
|
||||
window.addEventListener(ename, this);
|
||||
});
|
||||
|
||||
this.states = {
|
||||
// UX require to clean idle and highlighted notification
|
||||
// after sevaral seconds.
|
||||
highlightedNotifications: {},
|
||||
currentHighlighted: null,
|
||||
currentHighlightedId: null,
|
||||
arrowVisible: false // container is scrollable
|
||||
};
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.handleEvent =
|
||||
function lsn_handleEvent(evt) {
|
||||
var detail = evt.detail || {};
|
||||
var { id, timestamp, node } = detail;
|
||||
switch (evt.type) {
|
||||
case 'lockscreen-notification-request-append':
|
||||
this.onNotificationAppend(id, node);
|
||||
break;
|
||||
case 'lockscreen-notification-request-remove':
|
||||
var containerEmpty = false;
|
||||
if (evt.detail && evt.detail.containerEmpty) {
|
||||
containerEmpty = true;
|
||||
}
|
||||
this.onNotificationRemoved(containerEmpty);
|
||||
break;
|
||||
case 'lockscreen-notification-request-clear':
|
||||
this.onNotificationsClear();
|
||||
break;
|
||||
case 'lockscreen-notification-request-highlight':
|
||||
if (!this.notificationOutOfViewport(evt.detail.node)) {
|
||||
evt.detail.highlighter();
|
||||
}
|
||||
break;
|
||||
case 'lockscreen-notification-notify-highlighted':
|
||||
this.onNotificationHighlighted(id, timestamp);
|
||||
break;
|
||||
case 'lockscreen-notification-request-clean-highlighted':
|
||||
this.onCleanHighlighted(id, timestamp);
|
||||
break;
|
||||
case 'lockscreen-notification-request-activate':
|
||||
this.onNotificationActivate(evt.detail);
|
||||
break;
|
||||
case 'touchstart':
|
||||
if (!evt.target.classList.contains('notification') &&
|
||||
!evt.target.classList.contains('button-actionable')) {
|
||||
this.onNotificationsBlur();
|
||||
}
|
||||
break;
|
||||
case 'lockscreen-appopened':
|
||||
// When it's visible because of locking, bind the listener.
|
||||
window.addEventListener('touchstart', this);
|
||||
break;
|
||||
case 'lockscreen-appclosed':
|
||||
// When it's invisible because of unlocking, unbind the listener.
|
||||
window.removeEventListener('touchstart', this);
|
||||
break;
|
||||
case 'scroll':
|
||||
this.onContainerScrolling();
|
||||
break;
|
||||
case 'visibilitychange':
|
||||
if (!document.hidden) {
|
||||
// When it's visible, bind the listener.
|
||||
window.addEventListener('touchstart', this);
|
||||
this.updateTimestamps();
|
||||
} else {
|
||||
// When it's invisible, unbind the listener.
|
||||
window.removeEventListener('touchstart', this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.onContainerScrolling =
|
||||
function lsn_onContainerScrolling() {
|
||||
if (this.notificationOutOfViewport(this.states.currentHighlighted)) {
|
||||
this.onNotificationsBlur();
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.notificationOutOfViewport =
|
||||
function lsn_notificationOutOfViewport(node) {
|
||||
var top, bottom;
|
||||
var topBoundary, bottomBoundary;
|
||||
if (node) {
|
||||
// If we're in HVGA resolution, the too small notification container
|
||||
// would lead second and following up notifications overlap the boundary
|
||||
// by several pixels.
|
||||
var HVGATolerance = 0;
|
||||
if (this._getWindowInnerDimension().height <= 480) {
|
||||
HVGATolerance = 1;
|
||||
}
|
||||
topBoundary = this.container.getBoundingClientRect().top;
|
||||
bottomBoundary = this.container.getBoundingClientRect().bottom;
|
||||
top = node.getBoundingClientRect().top;
|
||||
bottom = node.getBoundingClientRect().bottom;
|
||||
if (top < topBoundary ||
|
||||
bottom > bottomBoundary + HVGATolerance) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the notification at the bottom boundary.
|
||||
*/
|
||||
LockScreenNotifications.prototype.notificationAtBottomBoundary =
|
||||
function lsn_notificationAtBottomBoundary() {
|
||||
var boundaryElement = this.arrow;
|
||||
var detectingPoint = {
|
||||
x: window.innerWidth >> 1,
|
||||
y: boundaryElement.getBoundingClientRect().top +
|
||||
this.configs.boundaryThresholdBottom
|
||||
};
|
||||
var element = document.elementFromPoint(detectingPoint.x, detectingPoint.y);
|
||||
if (element.classList.contains('notification')) {
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the notification at the top boundary.
|
||||
*/
|
||||
LockScreenNotifications.prototype.notificationAtTopBoundary =
|
||||
function lsn_notificationAtBottomBoundary() {
|
||||
var boundaryElement = this.container;
|
||||
var detectingPoint = {
|
||||
x: window.innerWidth >> 1,
|
||||
y: boundaryElement.getBoundingClientRect().top +
|
||||
this.configs.boundaryThresholdTop
|
||||
};
|
||||
var element = document.elementFromPoint(detectingPoint.x, detectingPoint.y);
|
||||
if (element.classList.contains('notification')) {
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.onNotificationsBlur =
|
||||
function lsn_onNotificationsBlur() {
|
||||
var id = this.states.currentHighlightedId;
|
||||
var notificationNode = this.states.currentHighlighted;
|
||||
if (notificationNode &&
|
||||
notificationNode.classList.contains('actionable')) {
|
||||
this.removeNotificationHighlight(notificationNode);
|
||||
delete this.states.highlightedNotifications[id];
|
||||
this.states.currentHighlighted = null;
|
||||
this.states.currentHighlightedId = null;
|
||||
|
||||
this._tryAddTopMaskByNotification(notificationNode);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the timestamp when a notification is highlighted.
|
||||
*/
|
||||
LockScreenNotifications.prototype.onNotificationHighlighted =
|
||||
function lsn_onNotificationHighlighted(id, timestamp) {
|
||||
// Overwrite existing because re-clicking on it is legal.
|
||||
this.states.highlightedNotifications[id] = timestamp;
|
||||
this.states.currentHighlightedId = id;
|
||||
this.states.currentHighlighted = this.container.querySelector(
|
||||
'[data-notification-id="' + id + '"]');
|
||||
|
||||
// because background of an active actionable notification can be clipped
|
||||
// by the top mask implemented in lockscreen visual refresh 2.0 (bug1023500)
|
||||
// we need to cancel the mask when the 'top' (in the visible viewport, not
|
||||
// the whole container) notification is active, by 'top-actionable' class.
|
||||
// (note: the 'top mask' displays only when the container scrolls to bottom)
|
||||
|
||||
// such 'top'-ness is decided by the viewport size of the container:
|
||||
// if the container can show N visible notifications,
|
||||
// then the last-Nth notification is that 'top' notification.
|
||||
|
||||
// Illustration:
|
||||
//
|
||||
// --top------------------ container -------------bottom--
|
||||
// ======= viewport ========
|
||||
// ------------------------------=========================
|
||||
// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
// | | | | | | 0 | 1 | 2 | 3 |
|
||||
// ------------------------------=========================
|
||||
// In this illustration, the ID of the visually top notification in the
|
||||
// viewport is #5; the number of notifications in container viewport is 4.
|
||||
// We can select the #5 notification by :nth-last-of-type(4).
|
||||
|
||||
var numNotificationInContainerViewport =
|
||||
this._getNumNotificationInContainerViewport();
|
||||
|
||||
if (this.container.querySelector('div.notification:nth-last-of-type(' +
|
||||
numNotificationInContainerViewport + ')') ===
|
||||
this.states.currentHighlighted){
|
||||
this.container.classList.add('top-actionable');
|
||||
}else{
|
||||
this.container.classList.remove('top-actionable');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean the highlighted notification.
|
||||
*/
|
||||
LockScreenNotifications.prototype.onCleanHighlighted =
|
||||
function lsn_onNotificationHighlighted(id, timestamp) {
|
||||
var prevTimestamp = this.states.highlightedNotifications[id];
|
||||
if (prevTimestamp &&
|
||||
timestamp - prevTimestamp > this.configs.timeoutHighlighted) {
|
||||
var notificationNode = this.container.querySelector(
|
||||
'[data-notification-id="' + id + '"]');
|
||||
if (notificationNode &&
|
||||
notificationNode.classList.contains('actionable')) {
|
||||
this.removeNotificationHighlight(notificationNode);
|
||||
delete this.states.highlightedNotifications[id];
|
||||
this.states.currentHighlighted = null;
|
||||
this.states.currentHighlightedId = null;
|
||||
|
||||
this._tryAddTopMaskByNotification(notificationNode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.removeNotificationHighlight =
|
||||
function lsn_removeNotificationHighlight(node) {
|
||||
node.classList.remove('actionable');
|
||||
node.querySelector('.button-actionable').remove();
|
||||
var oldPrevious = this.container.querySelector(
|
||||
'.notification.previous-actionable');
|
||||
if (null !== oldPrevious) {
|
||||
oldPrevious.classList.remove('previous-actionable');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When user clicked on the notification while it's locked.
|
||||
*/
|
||||
LockScreenNotifications.prototype.onNotificationActivate =
|
||||
function lsn_onNotificationActivate(info) {
|
||||
this._lockScreen._unlockingMessage = {
|
||||
notificationId: info.notificationId
|
||||
};
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('lockscreen-notification-request-activate-unlock'));
|
||||
};
|
||||
|
||||
/**
|
||||
* When new notification appended, do the visual change.
|
||||
* Would receive a ordinary Notificaition node.
|
||||
*/
|
||||
LockScreenNotifications.prototype.onNotificationAppend =
|
||||
function lsn_onNotificationsAppend(id, node) {
|
||||
var notifSelector = '[data-notification-id="' + id + '"]';
|
||||
// First we try and find an existing notification with the same id.
|
||||
// If we have one, we'll replace it. If not, we'll create a new node.
|
||||
var oldLockScreenNode =
|
||||
this.container.querySelector(notifSelector);
|
||||
if (oldLockScreenNode) {
|
||||
this.container.replaceChild(
|
||||
node,
|
||||
oldLockScreenNode
|
||||
);
|
||||
}
|
||||
else {
|
||||
this.container.insertBefore(
|
||||
node,
|
||||
this.container.firstElementChild
|
||||
);
|
||||
}
|
||||
|
||||
this._lockScreenNotificationBuilder.decorate(node);
|
||||
this.showColoredMaskBG();
|
||||
|
||||
// UX specifies that the container should scroll to top
|
||||
/* note two things:
|
||||
* 1. we need to call adjustContainerVisualHints even
|
||||
* though we're setting scrollTop, since setting sT doesn't
|
||||
* necessarily invoke onscroll (if the old container is already
|
||||
* scrolled to top, we might still need to decide to show
|
||||
* the arrow)
|
||||
* 2. set scrollTop before calling adjustContainerVisualHints
|
||||
* since sT = 0 will hide the mask if it's showing,
|
||||
* and if we call aCVH before setting sT,
|
||||
* under some circumstances aCVH would decide to show mask,
|
||||
* only to be negated by st = 0 (waste of energy!).
|
||||
*/
|
||||
this.scrollToTop();
|
||||
|
||||
// check if lockscreen notifications visual
|
||||
// hints (masks & arrow) need to show
|
||||
this.adjustContainerVisualHints();
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.onNotificationRemoved =
|
||||
function lsn_onNotificationRemoved(containerEmpty) {
|
||||
// if we don't have any notifications,
|
||||
// use the no-notifications masked background for lockscreen
|
||||
if (containerEmpty) {
|
||||
this.hideColoredMaskBG();
|
||||
}
|
||||
// check if lockscreen notifications visual
|
||||
// hints (masks & arrow) need to show
|
||||
this.adjustContainerVisualHints();
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.onNotificationsClear =
|
||||
function lsn_onNotificationsClear() {
|
||||
// remove the "have notifications" masked background from lockscreen
|
||||
this.hideColoredMaskBG();
|
||||
// check if lockscreen notifications visual
|
||||
// hints (masks & arrow) need to show
|
||||
this.adjustContainerVisualHints();
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind lockscreen onto this module
|
||||
*
|
||||
* @this {LockScreenNotifications}
|
||||
* @memberof LockScreenNotifications
|
||||
* @param bindLockScreen the lockScreen instance
|
||||
*/
|
||||
LockScreenNotifications.prototype.bindLockScreen =
|
||||
function lsn_bindLockScreen(lockScreen) {
|
||||
this._lockScreen = lockScreen;
|
||||
};
|
||||
|
||||
/**
|
||||
* When we have notifications, show bgcolor from wallpaper
|
||||
* Remove the simple gradient at the same time
|
||||
*
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.showColoredMaskBG =
|
||||
function lsn_showColoredMaskBG() {
|
||||
this._lockScreen.maskedBackground.style.backgroundColor =
|
||||
this._lockScreen.maskedBackground.dataset.wallpaperColor;
|
||||
|
||||
this._lockScreen.maskedBackground.classList.remove('blank');
|
||||
};
|
||||
|
||||
/**
|
||||
* When we don't have notifications, use the
|
||||
* simple gradient as lockscreen's masked background
|
||||
*
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.hideColoredMaskBG =
|
||||
function lsn_hideColoredMaskBG() {
|
||||
this._lockScreen.maskedBackground.classList.add('blank');
|
||||
};
|
||||
|
||||
/**
|
||||
* We use a smaller notifications container when we have a media player
|
||||
* widget on the lockscreen. This function collapses the container.
|
||||
*
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.collapseNotifications =
|
||||
function lsn_collapseNotifications() {
|
||||
this._lockScreen.notificationsContainer.classList.add('collapsed');
|
||||
this._lockScreen.notificationArrow.classList.add('collapsed');
|
||||
};
|
||||
|
||||
/**
|
||||
* ...and this function expands the container.
|
||||
*
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.expandNotifications =
|
||||
function lsn_expandNotifications() {
|
||||
this._lockScreen.notificationsContainer.classList.remove('collapsed');
|
||||
this._lockScreen.notificationArrow.classList.remove('collapsed');
|
||||
};
|
||||
|
||||
/**
|
||||
* adjust the container's visual hints: masks and "more notification" arrow
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.adjustContainerVisualHints =
|
||||
function lsn_adjustContainerVisualHints() {
|
||||
var notificationsContainer = this._lockScreen.notificationsContainer;
|
||||
|
||||
/* mask:
|
||||
* the gradient masks only show when:
|
||||
* - "top" mask: when the user scrolls to bottom (this is a solid mask)
|
||||
* - "both" mask: when the user scrolls in between (this is a gradient mask)
|
||||
* (but we need to rule out the situation that
|
||||
* the container isn't actually scrollable)
|
||||
*/
|
||||
if(notificationsContainer.clientHeight ===
|
||||
notificationsContainer.scrollHeight){
|
||||
// no mask if the container can't be scrolled
|
||||
this._setMaskVisibility(false, false);
|
||||
}else{
|
||||
if(notificationsContainer.scrollTop +
|
||||
notificationsContainer.clientHeight ===
|
||||
notificationsContainer.scrollHeight){
|
||||
// user scrolls to bottom -> top mask
|
||||
this._setMaskVisibility(true, false);
|
||||
}else if(0 === notificationsContainer.scrollTop){
|
||||
// user scrolls to top -> no mask
|
||||
this._setMaskVisibility(false, false);
|
||||
}else{
|
||||
// anything in between -> "both" mask
|
||||
this._setMaskVisibility(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* arrow:
|
||||
* The "more notifications" arrow only shows
|
||||
* when the user is on the top of the container
|
||||
* and the container is scrollable
|
||||
*/
|
||||
this._setArrowVisibility(
|
||||
0 === notificationsContainer.scrollTop &&
|
||||
notificationsContainer.clientHeight <
|
||||
notificationsContainer.scrollHeight
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the masks' visibility.
|
||||
*
|
||||
* @private
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
* @param top Boolen shows top solid mask or not
|
||||
* @param both Boolen shows top-and-bottom gradient masks or not
|
||||
*/
|
||||
LockScreenNotifications.prototype._setMaskVisibility =
|
||||
function lsn__setMaskVisibility(top, both) {
|
||||
var notificationsContainer = this._lockScreen.notificationsContainer;
|
||||
|
||||
if(top){
|
||||
notificationsContainer.classList.add('masked-top');
|
||||
}else{
|
||||
notificationsContainer.classList.remove('masked-top');
|
||||
}
|
||||
|
||||
if(both){
|
||||
notificationsContainer.classList.add('masked-both');
|
||||
}else{
|
||||
notificationsContainer.classList.remove('masked-both');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the arrow's visibility.
|
||||
*
|
||||
* @private
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
* @param top Boolen shows top solid mask or not
|
||||
* @param both Boolen shows top-and-bottom gradient masks or not
|
||||
*/
|
||||
LockScreenNotifications.prototype._setArrowVisibility =
|
||||
function lsn__setArrowVisibility(visible) {
|
||||
var notificationArrow = this._lockScreen.notificationArrow;
|
||||
|
||||
if(visible){
|
||||
notificationArrow.classList.add('visible');
|
||||
this.states.arrowVisible = true;
|
||||
}else{
|
||||
notificationArrow.classList.remove('visible');
|
||||
this.states.arrowVisible = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls the notifications to the top
|
||||
*/
|
||||
LockScreenNotifications.prototype.scrollToTop =
|
||||
function lsn_scrollToTop() {
|
||||
this._lockScreen.notificationsContainer.scrollTop = 0;
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype._getWindowInnerDimension =
|
||||
function lsn_getWindowInnerDimension() {
|
||||
return {
|
||||
height: window.innerHeight,
|
||||
width: window.innerWidth
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of notifications visible in the container.
|
||||
* we have 2 viewable notifications for HVGA lockscreen with music player
|
||||
* widget, and for larger screen, and the absence of the widget,
|
||||
* we can allow one, or two, more viewable notifications.
|
||||
*/
|
||||
LockScreenNotifications.prototype._getNumNotificationInContainerViewport =
|
||||
function lsn_getNumNotificationInContainerViewport() {
|
||||
var numNotificationInContainerViewport = 2;
|
||||
|
||||
// monitor > HVGA => allow one more notification
|
||||
if (this._getWindowInnerDimension().height > 480) {
|
||||
numNotificationInContainerViewport++;
|
||||
}
|
||||
|
||||
// no music player widget => also allow one more notification
|
||||
if (!this.container.classList.contains('collapsed')) {
|
||||
numNotificationInContainerViewport++;
|
||||
}
|
||||
|
||||
return numNotificationInContainerViewport;
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype._tryAddTopMaskByNotification =
|
||||
function lsn_tryAddTopMaskByNotification(notificationNode) {
|
||||
// if the unhighlighted node was the visually top notification of the
|
||||
// viewport, add the top mask back by removing the top-actionable class.
|
||||
// (see also: onNotificationHighlighted)
|
||||
var numNotificationInContainerViewport =
|
||||
this._getNumNotificationInContainerViewport();
|
||||
if (this.container.querySelector('div.notification:nth-last-of-type(' +
|
||||
numNotificationInContainerViewport + ')') === notificationNode){
|
||||
this.container.classList.remove('top-actionable');
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.updateTimestamps =
|
||||
function lsn_updateTimestamps() {
|
||||
var timestamps = [...document.querySelectorAll('.notification .timestamp')];
|
||||
var formatter = mozIntl._gaia.RelativeDate(navigator.languages, {
|
||||
style: 'short'
|
||||
});
|
||||
|
||||
timestamps.forEach((element) => {
|
||||
var date = new Date(element.dataset.timestamp);
|
||||
formatter.formatElement(element, date);
|
||||
});
|
||||
};
|
||||
|
||||
/** @exports LockScreenWindowManager */
|
||||
exports.LockScreenNotifications = LockScreenNotifications;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen plays the animation.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateKeypadHiding = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateKeypadHiding.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateKeypadHiding.prototype.start = function(lockScreen) {
|
||||
this.type = 'keypadHiding';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateKeypadHiding.prototype.transferTo =
|
||||
function lsskh_transferTo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Copy from the original switching method.
|
||||
this.lockScreen.overlay.classList.remove('no-transition');
|
||||
// Trigger the transition.
|
||||
this.lockScreen.overlay.dataset.panel = 'main';
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('lockscreen-request-inputpad-close'));
|
||||
// XXX: We need a overall refactoring about panel and
|
||||
// panel styling in the future.
|
||||
this.lockScreen.overlay.classList.remove('passcode-unlocking');
|
||||
// XXX: We assume this is sync in order. But if we use real
|
||||
// input window this would be broken.
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateKeypadHiding = LockScreenStateKeypadHiding;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen plays the animation.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateKeypadRising = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateKeypadRising.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateKeypadRising.prototype.start = function(lockScreen) {
|
||||
this.type = 'keypadRising';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateKeypadRising.prototype.transferTo =
|
||||
function lsskr_transferTo(inputs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// XXX: Because when it's 'success', the keyboard would be hidden.
|
||||
// This should be fixed: we should manage the show/hide with other
|
||||
// CSS flags.
|
||||
this.lockScreen.overlay.dataset.passcodeStatus = '';
|
||||
this.lockScreen.overlay.dataset.panel = 'passcode';
|
||||
// XXX: Before it become a real input window.
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('lockscreen-request-inputpad-open'));
|
||||
// XXX: We need a overall refactoring about panel and
|
||||
// panel styling in the future.
|
||||
this.lockScreen.overlay.classList.add('passcode-unlocking');
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateKeypadRising = LockScreenStateKeypadRising;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen is showing with the keypad.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateKeypadShow = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateKeypadShow.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateKeypadShow.prototype.start = function(lockScreen) {
|
||||
this.type = 'keypadShow';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateKeypadShow.prototype.transferTo =
|
||||
function lssks_transferTo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Copy from the original switching method.
|
||||
this.lockScreen.overlay.classList.add('no-transition');
|
||||
this.lockScreen.overlay.dataset.panel = 'passcode';
|
||||
// Resetting slider when it get hidden by the passcode keypad
|
||||
this.lockScreen._unlocker.reset();
|
||||
// XXX: Because when it's 'success', the keyboard would be hidden.
|
||||
// This should be fixed: we should manage the show/hide with other
|
||||
// CSS flags.
|
||||
this.lockScreen.overlay.dataset.passcodeStatus = '';
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateKeypadShow = LockScreenStateKeypadShow;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* To log state transferring in a proper way, rather dump raw console
|
||||
* messages and then overwhelm it.
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
var LockScreenStateLogger = function() {};
|
||||
|
||||
LockScreenStateLogger.prototype.start =
|
||||
function lss_start(configs) {
|
||||
this.stateStack = [];
|
||||
this.configs = {
|
||||
verbose: false || configs.verbose,
|
||||
warning: false || configs.warning,
|
||||
error: true && configs.error,
|
||||
graph: false || configs.graph,
|
||||
debug: true && configs.debug,
|
||||
reporter: configs.reporter || this.consoleReporter.bind(this)
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.debug =
|
||||
function lss_debug() {
|
||||
if (this.configs.debug) {
|
||||
this.log.apply(this, ['[I] '].concat(Array.from(arguments)));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.verbose =
|
||||
function lss_verbose() {
|
||||
if (this.configs.verbose) {
|
||||
this.log.apply(this, ['[V] '].concat(Array.from(arguments)));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.warning = function lss_warning() {
|
||||
if (this.configs.warning || this.configs.verbose) {
|
||||
this.log.apply(this, ['[!] '].concat(Array.from(arguments)));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.error =
|
||||
function lss_error() {
|
||||
if (this.configs.error || this.configs.warning ||
|
||||
this.configs.verbose) {
|
||||
this.log.apply(this, ['[E] '].concat(Array.from(arguments)));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Print the stack. For example:
|
||||
*
|
||||
* logger.debug('the thing at stack: ').stack()
|
||||
*
|
||||
* would print out the message and the stack.
|
||||
*/
|
||||
LockScreenStateLogger.prototype.stack = function lss_stack() {
|
||||
this.log((new Error()).stack);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Log the transferring manipulation.
|
||||
*
|
||||
* To log the conditions, this function would stringify the conditions
|
||||
* and then parse it to do the deep copy. So please turn off the
|
||||
* `config.debug` in production mode.
|
||||
*
|
||||
* @param from {string} - from state type
|
||||
* @param to {string} - to state type
|
||||
* @param conditions {object} - under what conditions we do the transferring
|
||||
*/
|
||||
LockScreenStateLogger.prototype.transfer =
|
||||
function lss_transfer(from, to, conditions = {}) {
|
||||
if (!this.configs.debug) {
|
||||
return;
|
||||
}
|
||||
var transferDetails = {
|
||||
from: from,
|
||||
to: to,
|
||||
conditions: JSON.parse(JSON.stringify(conditions))
|
||||
};
|
||||
if (this.configs.graph) {
|
||||
this.stateStack.push(transferDetails);
|
||||
}
|
||||
this.debug(`State transfer: from ${from} to ${to} because of:`,
|
||||
transferDetails.conditions);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* To get the graph. The array it return would be:
|
||||
*
|
||||
* [ 'foo', {conditions}, 'bar', {conditions}, 'gamma'...]
|
||||
*
|
||||
* which can be rendered as a real graph.
|
||||
*/
|
||||
LockScreenStateLogger.prototype.graph =
|
||||
function lss_graph() {
|
||||
return this.stateStack.reduce((prev, info) => {
|
||||
return prev.concat([info.from, info.conditions, info.to]);
|
||||
}, []);
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.log =
|
||||
function lss_log() {
|
||||
var reporter = this.configs.reporter;
|
||||
reporter.apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.stop =
|
||||
function lss_stop() {
|
||||
this.stateStack.length = 0;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.consoleReporter =
|
||||
function lss_consoleReporter() {
|
||||
console.log.apply(console, arguments);
|
||||
return this;
|
||||
};
|
||||
exports.LockScreenStateLogger = LockScreenStateLogger;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen plays the animation.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStatePanelHide = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStatePanelHide.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStatePanelHide.prototype.start = function(lockScreen) {
|
||||
this.type = 'panelHide';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStatePanelHide.prototype.transferTo =
|
||||
function lssph_transferTo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Copy from the original switching method.
|
||||
this.lockScreen.overlay.classList.add('no-transition');
|
||||
// XXX: clear the passcode we entered.
|
||||
// To have 'passcodeStatus' would make UI don't update.
|
||||
delete this.lockScreen.overlay.dataset.passcodeStatus;
|
||||
this.lockScreen.passCodeEntered = '';
|
||||
// XXX: even though we set the no-transition, it still do transition.
|
||||
this.lockScreen.overlay.dataset.panel = 'passcode';
|
||||
this.lockScreen.overlay.dataset.passcodeStatus = 'success';
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStatePanelHide = LockScreenStatePanelHide;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A state represents the mode Secure app launching and launched.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateSecureAppLaunching = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateSecureAppLaunching.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateSecureAppLaunching.prototype.start = function(lockScreen) {
|
||||
this.type = 'secureAppLaunching';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateSecureAppLaunching.prototype.transferTo =
|
||||
function lsskh_transferTo(inputs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.lockScreen.invokeSecureApp(inputs.appName);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateSecureAppLaunching = LockScreenStateSecureAppLaunching;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen shows the slide.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateSlideRestore = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateSlideRestore.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateSlideRestore.prototype.start = function(lockScreen) {
|
||||
this.type = 'slideRestore';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateSlideRestore.prototype.transferTo =
|
||||
function lssss_transferTo(inputs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.lockScreen._unlocker.reset();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateSlideRestore = LockScreenStateSlideRestore;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen shows the slide.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateSlideShow = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateSlideShow.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateSlideShow.prototype.start = function(lockScreen) {
|
||||
this.type = 'slideShow';
|
||||
this.lockScreen = lockScreen;
|
||||
this.lockScreen.nextStep(() => {
|
||||
this.stopWhenActivated(this.lockScreen._unlocker);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Slider should be stopped until the manager do
|
||||
* the decision to transfer to which state. And we
|
||||
* stop it because the slideshow is the only state
|
||||
* that shows the slider, so to stop it before doing
|
||||
* any decision.
|
||||
**/
|
||||
LockScreenStateSlideShow.prototype.stopWhenActivated =
|
||||
function lssss_stopWhenActivated(unlocker) {
|
||||
var stopIt = function stopIt() {
|
||||
unlocker._stop();
|
||||
window.removeEventListener('lockscreenslide-activate-left', stopIt);
|
||||
window.removeEventListener('lockscreenslide-activate-right', stopIt);
|
||||
};
|
||||
window.addEventListener('lockscreenslide-activate-left', stopIt);
|
||||
window.addEventListener('lockscreenslide-activate-right', stopIt);
|
||||
};
|
||||
|
||||
LockScreenStateSlideShow.prototype.transferTo =
|
||||
function lssss_transferTo(inputs) {
|
||||
// Only when the lockscreen is ready, we do the things.
|
||||
return this.lockScreen.nextStep(() => {
|
||||
var unlocker = this.lockScreen._unlocker;
|
||||
// Clear passcode while the keypad is hiding.
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('lockscreen-request-inputpad-close'));
|
||||
// Resetting slider before we want to show it again
|
||||
unlocker.reset();
|
||||
// Start it because the slideshow is the only state
|
||||
// that shows the slider, so start it here.
|
||||
unlocker._start();
|
||||
|
||||
// Copy from the original switching method.
|
||||
this.lockScreen.overlay.classList.add('no-transition');
|
||||
this.lockScreen.overlay.dataset.panel = 'main';
|
||||
});
|
||||
};
|
||||
|
||||
exports.LockScreenStateSlideShow = LockScreenStateSlideShow;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* If we follow the ordinary unlocking logic, that is, no need to care about
|
||||
* instantly unlock and activity, we need to integrate the unlocking function
|
||||
* call into state to coordinate the relationships between panel switching and
|
||||
* unlocking animation.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateUnlock = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateUnlock.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateUnlock.prototype.start = function(lockScreen, innerStates) {
|
||||
this.type = 'unlock';
|
||||
this.lockScreen = lockScreen;
|
||||
this.innerStates = innerStates;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateUnlock.prototype.transferTo =
|
||||
function lsskh_transferTo(inputs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var unlockingDetails = {
|
||||
'unlockSoundEnabled': this.innerStates.unlockSoundEnabled
|
||||
};
|
||||
if (inputs.unlockingAppActivated) {
|
||||
this.lockScreen._activateUnlockingCamera(unlockingDetails);
|
||||
} else {
|
||||
this.lockScreen.unlock(false, unlockingDetails);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateUnlock = LockScreenStateUnlock;
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* A settings getter/setter cache.
|
||||
* Provide as few as possible APIs like the native APIs do.
|
||||
**/
|
||||
(function(exports) {
|
||||
var LockScreenSettingsCache = function() {
|
||||
this.cache = {};
|
||||
this.handleSettings = this.handleSettings.bind(this);
|
||||
};
|
||||
LockScreenSettingsCache.prototype.get = function(entry) {
|
||||
if (this.cache[entry]) {
|
||||
return Promise.resolve(this.cache[entry]);
|
||||
}
|
||||
|
||||
var resolve, reject;
|
||||
var promise = new Promise((rev, rej) => {
|
||||
resolve = rev;
|
||||
reject = rej;
|
||||
});
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var req = lock.get(entry);
|
||||
req.then(() => {
|
||||
this.cache[entry] = req.result;
|
||||
// Once it getted, monitor it to update cache.
|
||||
navigator.mozSettings
|
||||
.addObserver(entry, this.handleSettings);
|
||||
resolve(req.result);
|
||||
}).catch(() => {
|
||||
reject(req.error);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
LockScreenSettingsCache.prototype.set = function(entry, value) {
|
||||
var resolve, reject;
|
||||
var promise = new Promise((rev, rej) => {
|
||||
resolve = rev;
|
||||
reject = rej;
|
||||
});
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var reqcontent = {};
|
||||
reqcontent[entry] = value;
|
||||
var req = lock.set(reqcontent);
|
||||
req.then(() => {
|
||||
this.cache[entry] = value;
|
||||
resolve();
|
||||
}).catch(() => {
|
||||
reject();
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
LockScreenSettingsCache.prototype.handleSettings = function(evt) {
|
||||
var { settingsName, settingsValue } = evt;
|
||||
this.cache[settingsName] = settingsValue;
|
||||
};
|
||||
LockScreenSettingsCache.prototype.stop = function() {
|
||||
Object.keys(this.cache).forEach((entry) => {
|
||||
navigator.mozSettings.removeObserver(entry, this.handleSettings);
|
||||
});
|
||||
};
|
||||
exports.LockScreenSettingsCache = LockScreenSettingsCache;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/* global SourceEvent */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* DOM event source for Stream. One Stream can collect events from multiple
|
||||
* sources, which pass different native events (not only DOM events)
|
||||
* to Stream.
|
||||
**/
|
||||
(function(exports) {
|
||||
var DOMEventSource = function(configs) {
|
||||
this.configs = {
|
||||
events: configs.events || [],
|
||||
};
|
||||
this._collector = window.addEventListener.bind(window);
|
||||
this._decollector = window.removeEventListener.bind(window);
|
||||
this._forwardTo; // The forwarding target.
|
||||
|
||||
// Some API you just can't bind it with the object,
|
||||
// but a function.
|
||||
this.onchange = this.onchange.bind(this);
|
||||
};
|
||||
|
||||
DOMEventSource.prototype.start = function(forwardTo) {
|
||||
this.configs.events.forEach((ename) => {
|
||||
this._collector(ename, this.onchange);
|
||||
});
|
||||
this._forwardTo = forwardTo;
|
||||
return this;
|
||||
};
|
||||
|
||||
DOMEventSource.prototype.stop = function() {
|
||||
this._forwardTo = null;
|
||||
this.configs.events.forEach((ename) => {
|
||||
this._decollector(ename, this.onchange);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* For forwarding to the target.
|
||||
*/
|
||||
DOMEventSource.prototype.onchange = function(domevt) {
|
||||
if (this._forwardTo) {
|
||||
var sourceEvent = new SourceEvent(
|
||||
domevt.type, domevt.detail, domevt);
|
||||
this._forwardTo(sourceEvent);
|
||||
}
|
||||
};
|
||||
|
||||
exports.DOMEventSource = DOMEventSource;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/* global SourceEvent */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A source fire events every clock minutes.
|
||||
**/
|
||||
(function(exports) {
|
||||
var MinuteClockSource = function(configs) {
|
||||
this.configs = {
|
||||
type: configs.type,
|
||||
interval: 60000 // one minute.
|
||||
};
|
||||
this._tickId = null;
|
||||
this._forwardTo = null;
|
||||
// Some API you just can't bind it with the object,
|
||||
// but a function.
|
||||
this.onchange = this.onchange.bind(this);
|
||||
};
|
||||
|
||||
MinuteClockSource.prototype.start = function(forwardTo) {
|
||||
this._forwardTo = forwardTo;
|
||||
var seconds = (new Date()).getSeconds();
|
||||
// If it's the #0 second of that minute,
|
||||
// immediately tick or we would lost this minute.
|
||||
if (0 === seconds) {
|
||||
this.onchange();
|
||||
}
|
||||
// For the first tick we must set timeout for it.
|
||||
this._tickId = window.setTimeout(() => {
|
||||
this.tick();
|
||||
}, this.calcLeftMilliseconds());
|
||||
return this;
|
||||
};
|
||||
|
||||
MinuteClockSource.prototype.tick = function() {
|
||||
this.onchange();
|
||||
// For the first tick we must set timeout for it.
|
||||
this._tickId = window.setTimeout(() => {
|
||||
this.tick();
|
||||
}, this.calcLeftMilliseconds());
|
||||
};
|
||||
|
||||
MinuteClockSource.prototype.stop = function() {
|
||||
this._forwardTo = null;
|
||||
if (this._tickId) {
|
||||
window.clearTimeout(this._tickId);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* For forwarding to the target.
|
||||
* When the time is up, fire an event by generator.
|
||||
* So that the onchange method would forward it to the target.
|
||||
*/
|
||||
MinuteClockSource.prototype.onchange = function() {
|
||||
if (this._forwardTo) {
|
||||
this._forwardTo(new SourceEvent(this.configs.type));
|
||||
}
|
||||
};
|
||||
|
||||
MinuteClockSource.prototype.calcLeftMilliseconds = function() {
|
||||
var seconds = (new Date()).getSeconds();
|
||||
// If it's at the second 0th of the minute, immediate start to tick.
|
||||
var leftMilliseconds = (60 - seconds) * 1000;
|
||||
return leftMilliseconds;
|
||||
};
|
||||
|
||||
exports.MinuteClockSource = MinuteClockSource;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/* global SourceEvent */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Event source for Stream. One Stream can collect events from multiple
|
||||
* sources, which pass different native events (not only DOM events)
|
||||
* to Stream.
|
||||
**/
|
||||
(function(exports) {
|
||||
var SettingSource = function(configs) {
|
||||
this.configs = {
|
||||
settings: configs.settings || []
|
||||
};
|
||||
this._collector = navigator.mozSettings.addObserver;
|
||||
this._decollector = navigator.mozSettings.removeObserver;
|
||||
this._forwardTo = null;
|
||||
// Some API you just can't bind it with the object,
|
||||
// but a function.
|
||||
this.onchange = this.onchange.bind(this);
|
||||
};
|
||||
|
||||
SettingSource.prototype.start = function(forwardTo) {
|
||||
this.configs.settings.forEach((key) => {
|
||||
this._collector(key, this.onchange);
|
||||
});
|
||||
this._forwardTo = forwardTo;
|
||||
return this;
|
||||
};
|
||||
|
||||
SettingSource.prototype.stop = function() {
|
||||
this._forwardTo = null;
|
||||
this.configs.settings.forEach((key) => {
|
||||
this._decollector(key, this.onchange);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* For forwarding to the target.
|
||||
* Would transform the original 'settingName' and 'settingValue' pair as
|
||||
* 'type' and 'detail', as the event formant.
|
||||
*/
|
||||
SettingSource.prototype.onchange = function(change) {
|
||||
if (this._forwardTo) {
|
||||
this._forwardTo(
|
||||
new SourceEvent(change.settingName, change.settingValue));
|
||||
}
|
||||
};
|
||||
|
||||
exports.SettingSource = SettingSource;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* A datum that every source would fire.
|
||||
**/
|
||||
(function(exports) {
|
||||
var SourceEvent = function(type, detail, original) {
|
||||
this.type = type;
|
||||
this.detail = detail;
|
||||
this.original = original; // original event, if any.
|
||||
};
|
||||
exports.SourceEvent = SourceEvent;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/* global Stream */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
var LockScreenBasicState = function(component) {
|
||||
// Replace with the name of concrete state.
|
||||
this.configs = {
|
||||
name: 'LockScreenBasicState',
|
||||
// Note the event means events forwarded from sources, not DOM events.
|
||||
stream: {
|
||||
events: [],
|
||||
interrupts: [],
|
||||
sources: []
|
||||
}
|
||||
};
|
||||
// Component reference proivdes every resource & property
|
||||
// need by all states.
|
||||
this.component = component;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stream' phase is the state's phase.
|
||||
*/
|
||||
LockScreenBasicState.prototype.phase =
|
||||
function() {
|
||||
return this.stream.phase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Derived states should extend these basic methods.
|
||||
*/
|
||||
LockScreenBasicState.prototype.start =
|
||||
function() {
|
||||
this.stream = new Stream(this.configs.stream);
|
||||
return this.stream.start(this.handleSourceEvent.bind(this))
|
||||
.next(this.stream.ready.bind(this.stream));
|
||||
};
|
||||
|
||||
LockScreenBasicState.prototype.stop = function() {
|
||||
return this.stream.stop();
|
||||
};
|
||||
|
||||
LockScreenBasicState.prototype.destroy = function() {
|
||||
return this.stream.destroy();
|
||||
};
|
||||
|
||||
LockScreenBasicState.prototype.live = function() {
|
||||
return this.stream.until('stop');
|
||||
};
|
||||
|
||||
LockScreenBasicState.prototype.exist = function() {
|
||||
return this.stream.until('destroy');
|
||||
};
|
||||
|
||||
/**
|
||||
* Must transfer to next state via component's method.
|
||||
* Or the component cannot track the last active state.
|
||||
*/
|
||||
LockScreenBasicState.prototype.transferTo = function() {
|
||||
return this.component.transferTo.apply(this.component, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* If this handler return a Promise, or Process, the underlying Stream
|
||||
* can make sure the steps are queued even with asynchronous steps.
|
||||
*/
|
||||
LockScreenBasicState.prototype.handleSourceEvent = function() {};
|
||||
|
||||
exports.LockScreenBasicState = LockScreenBasicState;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,386 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* The core component to sequentialize asynchronous steps.
|
||||
* Basically it's an 'interruptable promise', but more than be interrupted,
|
||||
* it could 'shift' from one to another phase, with the non-preemptive
|
||||
* interrupting model.
|
||||
*
|
||||
* Example:
|
||||
* var process = new Process();
|
||||
* process.start() // the default phase is 'start'
|
||||
* .next(stepA)
|
||||
* .next(stepB)
|
||||
* ...
|
||||
* // later, some urgent events come
|
||||
* process.stop() // one of the default three phases
|
||||
* .next(stopStepA)
|
||||
* .next(stopStepB)
|
||||
* ....
|
||||
* // later, some other interrupts come
|
||||
* process.shift('stop', 'dizzy')
|
||||
* .next(dizzyStepA)
|
||||
* .next(dizzyStepB)
|
||||
*
|
||||
* The phases listed above would immediately interrupt the steps scheduled
|
||||
* at the previous phase. However, this is a *non-preemptive* process by
|
||||
* default. So, if there is a long-waiting Promise step in the 'start' phase:
|
||||
*
|
||||
* process.start()
|
||||
* .next( longlonglongWaitingPromise ) // <--- now it's waiting this
|
||||
* .next( thisStepIsStarving )
|
||||
* .next( andThisOneToo )
|
||||
* .next( poorSteps )
|
||||
* ....
|
||||
* // some urgent event occurs when it goes to the long waiting promise
|
||||
* process.stop()
|
||||
* .next( doThisAsQuickAsPossible )
|
||||
*
|
||||
* The first step of the 'stop' phase, namely the 'doThisAsQuickAsPossible',
|
||||
* would *not* get executed immediately, since the promise is still waiting the
|
||||
* last step earlier interruption. So, even the following steps of the 'start'
|
||||
* phase would all get dropped, the new phase still need to wait the last one
|
||||
* asynchronous step get resolved to get kicked off.
|
||||
*
|
||||
* ---
|
||||
* ## About the non-preemptive model
|
||||
*
|
||||
* The reason why we can't have a preemptive process is because we couldn't
|
||||
* interrupt each single step in the process, so the most basic unit could be
|
||||
* interrupted is the step. So, the caveat here is make the step as small as
|
||||
* possible, and treat it as some atomic operation that guaranteed to not been
|
||||
* interrupted by Process. For example, if we alias 'next' as 'atomic':
|
||||
*
|
||||
* process.start()
|
||||
* .atomic(stepA) // <--- now it's waiting this
|
||||
* .atomic(stepB)
|
||||
*
|
||||
* // some urgent event occurs
|
||||
* process.stop()
|
||||
* .atomic( doThisAsQuickAsPossible )
|
||||
*
|
||||
* It would be better than:
|
||||
*
|
||||
* process.start()
|
||||
* .atomic(() => stepA.then(stepB))
|
||||
*
|
||||
* // some urgent event occurs
|
||||
* process.stop()
|
||||
* .atomic( doThisAsQuickAsPossible )
|
||||
*
|
||||
* Since in the second example the first step of the 'stop' phase must wait
|
||||
* both the stepA & stepB, while in the first one it only needs to wait stepA.
|
||||
* However, this depends on which atomic operations is needed.
|
||||
*
|
||||
* Nevertheless, user is able to make the steps 'interruptible' via some special
|
||||
* methods of the process. That is, to monitor the phase changes to nullify the
|
||||
* step:
|
||||
*
|
||||
* var process = new Process();
|
||||
* process.start()
|
||||
* .next(() => {
|
||||
* var phaseShifted = false;
|
||||
* process.until('stop')
|
||||
* .next(() => {phaseShifted = true;});
|
||||
* return new Promise((r, rj) => {
|
||||
* setTimeout(() => {
|
||||
* if (phaseShifted) { console.log('do nothing'); }
|
||||
* else { console.log('do something'); }
|
||||
* }, 1000)
|
||||
* });
|
||||
* })
|
||||
*
|
||||
* // some urgent event occurs
|
||||
* process.stop()
|
||||
* .next( doThisAsQuickAsPossible )
|
||||
*
|
||||
* So that the first step of the 'stop' phase would execute immediately after
|
||||
* the phase shifted, since the last step of the previous phase aborted itself.
|
||||
* In future the trick to nullify the last step may be included in as a method
|
||||
* of Process, but currently the manual detecting is still necessary.
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
var Process = function() {
|
||||
this.states = {
|
||||
phase: null,
|
||||
currentPromise: null,
|
||||
until: {
|
||||
resolver: null,
|
||||
phase: null
|
||||
},
|
||||
// @see: #next
|
||||
stepResults: [],
|
||||
// @see: #next
|
||||
currentPhaseSteps: 0
|
||||
};
|
||||
};
|
||||
|
||||
Process.prototype.start = function() {
|
||||
this.states.phase = 'start';
|
||||
this.states.currentPromise = Promise.resolve();
|
||||
return this;
|
||||
};
|
||||
|
||||
Process.prototype.stop = function() {
|
||||
return this.shift('start', 'stop');
|
||||
};
|
||||
|
||||
Process.prototype.destroy = function() {
|
||||
return this.shift('stop', 'destroy');
|
||||
};
|
||||
|
||||
Process.prototype.shift = function(prev, current) {
|
||||
if (prev !== this.states.phase) {
|
||||
var error = new Error(`Must be ${prev} before shift to ${current},
|
||||
but now it's ${this.states.phase}`);
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
this.states.phase = current;
|
||||
if (this.until.phase === this.states.phase) {
|
||||
this.until.resolver();
|
||||
}
|
||||
// Concat new step to switch to the 'next promise'.
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.catch((err) => {
|
||||
if (!(err instanceof Process.InterruptError)) {
|
||||
// We need to re-throw it again and bypass the whole
|
||||
// phase, until the next phase (final phase) to
|
||||
// handle it. Since in Promise, steps after catch would
|
||||
// not be affected by the catched error and keep executing.
|
||||
throw err;
|
||||
}
|
||||
// And if it's an interrupt error we do nothing, so that it would
|
||||
// make the chain omit this error and execute the following steps.
|
||||
});
|
||||
// At the moment of shifting, there are no steps belong to the new phase.
|
||||
this.states.currentPhaseSteps = 0;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Promise that only be resolved when we get shifed to the
|
||||
* target phase.
|
||||
*/
|
||||
Process.prototype.until = function(phase) {
|
||||
var promise = new Promise((res) => {
|
||||
this.states.until.resolver = res;
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* The 'step' can only be a function return Promise/Process/plain value.
|
||||
* No matter a Promise or Process it would return,
|
||||
* the chain would concat it as the Promise rule.
|
||||
* If it's plain value then this process would ignore it, as
|
||||
* what a Promise does.
|
||||
*
|
||||
* About the resolving values:
|
||||
*
|
||||
* .next( fnResolveA, fnResolveB ) --> #save [a, b] in this process
|
||||
* .next( fnResolveC ) --> #receive [a, b] as first argument
|
||||
* .next( fnResolveD ) --> #receive c as first argument
|
||||
* .next( fnResolveE, fnResolveF) --> #each of them receive d as argument
|
||||
*/
|
||||
Process.prototype.next = function(...tasks) {
|
||||
if (!this.states.currentPromise) {
|
||||
throw new Error('Process should initialize with the `start` method');
|
||||
}
|
||||
// At definition stage, set it's phase.
|
||||
// And check if it's a function.
|
||||
tasks.forEach((task) => {
|
||||
if ('function' !== typeof task) {
|
||||
throw new Error(`The task is not a function: ${task}`);
|
||||
}
|
||||
task.phase = this.states.phase;
|
||||
});
|
||||
|
||||
// First, concat a 'then' to check interrupt.
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.then(() => {
|
||||
// Would check: if the phase it belongs to is not what we're in,
|
||||
// the process need to be interrputed.
|
||||
for (var task of tasks) {
|
||||
if (this.checkInterrupt(task)) {
|
||||
throw new Process.InterruptError();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Read it as:
|
||||
// 1. execute all tasks to generate resolvable-promises
|
||||
// 2. Promise.all(...) to wait these resolvable-promises
|
||||
// 3. append a general error handler after the Promise.all
|
||||
// so that if any error occurs it would print them out
|
||||
// So, including the code above, we would have:
|
||||
//
|
||||
// currentPromise {
|
||||
// [checkInterrupt(tasks)]
|
||||
// [Promise.all([taskA1, taskA2...])]
|
||||
// [error handler] +}
|
||||
//
|
||||
// The 'checkInterrupt' and 'error handler' wrap the actual steps
|
||||
// to do the necessary checks.
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.then(() => this.generateStep(tasks));
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.catch(this.generateErrorLogger({
|
||||
'nth-step': this.currentPhaseSteps
|
||||
}));
|
||||
|
||||
// A way to know if these tasks is the first steps in the current phase,
|
||||
// and it's also convenient for debugging.
|
||||
this.states.currentPhaseSteps += 1;
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
Process.prototype.rescue = function(handler) {
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.catch((err) => {
|
||||
if (err instanceof Process.InterruptError) {
|
||||
// Only built-in phase transferring catch can handle interrupts.
|
||||
// Re-throw it until we reach the final catch we set.
|
||||
throw err;
|
||||
} else {
|
||||
handler(err);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface to explicitly put multiple tasks execute at one time.
|
||||
**/
|
||||
Process.prototype.wait = function() {
|
||||
this.next.apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* At the preemptive mode a suppressor would be installed as the final step
|
||||
* of the previous interrupted Promise, to igore any errors since it has been
|
||||
* interrupted.
|
||||
*/
|
||||
Process.prototype.generateSuppressor = function() {
|
||||
return (err) => {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute task and get Promises or plain values them return,
|
||||
* and then return the wrapped Promise as the next step of this
|
||||
* process. The name 'step' indicates the generated Promise,
|
||||
* which is one step of the main Promise of the current phase.
|
||||
*/
|
||||
Process.prototype.generateStep = function(tasks) {
|
||||
// So we unwrap the task first, and then put it in the array.
|
||||
// Since we need to give the 'currentPromise' a function as what the
|
||||
// tasks passed here.
|
||||
var chains = tasks.map((task) => {
|
||||
// Reset the registered results.
|
||||
var stepResults = this.states.stepResults;
|
||||
this.states.stepResults = [];
|
||||
var chain;
|
||||
// If it has multiple results, means it's a task group
|
||||
// generated results.
|
||||
if (this.states.stepResults.length > 1) {
|
||||
chain = task(stepResults);
|
||||
} else {
|
||||
chain = task(stepResults[0]);
|
||||
}
|
||||
|
||||
// Ordinary function returns 'undefine' or other things.
|
||||
if (!chain) {
|
||||
// It's a plain value.
|
||||
// Store it as one of results.
|
||||
this.states.stepResults.push(chain);
|
||||
return Promise.resolve(chain);
|
||||
}
|
||||
|
||||
if (chain instanceof Process) {
|
||||
// Premise: it's a started process.
|
||||
return chain.states.currentPromise.then((resolvedValue) => {
|
||||
this.states.stepResults.push(resolvedValue);
|
||||
});
|
||||
} else if (chain.then) {
|
||||
// Ordinary promise can be concated immediately.
|
||||
return chain.then((resolvedValue) => {
|
||||
this.states.stepResults.push(resolvedValue);
|
||||
});
|
||||
} else {
|
||||
// It's a plain value.
|
||||
// Store it as one of results.
|
||||
this.states.stepResults.push(chain);
|
||||
return Promise.resolve(chain);
|
||||
}
|
||||
});
|
||||
return Promise.all(chains);
|
||||
};
|
||||
|
||||
/** We need this to prevent the step() throw errors.
|
||||
* In this catch, we distinguish the interrupt and other errors,
|
||||
* and then bypass the former and print the later out.
|
||||
*
|
||||
* The final fate of the real errors is it would be re-throw again
|
||||
* after we print the instance out. We need to do that since after an
|
||||
* error the promise shouldn't keep executing. If we don't throw it
|
||||
* again, since the error has been catched, the rest steps in the
|
||||
* promise would still be executed, and the user-set rescues would
|
||||
* not catch this error.
|
||||
*
|
||||
* As a conclusion, no matter whether the error is an interrupt,
|
||||
* we all need to throw it again. The only difference is if it's
|
||||
* and interrupt we don't print it out.
|
||||
*/
|
||||
Process.prototype.generateErrorLogger = function(debuginfo) {
|
||||
return (err) => {
|
||||
if (!(err instanceof Process.InterruptError)) {
|
||||
console.error(`ERROR during #${debuginfo['nth-steps']}
|
||||
step executes: ${err.message}`, err);
|
||||
}
|
||||
throw err;
|
||||
};
|
||||
};
|
||||
|
||||
Process.prototype.checkInterrupt = function(step) {
|
||||
if (step.phase !== this.states.phase) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Because DRY.
|
||||
*/
|
||||
Process.defer = function() {
|
||||
var resolve, reject;
|
||||
var promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
var result = {
|
||||
'resolve': resolve,
|
||||
'reject': reject,
|
||||
'promise': promise
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
/* Static version for mimicking Promise.all */
|
||||
Process.wait = function(steps) {
|
||||
var process = new Process();
|
||||
return process.wait(steps);
|
||||
};
|
||||
|
||||
Process.InterruptError = function(message) {
|
||||
this.name = 'InterruptError';
|
||||
this.message = message || '';
|
||||
};
|
||||
|
||||
Process.InterruptError.prototype = new Error();
|
||||
|
||||
exports.Process = Process;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/* global Process */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Combine the abilities of the event handling and asynchronous operations
|
||||
* sequentializing together. So that every Stream could:
|
||||
*
|
||||
* 1. For the ordinary events, append steps to the main Process to queue
|
||||
* the event handlers.
|
||||
* 2. For other urgent events (interrupts), immediately execute the event
|
||||
* handler without queuing it.
|
||||
* 3. Only receive events when it's 'ready'. Before that, no source events
|
||||
* would be forwarded and handled.
|
||||
* 4. Once phase becomes 'stop', no events would be received again.
|
||||
**/
|
||||
(function(exports) {
|
||||
var Stream = function(configs) {
|
||||
this.configs = {
|
||||
events: configs.events || [],
|
||||
interrupts: configs.interrupts || []
|
||||
};
|
||||
if (configs.sources && 0 !== configs.sources.length) {
|
||||
this.configs.sources = configs.sources;
|
||||
} else {
|
||||
this.configs.sources = [];
|
||||
}
|
||||
this._forwardTo = null;
|
||||
// Need to delegate to Source.
|
||||
this.onchange = this.onchange.bind(this);
|
||||
};
|
||||
|
||||
Stream.prototype.phase = function() {
|
||||
return this.process.states.phase;
|
||||
};
|
||||
|
||||
Stream.prototype.start = function(forwardTo) {
|
||||
this._forwardTo = forwardTo;
|
||||
this.process = new Process();
|
||||
this.process.start();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Kick off Source and start do things.
|
||||
*/
|
||||
Stream.prototype.ready = function() {
|
||||
this.configs.sources.forEach((source) => {
|
||||
source.start(this.onchange);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
Stream.prototype.stop = function() {
|
||||
this.process.stop();
|
||||
this.configs.sources.forEach((source) => {
|
||||
source.stop();
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
Stream.prototype.destroy = function() {
|
||||
this.process.destroy();
|
||||
return this;
|
||||
};
|
||||
|
||||
Stream.prototype.next = function(step) {
|
||||
this.process.next(step);
|
||||
return this;
|
||||
};
|
||||
|
||||
Stream.prototype.rescue = function(rescuer) {
|
||||
this.process.rescue(rescuer);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Promise get resolved when the stream turn to
|
||||
* the specific phase. For example:
|
||||
*
|
||||
* stream.until('stop')
|
||||
* .then(() => { console.log('stream stopped') });
|
||||
* stream.start();
|
||||
*/
|
||||
Stream.prototype.until = function(phase) {
|
||||
return this.process.until(phase);
|
||||
};
|
||||
|
||||
/**
|
||||
* Only when all tasks passed in get resolved,
|
||||
* the process would go to the next.
|
||||
*/
|
||||
Stream.prototype.wait = function(tasks) {
|
||||
this.process.wait(tasks);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* It would receive events from Source, and than queue or not queue
|
||||
* it, depends on whether the event is an interrupt.
|
||||
*/
|
||||
Stream.prototype.onchange = function(evt) {
|
||||
if ('start' !== this.process.states.phase) {
|
||||
return this;
|
||||
}
|
||||
if (-1 !== this.configs.interrupts.indexOf(evt.type)) {
|
||||
// Interrupt would be handled immediately.
|
||||
this._forwardTo(evt);
|
||||
return this;
|
||||
} else {
|
||||
// Event would be handled after queuing.
|
||||
// This is, if the event handle return a Promise or Process,
|
||||
// that can be fulfilled later.
|
||||
this.process.next(() => {
|
||||
return this._forwardTo(evt);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
exports.Stream = Stream;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global ImageUtils, LazyLoader */
|
||||
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const WALLPAPER_KEY = 'wallpaper.image';
|
||||
const WALLPAPER_VALID_KEY = 'wallpaper.image.valid';
|
||||
const DEFAULT_WALLPAPER_URL = '/resources/images/backgrounds/default.png';
|
||||
|
||||
/**
|
||||
* This system module reads the system wallpaper setting on startup
|
||||
* and monitors changes to that setting, broadcasting a
|
||||
* 'wallpaperchange' event with a blob: URL to tell the system and
|
||||
* lockscreen about the new wallpaper.
|
||||
*
|
||||
* If the wallpaper value read from the settings DB is a URL this
|
||||
* module converts it to a blob. If the wallpaper image does not
|
||||
* exactly match the size of the screen, this module resizes it
|
||||
* (lazy-loading shared/js/image_utils.js when needed). If the
|
||||
* wallpaper value is converted to a blob or resized, the modified
|
||||
* value is saved back to the settings DB so that it will not need
|
||||
* to be modified the next time it is read.
|
||||
*
|
||||
* start(), stop(), and getBlobURL() are the only public methods,
|
||||
* and stop() is only exposed for the benefit of unit
|
||||
* tests. _setWallpaper() is called on startup and whenever the
|
||||
* wallpaper.image setting changes. Each call to _setWallpaper()
|
||||
* eventually causes a call to _publish() which broadcasts the new
|
||||
* wallpaper event to the lockscreen and the rest of the system
|
||||
* app. The call to _publish() does not always happen directly,
|
||||
* however: _setWallpaper() may call _checkSize(), which calls
|
||||
* _publish(), or it may call _toBlob() which calls _checkSize().
|
||||
* Unless the build is mis-configured and the wallpaper in the
|
||||
* settings db and the fallback default wallpaper is broken, every
|
||||
* call to _setWallpaper() ends up broadcasting a 'wallpaperchange'
|
||||
* event with a valid blob: url for a wallpaper image that has the
|
||||
* same size as the screen.
|
||||
*
|
||||
* @class WallpaperManager
|
||||
*/
|
||||
function WallpaperManager() {
|
||||
this._started = false;
|
||||
this._blobURL = null;
|
||||
}
|
||||
|
||||
WallpaperManager.prototype = {
|
||||
/**
|
||||
* Bootstrap the module. Read the current wallpaper from the
|
||||
* settings db and pass it to _setWallpaper(). Also listen for
|
||||
* changes to the wallpaper and invoke _setWallpaper() for each
|
||||
* one.
|
||||
*/
|
||||
start: function() {
|
||||
if (this._started) {
|
||||
throw 'Instance should not be start()\'ed twice.';
|
||||
}
|
||||
this._started = true;
|
||||
debug('started');
|
||||
|
||||
// Query the wallpaper
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var query = lock.get(WALLPAPER_KEY);
|
||||
query.onsuccess = function() {
|
||||
var wallpaper = query.result[WALLPAPER_KEY];
|
||||
if (!wallpaper) {
|
||||
debug('no wallpaper found at startup; using default');
|
||||
this._setWallpaper(DEFAULT_WALLPAPER_URL);
|
||||
}
|
||||
else if (wallpaper instanceof Blob) {
|
||||
// If the wallpaper is a blob, first go see if we have already
|
||||
// validated it size. Because if we have, we don't have to check
|
||||
// the size again or even load the code to check its size.
|
||||
var query2 = lock.get(WALLPAPER_VALID_KEY);
|
||||
query2.onsuccess = function() {
|
||||
var valid = query2.result[WALLPAPER_VALID_KEY];
|
||||
this._setWallpaper(wallpaper, valid);
|
||||
}.bind(this);
|
||||
}
|
||||
else {
|
||||
// If the wallpaper is not a blob, just pass it to _setWallpaper
|
||||
// and try to convert it to a blob there.
|
||||
this._setWallpaper(wallpaper);
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
// And register a listener so we'll be notified of future changes
|
||||
// to the wallpaper
|
||||
this.observer = function(e) {
|
||||
this._setWallpaper(e.settingValue);
|
||||
}.bind(this);
|
||||
navigator.mozSettings.addObserver(WALLPAPER_KEY, this.observer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop the module an stop listening for changes to the wallpaper setting.
|
||||
* This method is only used by unit tests.
|
||||
*/
|
||||
stop: function() {
|
||||
if (!this._started) { return; }
|
||||
navigator.mozSettings.removeObserver(WALLPAPER_KEY, this.observer);
|
||||
this._started = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the blob URL saved from earlier wallpaper change event
|
||||
* The lockscreen may miss the event and needs to look the URL up here.
|
||||
* @returns {String} the blob URL
|
||||
*/
|
||||
getBlobURL: function() {
|
||||
if (!this._started) { return; }
|
||||
return this._blobURL;
|
||||
},
|
||||
|
||||
//
|
||||
// This method is called on startup and when the wallpaper
|
||||
// changes. It always causes _publish() to be invoked and a
|
||||
// "wallpaperchange" event to be broadcast to interested
|
||||
// listeners. If the new value is a blob that is already
|
||||
// validated, then _publish() is called directly. Otherwise, it is
|
||||
// called indirectly by _toBlob() or _checkSize().
|
||||
//
|
||||
_setWallpaper: function(value, valid) {
|
||||
if (!this._started) { return; }
|
||||
|
||||
// If we are called because we just saved a resized blob back
|
||||
// to the settings db, then ignore the call.
|
||||
if (value instanceof Blob && value.size === this.savedBlobSize) {
|
||||
this.savedBlobSize = false;
|
||||
return;
|
||||
}
|
||||
|
||||
debug('new wallpaper', valid ? 'size already validated' : '');
|
||||
|
||||
if (typeof value === 'string') {
|
||||
this._toBlob(value);
|
||||
}
|
||||
else if (value instanceof Blob) {
|
||||
// If this blob has already been validated, we can just display it.
|
||||
// Otherwise we need to check its size first
|
||||
if (valid) {
|
||||
this._publish(value);
|
||||
}
|
||||
else {
|
||||
this._checkSize(value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The value in the settings database is invalid, so
|
||||
// use the default image. Note that this will update the
|
||||
// settings db with a valid value.
|
||||
debug('Invalid wallpaper value in settings;',
|
||||
'reverting to default wallpaper.');
|
||||
this._toBlob(DEFAULT_WALLPAPER_URL);
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// This method expects a wallpaper URL (possibly a data: URL) and
|
||||
// uses XHR to convert it to a blob. If it succeeds, it passes the
|
||||
// blob to _checkSize() which resizes it if needed and calls
|
||||
// _publish() to broadcast the new wallpaper.
|
||||
//
|
||||
_toBlob: function(url) {
|
||||
if (!this._started) { return; }
|
||||
debug('converting wallpaper url to blob');
|
||||
|
||||
// If we trying to convert the default wallpaper url to a blob
|
||||
// note that because there is some error recovery code that behaves
|
||||
// differently in that last resort case.
|
||||
this.tryingDefaultWallpaper = (url === DEFAULT_WALLPAPER_URL);
|
||||
|
||||
// If the settings db had a string in it we assume it is a
|
||||
// relative url or data: url and try to read it with XHR.
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.send();
|
||||
xhr.onload = function() {
|
||||
// Once we've loaded the wallpaper as a blob, verify its size.
|
||||
// We pass true as the second argument to force it to be saved
|
||||
// back to the db (as a blob) even if the size is okay.
|
||||
this._checkSize(xhr.response, true);
|
||||
}.bind(this);
|
||||
xhr.onerror = function() {
|
||||
// If we couldn't load the url and if it was something other
|
||||
// than the default wallpaper url, then try again with the default.
|
||||
if (!this.tryingDefaultWallpaper) {
|
||||
debug('corrupt wallpaper url in settings;',
|
||||
'reverting to default wallpaper');
|
||||
this._toBlob(DEFAULT_WALLPAPER_URL);
|
||||
}
|
||||
else {
|
||||
// This was our last resort, and it failed, so no wallpaper
|
||||
// image is available.
|
||||
console.error('Cannot load wallpaper from', url);
|
||||
}
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
//
|
||||
// This method checks the dimensions of the image blob and crops
|
||||
// and resizes the image if necessary so that it is exactly the
|
||||
// same size as the screen. If the image was resized, or if it was
|
||||
// read from a URL, then this method saves the new blob back to
|
||||
// the settings db and marks it as valid. If the image was not
|
||||
// resized, then the image is marked as valid so that the check
|
||||
// does not need to be performed when the phone is rebooted. In
|
||||
// either case, after the image is saved and/or validated, this
|
||||
// method calls _publish() to broadcast the new wallpaper.
|
||||
//
|
||||
// If the blob does not hold a valid image, that will be
|
||||
// discovered while attempting to check its size and in that case,
|
||||
// this method falls back on the default wallpaper by calling
|
||||
// _toBlob() with the default wallpaper URL.
|
||||
//
|
||||
// This method lazy-loads ImageUtils from shared/js/image_utils.js.
|
||||
// Once a wallpaper has had its size checked once, it is marked as
|
||||
// valid in the settings db, so these image utilities will not
|
||||
// need to be loaded into the system app on subsequent reboots.
|
||||
//
|
||||
_checkSize: function(blob, needsToBeSaved) {
|
||||
if (!this._started) { return; }
|
||||
debug('resizing wallpaper if needed');
|
||||
|
||||
// How big (in device pixels) is the screen?
|
||||
var screenWidth = Math.ceil(screen.width * window.devicePixelRatio);
|
||||
var screenHeight = Math.ceil(screen.height * window.devicePixelRatio);
|
||||
|
||||
// For performance we need to guarantee that the size of the wallpaper
|
||||
// is exactly the same as the size of the screen. LazyLoad the
|
||||
// ImageUtils module, and call its resizeAndCropToCover() method to
|
||||
// resize and crop the image as needed so that it is the right size.
|
||||
// Note that this utility funtion can determine the size of an image
|
||||
// without decoding it and if the image is already the right size
|
||||
// it will not modify it.
|
||||
LazyLoader.load('shared/js/image_utils.js', function() {
|
||||
ImageUtils
|
||||
.resizeAndCropToCover(blob, screenWidth, screenHeight, ImageUtils.PNG)
|
||||
.then(
|
||||
function resolve(resizedBlob) {
|
||||
// If the blob changed or if the second argument was true
|
||||
// then we need to save the blob back to the settings db
|
||||
if (resizedBlob !== blob || needsToBeSaved) {
|
||||
this._save(resizedBlob);
|
||||
}
|
||||
else {
|
||||
// If the blob didn't change we don't have to save it,
|
||||
// but we do need to mark it as valid
|
||||
this._validate();
|
||||
}
|
||||
|
||||
// Display the wallpaper
|
||||
this._publish(resizedBlob);
|
||||
}.bind(this),
|
||||
function reject(error) {
|
||||
// This will only happen if the settings db contains a blob that
|
||||
// is not actually an image. If that happens for some reason,
|
||||
// fall back on the default wallpaper.
|
||||
if (!this.tryingDefaultWallpaper) {
|
||||
debug('Corrupt wallpaper image in settings;',
|
||||
'reverting to default wallpaper.');
|
||||
this._toBlob(DEFAULT_WALLPAPER_URL);
|
||||
}
|
||||
else {
|
||||
// We were already trying the default wallpaper and it failed.
|
||||
// So we just give up in this case.
|
||||
console.error('Default wallpaper image is invalid');
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
//
|
||||
// This method sets a property in the settings db to indicate that
|
||||
// the current wallpaper is the same size as the screen. Setting
|
||||
// this property is an optimization that allows us to skip the
|
||||
// call to _checkSize() on subsequent startups. This method
|
||||
// returns synchronously and does not wait for the settings db
|
||||
// operation to complete.
|
||||
//
|
||||
_validate: function() {
|
||||
if (!this._started) { return; }
|
||||
debug('marking wallpaper as valid');
|
||||
var settings = {};
|
||||
settings[WALLPAPER_VALID_KEY] = true; // We've checked its size
|
||||
navigator.mozSettings.createLock().set(settings);
|
||||
},
|
||||
|
||||
//
|
||||
// This method saves the wallpaper blob to the settings db and
|
||||
// also marks it as valid so that we know on subsequent startups
|
||||
// that its size has already been checked. This method returns
|
||||
// synchronously and does not wait for the settings db operation
|
||||
// to complete.
|
||||
//
|
||||
_save: function(blob) {
|
||||
if (!this._started) { return; }
|
||||
debug('saving converted or resized wallpaper to settings');
|
||||
|
||||
// Set a flag so that we don't repeat this whole process when
|
||||
// we're notified about this save. The flag contains the size of
|
||||
// the blob we're saving so it is very unlikely that we'll have
|
||||
// a race condition.
|
||||
this.savedBlobSize = blob.size;
|
||||
|
||||
// Now save the blob to the settings db, and also save a flag
|
||||
// that indicates that we've already checked the size of the image.
|
||||
// This allows us to skip the check at boot time.
|
||||
var settings = {};
|
||||
settings[WALLPAPER_KEY] = blob;
|
||||
settings[WALLPAPER_VALID_KEY] = true; // We've checked its size
|
||||
navigator.mozSettings.createLock().set(settings);
|
||||
},
|
||||
|
||||
//
|
||||
// This method creates a blob: URL for the specfied blob and publishes
|
||||
// the URL via a 'wallpaperchange' event. If there was a previous
|
||||
// wallpaper, its blob: URL is revoked. This method is synchronous.
|
||||
//
|
||||
_publish: function(blob) {
|
||||
if (!this._started) { return; }
|
||||
debug('publishing wallpaperchange event');
|
||||
|
||||
// If we have a blob:// url for previous wallpaper, release it now
|
||||
if (this._blobURL) {
|
||||
URL.revokeObjectURL(this._blobURL);
|
||||
}
|
||||
|
||||
// Create a new blob:// url for this blob
|
||||
this._blobURL = URL.createObjectURL(blob);
|
||||
|
||||
// And tell the system about it.
|
||||
window.dispatchEvent(new CustomEvent('wallpaperchange',
|
||||
{ detail: { url: this._blobURL } }));
|
||||
}
|
||||
};
|
||||
|
||||
// Log debug messages
|
||||
function debug(...args) {
|
||||
if (WallpaperManager.DEBUG) {
|
||||
args.unshift('[WallpaperManager]');
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
}
|
||||
WallpaperManager.DEBUG = false; // Set to false to silence debug output
|
||||
|
||||
/** @exports WallpaperManager */
|
||||
exports.WallpaperManager = WallpaperManager;
|
||||
}(window));
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/* global LockScreenBasicComponent, LockScreenClockWidgetSetup, mozIntl */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The Clock widget on LockScreen.
|
||||
* Clock widget states:
|
||||
* ClockSetup, ClockTick, ClockStop
|
||||
**/
|
||||
(function(exports) {
|
||||
var LockScreenClockWidget = function() {
|
||||
LockScreenBasicComponent.apply(this);
|
||||
this.resources.elements.time = 'lockscreen-clock-time';
|
||||
this.resources.elements.date = 'lockscreen-date';
|
||||
this.resources.elements.alarm = 'lockscreen-alarm';
|
||||
this.resources.elements.alarmtime = 'lockscreen-alarm-time';
|
||||
this.timeFormatter = null;
|
||||
this.dateFormatter = null;
|
||||
this.configs.logger.debug = false; // turn on this when we're debugging
|
||||
};
|
||||
LockScreenClockWidget.prototype =
|
||||
Object.create(LockScreenBasicComponent.prototype);
|
||||
|
||||
LockScreenClockWidget.prototype.setup = function() {
|
||||
return (new LockScreenClockWidgetSetup(this));
|
||||
};
|
||||
|
||||
LockScreenClockWidget.prototype.updateFormatters = function() {
|
||||
this.dateFormatter = Intl.DateTimeFormat(navigator.languages, {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
// we're using mozIntl.DateTimeFormat to remove dayperiod
|
||||
this.timeFormatter = mozIntl.DateTimeFormat(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
dayperiod: false,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
LockScreenClockWidget.prototype.updateClock =
|
||||
function() {
|
||||
var now = new Date();
|
||||
|
||||
var timeText = this.timeFormatter.format(now);
|
||||
var dateText = this.dateFormatter.format(now);
|
||||
|
||||
this.resources.elements.time.firstChild.data = timeText;
|
||||
this.resources.elements.date.firstChild.data = dateText;
|
||||
this.logger.debug('Clock updated', now);
|
||||
};
|
||||
|
||||
LockScreenClockWidget.prototype.updateAlarm =
|
||||
function() {
|
||||
this.fetchAlarmData()
|
||||
.then((alarmData) => {
|
||||
// if there is no alarm
|
||||
if(!alarmData) {
|
||||
this.resources.elements.alarm.classList.add('no-alarms');
|
||||
this.resources.elements.alarm.classList.remove('has-alarm');
|
||||
return;
|
||||
}
|
||||
|
||||
var type = 'AM',
|
||||
hour = parseInt(alarmData.hour, 10);
|
||||
|
||||
// Decide wheter to use AM or PM
|
||||
if(hour > 12) {
|
||||
type = 'PM';
|
||||
hour = hour - 12;
|
||||
}
|
||||
|
||||
var fullAlarmTime = hour + ':' + alarmData.minute + type;
|
||||
this.resources.elements.alarmtime.textContent = fullAlarmTime;
|
||||
this.resources.elements.alarm.classList.remove('no-alarms');
|
||||
this.resources.elements.alarm.classList.add('has-alarm');
|
||||
this.logger.debug('Alarm updated to :', fullAlarmTime);
|
||||
}).catch(function(e){
|
||||
console.error('Error occured while alarm info on lockscreen : ', e);
|
||||
throw(e);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Gets the Alarm Data from the DataStore.
|
||||
*/
|
||||
LockScreenClockWidget.prototype.fetchAlarmData = function() {
|
||||
return navigator.getDataStores('alarms')
|
||||
.then((stores) => {
|
||||
return [stores[0].getLength(), stores[0]];
|
||||
}).then(([len, store]) => {
|
||||
if(0 === len) {
|
||||
return;
|
||||
}
|
||||
return store.get(1);
|
||||
}).then((fetchedData = {}) => {
|
||||
if(0 === Object.keys(fetchedData).length) {
|
||||
return null;
|
||||
}
|
||||
return fetchedData;
|
||||
}).catch(function(e){
|
||||
console.error('Error occured while getting alarm data : ', e);
|
||||
throw(e);
|
||||
});
|
||||
};
|
||||
|
||||
exports.LockScreenClockWidget = LockScreenClockWidget;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/* global DOMEventSource */
|
||||
/* global LockScreenBasicState */
|
||||
/* global LockScreenClockWidgetTick */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Setup state would initialize all resources and properties the
|
||||
* component has.
|
||||
*
|
||||
* This is only a initialization state, and would immediately
|
||||
* transfer to the starting state.
|
||||
*
|
||||
* Initialization state should be the only one state which isn't
|
||||
* reentrant. Other states by default should be reentrant.
|
||||
**/
|
||||
(function(exports) {
|
||||
var LockScreenClockWidgetSetup = function(component) {
|
||||
LockScreenBasicState.apply(this, arguments);
|
||||
this.configs.name = 'LockScreenClockWidgetSetup';
|
||||
// Just to prevent stream without stream would throw error.
|
||||
this.configs.stream.sources = [ new DOMEventSource({}) ];
|
||||
};
|
||||
LockScreenClockWidgetSetup.prototype =
|
||||
Object.create(LockScreenBasicState.prototype);
|
||||
|
||||
LockScreenClockWidgetSetup.prototype.start = function() {
|
||||
return LockScreenBasicState.prototype.start.call(this)
|
||||
.next(this.queryElements.bind(this))
|
||||
.next(this.component.updateFormatters.bind(this.component))
|
||||
.next(this.component.updateClock.bind(this.component))
|
||||
.next(this.component.updateAlarm.bind(this.component))
|
||||
.next(this.transferToTick.bind(this));
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSetup.prototype.transferToTick = function() {
|
||||
return this.transferTo(LockScreenClockWidgetTick);
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSetup.prototype.queryElements = function() {
|
||||
var elements = this.component.resources.elements;
|
||||
for (var key in elements) {
|
||||
if ('string' === typeof elements[key]) {
|
||||
var query = elements[key];
|
||||
var result = document.getElementById(query);
|
||||
if (!result) {
|
||||
throw new Error(`Can't query element ${key} with query: ${query}`);
|
||||
}
|
||||
|
||||
// Create a text node so we can update its data later, otherwise if we
|
||||
// change textContent it will remove and insert a new text node, which
|
||||
// could cause flickering if the docshell still has isActive false.
|
||||
if (!result.firstChild) {
|
||||
result.appendChild(document.createTextNode(''));
|
||||
}
|
||||
elements[key] = result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.LockScreenClockWidgetSetup = LockScreenClockWidgetSetup;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/* global Stream, DOMEventSource */
|
||||
/* global LockScreenClockWidgetTick, LockScreenBasicState */
|
||||
'use strict';
|
||||
|
||||
/***/
|
||||
(function(exports) {
|
||||
var LockScreenClockWidgetSuspend = function(component) {
|
||||
LockScreenBasicState.apply(this, arguments);
|
||||
this.configs.name = 'LockScreenClockWidgetSuspend';
|
||||
this.configs.stream.interrupts = [
|
||||
'screenchange',
|
||||
];
|
||||
this.configs.stream.sources =
|
||||
[new DOMEventSource({events: ['screenchange']})];
|
||||
this.handleSourceEvent = this.handleSourceEvent.bind(this);
|
||||
};
|
||||
LockScreenClockWidgetSuspend.prototype =
|
||||
Object.create(LockScreenBasicState.prototype);
|
||||
|
||||
LockScreenClockWidgetSuspend.prototype.start = function() {
|
||||
this.stream = new Stream(this.configs.stream);
|
||||
return this.stream.start(this.handleSourceEvent)
|
||||
.next(this.stream.ready.bind(this.stream));
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSuspend.prototype.stop =
|
||||
function() {
|
||||
return LockScreenBasicState.prototype.stop.call(this);
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSuspend.prototype.handleSourceEvent =
|
||||
function(evt) {
|
||||
switch (evt.type) {
|
||||
case 'screenchange':
|
||||
if (evt.detail.screenEnabled) {
|
||||
// Bug 1234731 mentioned that if we update clock in tick,
|
||||
// asynchronous step means it will be queued after other incidents
|
||||
// at the moment screen get light up (ex: ril events in Gecko),
|
||||
// so there is a visual lag that we need to deal with.
|
||||
//
|
||||
// The solution is we update the clock before we transfer to the
|
||||
// Tick state, so that the first painting will be earlier, and using
|
||||
// another entry in tick to transfer to, so that we can avoid
|
||||
// calling the function again in the Tick state.
|
||||
this.component.updateClock();
|
||||
return this.transferToTick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSuspend.prototype.transferToTick =
|
||||
function() {
|
||||
this.component.transferTo(LockScreenClockWidgetTick);
|
||||
};
|
||||
|
||||
exports.LockScreenClockWidgetSuspend = LockScreenClockWidgetSuspend;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/* global Stream, DOMEventSource, MinuteClockSource */
|
||||
/* global LockScreenClockWidgetSuspend, LockScreenBasicState */
|
||||
'use strict';
|
||||
|
||||
/***/
|
||||
(function(exports) {
|
||||
var LockScreenClockWidgetTick = function(component) {
|
||||
LockScreenBasicState.apply(this, arguments);
|
||||
this.configs.name = 'LockScreenClockWidgetTick';
|
||||
this.configs.stream.events = [
|
||||
'ftudone',
|
||||
'moztimechange',
|
||||
'lockscreen-notification-clock-tick'
|
||||
];
|
||||
this.configs.stream.interrupts = [
|
||||
'screenchange'
|
||||
];
|
||||
this.configs.stream.sources =
|
||||
[new DOMEventSource({events: [
|
||||
'screenchange',
|
||||
'ftudone',
|
||||
'moztimechange',
|
||||
'screenchange',
|
||||
'lockscreen-notification-clock-tick'
|
||||
]}),
|
||||
new MinuteClockSource({
|
||||
'type': 'lockscreen-notification-clock-tick'
|
||||
})
|
||||
];
|
||||
this.handleSourceEvent = this.handleSourceEvent.bind(this);
|
||||
};
|
||||
LockScreenClockWidgetTick.prototype =
|
||||
Object.create(LockScreenBasicState.prototype);
|
||||
|
||||
/**
|
||||
* Note:
|
||||
* Due to performance issue we actually forked the start method to two,
|
||||
* the first one, with previous state is Setup, means it's from the widget
|
||||
* initialization, and need a full warming up before we call the clock
|
||||
* updating function; the second one, because of Bug 1234731, will update
|
||||
* the clock in the Suspend state in prior to this state, so we shouldn't
|
||||
* call it here again.
|
||||
*
|
||||
* In fact, a state diagram conscious solution is to split one common Tick
|
||||
* state into two "TickAfterSetup" and "TickFromSuspend" states.
|
||||
* However, this is too rigid and verbosed, so that although we concern about
|
||||
* the growth of code may soon mix the complicated statements up, we may
|
||||
* treat the argument and branch as the threshold. If we trick the start
|
||||
* method more than this case, we should immediately reconsider to split the
|
||||
* state.
|
||||
*/
|
||||
LockScreenClockWidgetTick.prototype.start = function() {
|
||||
if ('LockScreenClockWidgetSetup' !==
|
||||
this.component._previousState.configs.name) {
|
||||
return this.restart();
|
||||
}
|
||||
|
||||
this.stream = new Stream(this.configs.stream);
|
||||
return this.stream.start(this.handleSourceEvent)
|
||||
.next(this.component.updateFormatters.bind(this.component))
|
||||
.next(this.component.updateClock.bind(this.component))
|
||||
.next(this.stream.ready.bind(this.stream));
|
||||
};
|
||||
|
||||
LockScreenClockWidgetTick.prototype.restart = function() {
|
||||
this.stream = new Stream(this.configs.stream);
|
||||
return this.stream.start(this.handleSourceEvent)
|
||||
.next(this.stream.ready.bind(this.stream));
|
||||
};
|
||||
|
||||
LockScreenClockWidgetTick.prototype.stop =
|
||||
function() {
|
||||
return LockScreenBasicState.prototype.stop.call(this);
|
||||
};
|
||||
|
||||
LockScreenClockWidgetTick.prototype.handleSourceEvent =
|
||||
function(evt) {
|
||||
switch (evt.type) {
|
||||
case 'moztimechange':
|
||||
this.component.updateFormatters();
|
||||
this.component.updateClock();
|
||||
break;
|
||||
case 'ftudone':
|
||||
case 'lockscreen-notification-clock-tick':
|
||||
this.component.updateClock();
|
||||
break;
|
||||
case 'screenchange':
|
||||
if (!evt.detail.screenEnabled) {
|
||||
return this.transferToSuspend();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenClockWidgetTick.prototype.transferToSuspend = function() {
|
||||
this.component.transferTo(LockScreenClockWidgetSuspend);
|
||||
};
|
||||
|
||||
exports.LockScreenClockWidgetTick = LockScreenClockWidgetTick;
|
||||
})(window);
|
||||
|
||||
120
assets/webapps/system.gaiamobile.org/lockscreen/lockscreen.html
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<!-- Content of LockScreen frame -->
|
||||
|
||||
<div id="lockscreen-frame" hidden>
|
||||
|
||||
<style scoped>
|
||||
@import url("lockscreen/style/lockscreen_fake_notification.css");
|
||||
@import url("lockscreen/style/lockscreen_frame.css");
|
||||
@import url("lockscreen/style/notifications.css");
|
||||
@import url("lockscreen/style/lockscreen.css");
|
||||
@import url("lockscreen/style/lockscreen_media_playback.css");
|
||||
</style>
|
||||
|
||||
<script defer src="lockscreen/js/lockscreen_media_playback.js"></script>
|
||||
|
||||
<script defer src="lockscreen/js/lockscreen_notification_builder.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_notifications.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_charging.js"></script>
|
||||
<script defer src="lockscreen/js/source/source_event.js"></script>
|
||||
<script defer src="lockscreen/js/source/dom_event_source.js"></script>
|
||||
<script defer src="lockscreen/js/source/setting_source.js"></script>
|
||||
<script defer src="lockscreen/js/source/minute_clock_source.js"></script>
|
||||
<script defer src="lockscreen/js/stream/process.js"></script>
|
||||
<script defer src="lockscreen/js/stream/stream.js"></script>
|
||||
<script defer src="lockscreen/js/component/lockscreen_basic_component.js"></script>
|
||||
<script defer src="lockscreen/js/state/lockscreen_basic_state.js"></script>
|
||||
<script defer src="lockscreen/js/widgets/clock/lockscreen_clock_widget.js"></script>
|
||||
<script defer src="lockscreen/js/widgets/clock/lockscreen_clock_widget_setup.js"></script>
|
||||
<script defer src="lockscreen/js/widgets/clock/lockscreen_clock_widget_tick.js"></script>
|
||||
<script defer src="lockscreen/js/widgets/clock/lockscreen_clock_widget_suspend.js"></script>
|
||||
|
||||
<script defer src="lockscreen/js/lockscreen.js"></script>
|
||||
|
||||
<script defer src="lockscreen/js/lockscreen_frame_agent.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_base_state.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_keypadhiding.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_keypadrising.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_keypadshow.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_panelhide.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_sliderestore.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_slideshow.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_unlock.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_secure_app_launching.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_logger.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_state_manager.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_bootstrap.js"></script>
|
||||
|
||||
<div id="lockscreen" class="uninit" data-panel="main" data-panel="main" data-z-index-level="lockscreen">
|
||||
<div id="lockscreen-background" class="lockscreen-background"></div>
|
||||
<div id="lockscreen-masked-background" class="blank"></div>
|
||||
<div id="lockscreen-container">
|
||||
<div id="lockscreen-panel-main" class="lockscreen-panel">
|
||||
<div id="lockscreen-header">
|
||||
<div id="lockscreen-conn-states" hidden></div>
|
||||
<div id="lockscreen-mute" hidden></div>
|
||||
<div id="lockscreen-alt-camera" class="lockscreen-icon">
|
||||
<div id="lockscreen-alt-camera-button" role="button" data-l10n-id="camera-a11y-button" aria-label="Camera"></div>
|
||||
</div>
|
||||
<div id="lockscreen-clock-widget">
|
||||
<div id="lockscreen-clock-time"></div>
|
||||
<div id="lockscreen-date-alarm">
|
||||
<div id="lockscreen-date"></div>
|
||||
<div id="lockscreen-alarm" class="no-alarms">
|
||||
<span aria-hidden="true">|</span>
|
||||
<span id="lockscreen-alarm-time" data-l10n-id="next-alarm" aria-label="Next Alarm"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="lockscreen-charging"></div>
|
||||
</div>
|
||||
<div id="notifications-lockscreen-container">
|
||||
</div>
|
||||
<div id="lockscreen-notification-arrow">
|
||||
</div>
|
||||
<div id="lockscreen-message" hidden></div>
|
||||
<div id="lockscreen-area"></div>
|
||||
<div id="lockscreen-media-container" hidden>
|
||||
<div id="lockscreen-media-container" class="media-playback-container fake-notification">
|
||||
<div class="media-playback-nowplaying">
|
||||
<bdi class="track" data-l10n-id="mediaPlaybackTrack"></bdi>
|
||||
</div>
|
||||
<div class="media-playback-controls" role="toolbar" data-l10n-id="mediaPlaybackControls">
|
||||
<!-- XXX: These used to be button elements for prev/pause/next buttons but we are not able to receive contextmenu event from them. Change to div elements to make it work and able to fast seek from it (Bug 1017294) -->
|
||||
<div data-icon="skip-back" class="previous" data-l10n-id="mediaPlaybackPrevious"></div>
|
||||
<div data-icon="play" class="play-pause" data-l10n-id="mediaPlaybackPlay"></div>
|
||||
<div data-icon="skip-forward" class="next" data-l10n-id="mediaPlaybackNext"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="lockscreen-icon-container">
|
||||
<div id="lockscreen-area-camera"
|
||||
class="lockscreen-icon-area lockscreen-icon-left dark" role="button"
|
||||
data-l10n-id="camera-a11y-button" aria-label="Camera">
|
||||
<div class="lockscreen-icon">
|
||||
</div>
|
||||
</div>
|
||||
<div id="lockscreen-area-unlock"
|
||||
class="lockscreen-icon-area lockscreen-icon-right dark" role="button"
|
||||
data-l10n-id="unlock-a11y-button" aria-label="Unlock">
|
||||
<div class="lockscreen-icon">
|
||||
</div>
|
||||
</div>
|
||||
<div id="lockscreen-area-slide"
|
||||
class="lockscreen-icon-area lockscreen-icon-center">
|
||||
<div id="lockscreen-canvas-wrapper">
|
||||
<canvas id="lockscreen-canvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- XXX: Before we use real keyboard as LockScreen input pad (Bug 1053680),
|
||||
we need this to do the decoupling -->
|
||||
<div id="lockscreen-inputpad-frame-placeholder"></div>
|
||||
|
||||
<div id="lockscreen-panel-emergency-call" class="lockscreen-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of content of LockScreen frame -->
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<!-- Content of LockScreenInputpad frame -->
|
||||
<div id="lockscreen-inputpad-frame" hidden>
|
||||
|
||||
<style scoped>
|
||||
@import url("lockscreen/style/lockscreen_inputpad.css");
|
||||
</style>
|
||||
<script defer src="lockscreen/js/lockscreen_inputpad.js"></script>
|
||||
<script defer src="lockscreen/js/lockscreen_inputpad_bootstrap.js"></script>
|
||||
|
||||
<div id="lockscreen-panel-passcode" class="lockscreen-panel" data-l10n-id="panel-passcode">
|
||||
<p id="lockscreen-passcode-code"><span></span><span></span><span></span><span></span></p>
|
||||
<div id="lockscreen-passcode-pad">
|
||||
<a role="key" href="#" data-key="1" class="row0"><div>1<span></span></div></a>
|
||||
<a role="key" href="#" data-key="2" class="row0"><div>2<span>ABC</span></div></a>
|
||||
<a role="key" href="#" data-key="3" class="row0"><div>3<span>DEF</span></div></a>
|
||||
<a role="key" href="#" data-key="4" class="row1"><div>4<span>GHI</span></div></a>
|
||||
<a role="key" href="#" data-key="5" class="row1"><div>5<span>JKL</span></div></a>
|
||||
<a role="key" href="#" data-key="6" class="row1"><div>6<span>MNO</span></div></a>
|
||||
<a role="key" href="#" data-key="7" class="row2"><div>7<span>PQRS</span></div></a>
|
||||
<a role="key" href="#" data-key="8" class="row2"><div>8<span>TUV</span></div></a>
|
||||
<a role="key" href="#" data-key="9" class="row2"><div>9<span>WXYZ</span></div></a>
|
||||
<a role="button" href="#" data-key="e" class="lockscreen-passcode-pad-func last-row row3"><div><span data-l10n-id="emergency-call-button">Emergency Call</span></div></a>
|
||||
<a role="key" href="#" data-key="0" class="last-row row3"><div>0</div></a>
|
||||
<a role="button" href="#" data-key="c" class="lockscreen-passcode-pad-func last-row row3"><div><span data-l10n-id="cancel">Cancel</span></div></a>
|
||||
<a role="key" href="#" data-key="b" class="last-row row3"><div data-l10n-id="undo">⌫</div></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 5 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 256 B |
|
After Width: | Height: | Size: 240 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 183 B |
|
|
@ -0,0 +1,458 @@
|
|||
/**
|
||||
* NOTICE: This file should be kept in sync with
|
||||
* callscreen/style/lockscreen.css
|
||||
**/
|
||||
|
||||
/**
|
||||
* BiDi note: because this stylesheet is 'scoped', instead of:
|
||||
* html[dir="rtl"] #volume { ... }
|
||||
* we must use:
|
||||
* #volume:-moz-dir(rtl) { ... }
|
||||
*/
|
||||
|
||||
#lockscreen {
|
||||
--panel-transition-secs: var(--transition-duration);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
visibility: visible;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
#screen.active-statusbar > #lockscreen {
|
||||
top: 4rem;
|
||||
height: calc(100% - 4rem);
|
||||
}
|
||||
|
||||
#lockscreen.unlocked {
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because of performance issue, we need to use an individual
|
||||
* background element instead of setting the background inside the container.
|
||||
*
|
||||
* This would be a step toward removing the overhead while a large container
|
||||
* contains children and do the opacity animation, which would cost lot on
|
||||
* temporary surfaces.
|
||||
**/
|
||||
#lockscreen-background, #lockscreen-masked-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
|
||||
/* Must match the CSS background rule of #screen in system.css */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
-moz-user-select: none;
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#lockscreen-background {
|
||||
background-color: #000;
|
||||
background-size: cover; /* Possible memory over-consumption with huge images, it should be removed for improving stability*/
|
||||
background-position: 50% 50%;
|
||||
}
|
||||
|
||||
#lockscreen-masked-background {
|
||||
background-size: 100% 100%;
|
||||
transition: background-image var(--transition-duration) ease,
|
||||
background-color var(--transition-duration) ease;
|
||||
}
|
||||
|
||||
#lockscreen-masked-background.blank {
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
#screen.lockscreen-camera > #lockscreen-camera {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* When switching lock-camera off,
|
||||
* transition time should be delayed to prevent homescreen appears.
|
||||
* (Let lockscreen appears first)
|
||||
*/
|
||||
#screen:not(.lockscreen-camera) > #lockscreen-camera {
|
||||
transition-delay: var(--transition-duration);
|
||||
}
|
||||
|
||||
#lockscreen-camera {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#lockscreen-camera > iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#lockscreen.no-transition {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#lockscreen-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#lockscreen .lockscreen-panel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
visibility: hidden;
|
||||
transition: visibility var(--panel-transition-secs) ease, transform var(--panel-transition-secs) ease;
|
||||
}
|
||||
|
||||
.lockscreen-background::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because of the individual background element, the slide would appear
|
||||
* after the passcode pad got invoked, so we need to hide it here.
|
||||
*/
|
||||
[data-panel="passcode"] #notifications-lockscreen-container,
|
||||
[data-panel="passcode"] #lockscreen-notification-arrow,
|
||||
[data-panel="passcode"] #lockscreen-media-container,
|
||||
[data-panel="passcode"] #lockscreen-icon-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
[data-panel="main"] #lockscreen-panel-main,
|
||||
[data-panel="passcode"] #lockscreen-panel-main,
|
||||
[data-panel="emergency-call"] #lockscreen-panel-emergency-call {
|
||||
visibility: inherit;
|
||||
}
|
||||
|
||||
[data-panel="main"] #lockscreen-alt-camera {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
[data-panel="passcode"] #lockscreen-alt-camera.lockscreen-icon {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
#lockscreen-header {
|
||||
z-index: 9999;
|
||||
position: absolute;
|
||||
top: 3rem; /* 3rem = height of status bar */
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 1.5rem 2.4rem 0;
|
||||
color: #fff;
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-panel="main"] #lockscreen-header {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
[data-panel="camera"] #lockscreen-header,
|
||||
[data-panel="emergency-call"] #lockscreen-header {
|
||||
transform: translateY(0);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#lockscreen-conn-states {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
font-weight: 300;
|
||||
font-size: 1.4rem;
|
||||
padding-bottom: 1.4rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#lockscreen-conn-states span:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#lockscreen-conn-states span:first-child {
|
||||
display: inline-block;
|
||||
margin-inline-end: 0.8rem;
|
||||
}
|
||||
|
||||
#lockscreen-conn-states span:last-child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* For some reason display: inline-block disregards hidden attribute */
|
||||
#lockscreen-conn-states span[hidden],
|
||||
#lockscreen-conn-states[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#lockscreen-clock-time {
|
||||
line-height: 0.9;
|
||||
margin: -1.4rem 0 0;
|
||||
margin-inline-start: -0.4rem;
|
||||
font-weight: 200;
|
||||
font-size: 8.5rem;
|
||||
}
|
||||
|
||||
#lockscreen-date {
|
||||
display: inline-block;
|
||||
}
|
||||
#lockscreen-date, #lockscreen-alarm {
|
||||
font-weight: 300;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
#lockscreen-alarm::after {
|
||||
content: ' ';
|
||||
top: 3px;
|
||||
background-image: url('./images/alarm_icon.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5rem;
|
||||
width: 15px;
|
||||
display: inline-block;
|
||||
height: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.has-alarm {
|
||||
display: inline-block;
|
||||
}
|
||||
.no-alarms {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#lockscreen-clock-time, #lockscreen-date, #lockscreen-alarm {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#lockscreen-charging {
|
||||
font-weight: 300;
|
||||
font-size: 1.2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#lockscreen-area {
|
||||
position: absolute;
|
||||
height: 8.6rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
#lockscreen .lockscreen-icon-area {
|
||||
height: 10rem;
|
||||
position: absolute;
|
||||
border-style: none;
|
||||
transition: opacity var(--panel-transition-secs) ease;
|
||||
}
|
||||
|
||||
#lockscreen .lockscreen-icon {
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#lockscreen-area-slide {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#lockscreen .touched .lockscreen-icon {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#lockscreen .lockscreen-icon-right {
|
||||
right: calc(50% - 14rem);
|
||||
}
|
||||
|
||||
#lockscreen .lockscreen-icon-left {
|
||||
left: calc(50% - 14rem);
|
||||
}
|
||||
|
||||
@media (min-width:768px) {
|
||||
#lockscreen .lockscreen-icon-right {
|
||||
right: calc(50% - 20.5rem);
|
||||
}
|
||||
|
||||
#lockscreen .lockscreen-icon-left {
|
||||
left: calc(50% - 20.5rem);
|
||||
}
|
||||
|
||||
#lockscreen-header {
|
||||
padding-inline-start: 6rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
#lockscreen-date {
|
||||
padding-top: 0.5rem;
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
button::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
:-moz-any(.touched, #screen:not(.locked), #screen.attention, #lockscreen:not([data-panel="main"]))
|
||||
:-moz-any(#lockscreen-left-arrow, #lockscreen-right-arrow) {
|
||||
display: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
#lockscreen-area-unlock > div {
|
||||
background-image: url('./images/lockscreen_unlock.png');
|
||||
background-position: 1.1rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 3rem;
|
||||
margin-top: 1.9rem;
|
||||
}
|
||||
|
||||
#lockscreen .lockscreen-icon-left, .lockscreen-icon-right {
|
||||
z-index: 16;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Compensation for the effects after enabling the pointer-event attribute. */
|
||||
#lockscreen .lockscreen-icon-left:active, .lockscreen-icon-right:active {
|
||||
background: none;
|
||||
}
|
||||
|
||||
#lockscreen-area-camera > div {
|
||||
background-image: url('./images/lockscreen_camera.png');
|
||||
background-position: 1.85rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 3rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
#lockscreen-alt-camera.lockscreen-icon {
|
||||
border-radius: 2.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
box-sizing: border-box;
|
||||
pointer-events: auto;
|
||||
opacity: 0.1;
|
||||
transition: opacity var(--panel-transition-secs) ease;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
margin-top: 5rem;
|
||||
}
|
||||
#lockscreen-alt-camera.lockscreen-icon:-moz-dir(ltr) { float: right; }
|
||||
#lockscreen-alt-camera.lockscreen-icon:-moz-dir(rtl) { float: left; }
|
||||
|
||||
#lockscreen-alt-camera-button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
pointer-events: auto;
|
||||
background-image: url('./images/pin_camera.png');
|
||||
background-position: 1rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 3rem;
|
||||
}
|
||||
|
||||
#lockscreen-panel-emergency-call {
|
||||
/* > camera, unlock icons > canvas */
|
||||
z-index: 32;
|
||||
}
|
||||
|
||||
#lockscreen-panel-emergency-call {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
#lockscreen-panel-emergency-call::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
#lockscreen-panel-emergency-call > iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
[data-panel="emergency-call"] #lockscreen-panel-emergency-call {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#lockscreen-area-handle {
|
||||
position: absolute;
|
||||
background-image: url('./images/handle.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 2rem;
|
||||
border-top: 0.1rem solid transparent;
|
||||
top: -2rem;
|
||||
height: 0.9rem;
|
||||
width: 100%;
|
||||
transition:
|
||||
transform var(--panel-transition-secs) ease,
|
||||
opacity var(--panel-transition-secs) ease;
|
||||
}
|
||||
|
||||
#lockscreen-icon-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 10rem;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#lockscreen .touched #lockscreen-icon-container {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#lockscreen .touched .lockscreen-icon-area {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#lockscreen-canvas-wrapper {
|
||||
position: absolute;
|
||||
bottom: 0.75rem;
|
||||
z-index: 8;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#lockscreen-message {
|
||||
position: relative;
|
||||
top: 18rem;
|
||||
offset-inline-start: 0;
|
||||
word-wrap: break-word;
|
||||
text-align: center;
|
||||
overflow-y: auto;
|
||||
line-height: 1.4rem;
|
||||
z-index: 16;
|
||||
margin: auto;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid #e6e6e6;
|
||||
padding: 5px;
|
||||
width: 27rem;
|
||||
min-height: 5rem;
|
||||
max-height: 10rem;
|
||||
background-color: #fafafa;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
color: black;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<mask id="solid_mask_top" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
|
||||
<rect x="0" y="0.04" width="1" height="0.96" fill="white" />
|
||||
</mask>
|
||||
<mask id="fade_mask_both" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
|
||||
<linearGradient id="fade_gradient_both" gradientUnits="objectBoundingBox" x2="0" y2="1">
|
||||
<stop stop-color="white" stop-opacity="0" offset="0"></stop>
|
||||
<stop stop-color="white" stop-opacity="1" offset="0.2"></stop>
|
||||
<stop stop-color="white" stop-opacity="1" offset="0.8"></stop>
|
||||
<stop stop-color="white" stop-opacity="0" offset="1"></stop>
|
||||
</linearGradient>
|
||||
<rect x="0" y="0" width="1" height="1" fill="url(#fade_gradient_both)"></rect>
|
||||
</mask>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 809 B |
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* BiDi note: because this stylesheet is 'scoped', instead of:
|
||||
* html[dir="rtl"] #volume { ... }
|
||||
* we must use:
|
||||
* #volume:-moz-dir(rtl) { ... }
|
||||
*/
|
||||
|
||||
#lockscreen .fake-notification {
|
||||
height: 6rem;
|
||||
padding: 0 1.5rem;
|
||||
font: 1.8rem/1.8rem auto;
|
||||
color: #e7e7e7;
|
||||
overflow: hidden;
|
||||
|
||||
border-bottom: .1rem solid rgba(255, 255, 255, .05);
|
||||
}
|
||||
|
||||
#lockscreen .fake-notification [data-icon] {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
#lockscreen:-moz-dir(ltr) .fake-notification [data-icon] { float: left; }
|
||||
#lockscreen:-moz-dir(rtl) .fake-notification [data-icon] { float: right; }
|
||||
|
||||
/* alert icon: white on red background */
|
||||
#lockscreen [data-icon].alert {
|
||||
color: #fff;
|
||||
background-color: #b90000;
|
||||
border-radius: 50%;
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
margin: 1.8rem 0;
|
||||
}
|
||||
|
||||
#lockscreen [data-icon].alert:before {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
font-size: 2rem; /* Slightly decreasing font-size to get the icon to fit in the circle */
|
||||
margin-top: -0.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#lockscreen .fake-notification .title-container {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#lockscreen .fake-notification .detail {
|
||||
color: #858585;
|
||||
font-size: 1.4rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
html {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#lockscreen-frame.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#lockscreen-inputpad-frame.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
/**
|
||||
* BiDi note: numeric passcodes are always displayed in LTR.
|
||||
* This stylesheet must *NOT* be mirrored for RTL.
|
||||
*/
|
||||
|
||||
#lockscreen-panel-passcode {
|
||||
/* > camera, unlock icons > canvas */
|
||||
z-index: 32;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-code {
|
||||
position: absolute;
|
||||
bottom: 20.4rem;
|
||||
height: 7rem;
|
||||
width: 100%;
|
||||
background-color: rgba(51, 51, 51, 0.3);
|
||||
margin: 0;
|
||||
padding: 0 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-code > span {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
float: left; /* BiDi-proof: passcodes are always LTR like all numbers */
|
||||
width: calc(25% - 1rem);
|
||||
margin: 1.5rem 0.5rem;
|
||||
height: calc(100% - 3rem);
|
||||
text-align: center;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 0.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-code.error > span {
|
||||
border: 0.1rem #B70404 solid;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-code.error > span[data-dot]::before {
|
||||
background-color: #B70404;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-code > span[data-dot]::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
background-color: #3e3b39;
|
||||
border-radius: 0.75rem;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -0.75rem;
|
||||
margin-top: -0.75rem;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 20.4rem;
|
||||
width: 100%;
|
||||
background-color: #606b6e;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
float: left; /* BiDi-proof: passcodes are always LTR like all numbers */
|
||||
width: 33.333%;
|
||||
height: 5.1rem;
|
||||
outline: none;
|
||||
padding: 0; /* 2rem; */
|
||||
font-size: 3.2rem;
|
||||
font-weight: 400;
|
||||
line-height: 4rem;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a > div {
|
||||
height: calc(100% - 0.8rem);
|
||||
margin-top: 0.4rem;
|
||||
border-right-width: 0.1rem;
|
||||
border-right-style: solid;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a:not(.lockscreen-passcode-pad-func) > div{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a[data-key="3"] > div,
|
||||
#lockscreen-passcode-pad > a[data-key="6"] > div,
|
||||
#lockscreen-passcode-pad > a[data-key="9"] > div,
|
||||
#lockscreen-passcode-pad > a[data-key="c"] > div,
|
||||
#lockscreen-passcode-pad > a[data-key="b"] > div{
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a[data-key="b"] > div {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
color: #abb0b1;
|
||||
font-family: 'Keyboard Symbols', sans-serif;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.last-row > div {
|
||||
padding-top: 0.4rem;
|
||||
height: calc(100% - 1.2rem);
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.last-row[data-key="e"] > div {
|
||||
height: calc(100% - 1rem);
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.last-row:not([data-key="0"]) > div {
|
||||
padding-top: 0.2rem;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a > div > span {
|
||||
pointer-events: none;
|
||||
font-size: 1.2rem;
|
||||
padding: 0.4rem;
|
||||
font-weight: 400;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad.passcode-entered > a[data-key="c"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad.passcode-entered > a[data-key="b"] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* if not fufilled, show it is pressed */
|
||||
#lockscreen-passcode-pad:not(.passcode-fulfilled) > a.active-key {
|
||||
background-color: #4a5255;
|
||||
color: #00caf2;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad:not(.passcode-fulfilled) > a.lockscreen-passcode-pad-func.active-key > div > span{
|
||||
color: #00caf2;
|
||||
}
|
||||
|
||||
/* if it's fulfilled, don't change the background */
|
||||
#lockscreen-passcode-pad.passcode-fulfilled .row0 {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad.passcode-fulfilled .row1,
|
||||
#lockscreen-passcode-pad .row1 {
|
||||
background-color: #5b6668;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad.passcode-fulfilled .row2,
|
||||
#lockscreen-passcode-pad .row2 {
|
||||
background-color: #566061;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad.passcode-fulfilled .row3,
|
||||
#lockscreen-passcode-pad .row3 {
|
||||
background-color: #525c5e;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad .row0 > div{
|
||||
border-right-color: #525b5e;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad .row1 > div {
|
||||
border-right-color: #4c5657;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad .row2 > div {
|
||||
border-right-color: #475052;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad .row3 > div {
|
||||
border-right-color: #434b4d;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.row0 > div,
|
||||
#lockscreen-passcode-pad > a.row1 > div,
|
||||
#lockscreen-passcode-pad > a.row2 > div {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.row0 > div > span,
|
||||
#lockscreen-passcode-pad > a.row1 > div > span,
|
||||
#lockscreen-passcode-pad > a.row2 > div > span {
|
||||
display: block;
|
||||
margin-top: -2.6rem;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad:not(.passcode-fulfilled) > a.active-key > div > span {
|
||||
color: transparent;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.lockscreen-passcode-pad-func > div{
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.lockscreen-passcode-pad-func:after {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
content: '';
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.lockscreen-passcode-pad-func > div > span {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.6rem;
|
||||
text-transform: uppercase;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.lockscreen-passcode-pad-func[data-key="e"] > div > span,
|
||||
#lockscreen-passcode-pad > a.lockscreen-passcode-pad-func[data-key="c"] > div > span {
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.4rem;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#lockscreen-passcode-pad > a.disabled > div > span {
|
||||
color: #4d4d4d;
|
||||
}
|
||||
|
||||
|
||||
#lockscreen-inputpad-frame .lockscreen-panel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
/* while other components on LockScreen should be touchable,
|
||||
* this one would capture the event. */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* BiDi note: media controls are always displayed in LTR (hardware legacy).
|
||||
* This stylesheet must *NOT* be mirrored for RTL.
|
||||
*/
|
||||
|
||||
#lockscreen .media-playback-container {
|
||||
height: auto;
|
||||
padding: 0 1.8rem;
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-container.disabled {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-nowplaying {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
height: 2.4rem;
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-nowplaying > .track {
|
||||
display: table-cell;
|
||||
vertical-align: bottom;
|
||||
padding: 0 1.5rem;
|
||||
font: italic 1.4rem auto;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-controls {
|
||||
height: 5.6rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
font-size: 0; /* ensure there's no space between the buttons */
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-controls > [data-icon] {
|
||||
width: calc(100% / 3);
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: #e7e7e7;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-controls > [data-icon]:before {
|
||||
font-size: 4rem;
|
||||
line-height: 5.6rem;
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-controls > .previous {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-controls > .next {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#lockscreen .media-playback-controls > div {
|
||||
/* Delay transition post tap by some milliseconds. */
|
||||
transition: 0s var(--transition-duration);
|
||||
}
|
||||
|
||||
[role="dialog"] #lockscreen .media-playback-controls > div:active,
|
||||
#lockscreen .media-playback-controls > div:active {
|
||||
background-color: transparent;
|
||||
color: #0ac;
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
/* Lock screen music player */
|
||||
#lockscreen-media-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 5rem;
|
||||
background: rgba(51, 51, 51, 0.3);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* BiDi note: player controls do not change in RTL, for hardware legacy reason:
|
||||
* Forcing float: left
|
||||
*/
|
||||
#lockscreen #lockscreen-media-container .fake-notification [data-icon] {
|
||||
float: left;
|
||||
}
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
/**
|
||||
* BiDi note: because this stylesheet is 'scoped', instead of:
|
||||
* html[dir="rtl"] #volume { ... }
|
||||
* we must use:
|
||||
* #volume:-moz-dir(rtl) { ... }
|
||||
*/
|
||||
|
||||
/* Individual style; to avoid utility tray patches to break lockscreen notitifications. */
|
||||
#notifications-lockscreen-container .notification {
|
||||
position: relative;
|
||||
height: 6rem;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.9rem;
|
||||
border-top: 0.1rem solid transparent;
|
||||
border-bottom: 0.1rem solid transparent;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification:first-child {
|
||||
border-top-color: black;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification div {
|
||||
pointer-events: none;
|
||||
}
|
||||
:-moz-dir(ltr) #notifications-lockscreen-container .notification div {
|
||||
text-align: left;
|
||||
}
|
||||
:-moz-dir(rtl) #notifications-lockscreen-container .notification div {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > div.title-container {
|
||||
margin-top: 1rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > div {
|
||||
width: calc(100% - 6.5rem);
|
||||
margin-inline-start: 5rem;
|
||||
color: #bfbfbf;
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.9rem;
|
||||
min-height: 1.9rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > img {
|
||||
display: block;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin: 1.5rem 1rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
:-moz-dir(ltr) #notifications-lockscreen-container .notification > img { float: left; }
|
||||
:-moz-dir(rtl) #notifications-lockscreen-container .notification > img { float: right; }
|
||||
|
||||
#notifications-lockscreen-container .notification > div.title-container .title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-inline-end: .5rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification.actionable {
|
||||
/* Can't set border as none because it affects layout,
|
||||
* so we do this to hide them */
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification.actionable > div {
|
||||
/* 18rem is the magic number to make sure it get truncated before the actionable button */
|
||||
/* ideally it should be */
|
||||
/* width: calc(100% - var(--padleft-button) - var(--width-actionable-button)); */
|
||||
/* but it would fail and make the content too short. */
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification.actionable > div:first-of-type {
|
||||
width: 18.5rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification.actionable .timestamp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: -1.1rem;
|
||||
offset-inline-start: 0;
|
||||
height: 6rem;
|
||||
background-color: rgba(51, 51, 51, 0.3);
|
||||
z-index: -1; /* make this as 'background' */
|
||||
opacity: 0.0;
|
||||
transition: opacity .25s ease-out;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification.actionable::before {
|
||||
opacity: 1.0;
|
||||
transition: opacity .25s ease-out;
|
||||
}
|
||||
|
||||
/* for the border */
|
||||
#notifications-lockscreen-container .notification::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: calc(100% - 4.8rem);
|
||||
top: -1.1rem;
|
||||
offset-inline-start: 0;
|
||||
height: 6rem;
|
||||
z-index: -1; /* make this as 'background' */
|
||||
border-top: none;
|
||||
border-bottom: 0.1rem solid rgba(255, 255, 255, 0.3);
|
||||
margin-inline-start: 2.4rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container > div:last-child::after {
|
||||
border-bottom: 0.1rem solid rgba(255, 255, 255, 0.0);
|
||||
}
|
||||
|
||||
/* since CSS don't have 'previous' selector */
|
||||
#notifications-lockscreen-container .notification.actionable::after,
|
||||
#notifications-lockscreen-container .notification.previous-actionable::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .button-actionable {
|
||||
border-inline-start: 0.1rem rgba(255, 255, 255, 0.15) solid;
|
||||
color: rgba(0, 202, 242, 1);
|
||||
display: inline-block;
|
||||
|
||||
|
||||
font-size: var(--button-label-size);
|
||||
font-family: "Fira Sans";
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
position: absolute;
|
||||
|
||||
/* from the border line to the text */
|
||||
padding-inline-start: var(--padleft-button);
|
||||
|
||||
/* 0.3rem is the magic number to make it (with the border) vertical-align: middle*/
|
||||
top: 0.3rem;
|
||||
|
||||
/* 100% - the actionable area + the margin of the actionable area */
|
||||
/* The 0.8rem is for alignment of the position of previous timestamp */
|
||||
/* The 0.3rem is for centering the vertical bar of the button */
|
||||
offset-inline-start: calc(100% - var(--width-actionable-button) + 1.4rem);
|
||||
height: 3.5rem;
|
||||
line-height: 3.3rem; /* to make the lable vertical-align: middle */
|
||||
transition-property: color, text;
|
||||
transition-duration: 0.25s;
|
||||
transition-timing-function: linear, ease-in;
|
||||
}
|
||||
|
||||
/* When the notification be touched and the button has been tapped */
|
||||
#notifications-lockscreen-container .notification.actionable.activated .button-actionable,
|
||||
#notifications-lockscreen-container .button-actionable.activated {
|
||||
color: rgba(0, 202, 242, 0.2);
|
||||
}
|
||||
|
||||
/* notifications */
|
||||
|
||||
/* in "normal" mode (there is no widget)
|
||||
* desired vertical position is centered in the space
|
||||
* between date and slider
|
||||
* for big screen, we allow four notifications,
|
||||
* for small screen, we allow three notification
|
||||
* that would be (for four notifications)
|
||||
* (100% - 16.3rem (header) - 3rem (statusbar)
|
||||
* - 10rem (slider) - 24.6rem (4xNotifications height)) / 2
|
||||
* + 16.3rem + 3rem
|
||||
*/
|
||||
|
||||
#notifications-lockscreen-container {
|
||||
--width-actionable-button: 8.3rem;
|
||||
--padleft-button: 1.1rem;
|
||||
--button-label-size: 1.4rem;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: calc(50% - 7.65rem);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 24.6rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* in collapsed mode (there is a widget)
|
||||
* desired vertical position is manually adjusted for big screen,
|
||||
* and for big screen we allow three notifications
|
||||
* for small screen, we allow two notification; they are
|
||||
* vertically centered between date and music player widget
|
||||
*/
|
||||
#notifications-lockscreen-container.collapsed {
|
||||
top: calc(50% - 8.7rem);
|
||||
height: 17.7rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container.masked-both {
|
||||
mask: url('lockscreen_container_mask.svg#fade_mask_both');
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container.masked-top {
|
||||
mask: url('lockscreen_container_mask.svg#solid_mask_top');
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container.masked-top.top-actionable {
|
||||
mask: none;
|
||||
}
|
||||
|
||||
@media (max-height:480px) {
|
||||
/* small screen, normal mode: for three notifications...
|
||||
* (100% - 16.3rem (header) - 3rem (statusbar)
|
||||
* - 10rem (slider) - 18.8rem (3xNotifications height)) / 2
|
||||
* + 16.3rem + 3rem
|
||||
*/
|
||||
|
||||
#notifications-lockscreen-container {
|
||||
top: calc(50% - 4.7rem);
|
||||
height: 18.8rem;
|
||||
}
|
||||
|
||||
/* small screen, collapsed mode: for one notification...
|
||||
* manally adjusted according to UX; 1x Notification is 5.8rem
|
||||
* UX specifies in bug 1023500 that the container should not be scrollable
|
||||
* (so there is no mask either)
|
||||
*
|
||||
* Bug 1115921 specifies that in this condition, the notification should
|
||||
* be vertically centered, thus 2.5rem which puts it in the middle
|
||||
*/
|
||||
#notifications-lockscreen-container.collapsed {
|
||||
top: calc(50% - 2.5rem);
|
||||
height: 5.8rem;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container.collapsed.masked-top,
|
||||
#notifications-lockscreen-container.collapsed.masked-both {
|
||||
mask: none;
|
||||
}
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification {
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
border-top: none;
|
||||
/* delegate border to :after element while keeping layout correct */
|
||||
border-bottom: 0.1rem solid rgba(0, 0, 0, 0);
|
||||
height: 4.8rem;
|
||||
margin: 0;
|
||||
padding-inline-start: 2.4rem;
|
||||
padding-inline-end: 2.4rem;
|
||||
width: calc(100% - 4.8rem);
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container.collapsed .notification:last-child {
|
||||
margin-bottom: 0.05rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > div {
|
||||
width: calc(100% - 3.6rem);
|
||||
margin-inline-start: 3.6rem;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > .title-container div:first-of-type {
|
||||
width: calc(100% - 9.4rem);
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > div.detail {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > div.detail .detail-content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > .title-container .timestamp {
|
||||
margin-top: 0.1rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
|
||||
flex: initial;
|
||||
top: -.2rem;
|
||||
offset-inline-end: 1.5rem;
|
||||
padding: 0;
|
||||
max-width: 6.5rem;
|
||||
font-size: 1.2rem;
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
line-height: 1.6rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#notifications-lockscreen-container .notification > img {
|
||||
margin-top: 0.8rem;
|
||||
margin-inline-start: 0;
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
}
|
||||
|
||||
#lockscreen-notification-arrow {
|
||||
width: calc(100% - 5.2rem);
|
||||
height: 1.6rem;
|
||||
background-image: url('./images/lockscreen_notification_arrow.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: calc(50% + 0.25rem) center;
|
||||
/* hide it while we adjust no layout */
|
||||
border-top: 0.1rem solid rgba(255, 255, 255, 0.0);
|
||||
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
background-size: 1.6rem 1.6rem;
|
||||
margin: 0 2.4rem;
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-duration) ease;
|
||||
}
|
||||
:-moz-dir(ltr) #lockscreen-notification-arrow { left: 0; }
|
||||
:-moz-dir(rtl) #lockscreen-notification-arrow { right: 0; }
|
||||
|
||||
#lockscreen-notification-arrow.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* please see how we calculate top and max-height of
|
||||
* #notifications-lockscreen-container to place the arrow's top.
|
||||
* the last offset in calc is to make sure hairlines align together,
|
||||
* and is empirical values.
|
||||
*/
|
||||
#lockscreen-notification-arrow {
|
||||
top: calc(50% - 7.65rem + 24.6rem - 1.2rem);
|
||||
}
|
||||
|
||||
#lockscreen-notification-arrow.collapsed {
|
||||
/* manually adjusted */
|
||||
top: calc(50% - 8.7rem + 17.7rem - 0.05rem);
|
||||
}
|
||||
|
||||
@media (max-height:480px) {
|
||||
#lockscreen-notification-arrow {
|
||||
top: calc(50% - 4.7rem + 18.8rem - 1.1rem);
|
||||
}
|
||||
|
||||
#lockscreen-notification-arrow.collapsed {
|
||||
/* UX specifies in bug 1023500 that the notifications
|
||||
container should not be scrollable with collapsed */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||