VulpesOS/gaia/apps/clock/js/timer.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

294 lines
7.7 KiB
JavaScript
Executable file

define(function(require) {
'use strict';
var asyncStorage = require('shared/js/async_storage');
var Utils = require('utils');
var timerPrivate = new WeakMap();
/**
* Timer
*
* Create new or revive existing timer objects.
*
* @param {Object} opts Optional timer object to create or revive
* a new or existing timer object.
* - startTime, number time in ms.
* - duration, time to count from `start`.
* - configuredDuration, time requested by user.
* - sound, string sound name.
* - vibrate, boolean, vibrate or not.
* - id, integer, mozAlarm API id number.
*/
function Timer(opts) {
opts = opts || {};
var now = Date.now();
if (opts.id !== undefined) {
delete opts.id;
}
// private properties
timerPrivate.set(this, Utils.extend({
state: Timer.INITIAL
}, extractProtected(opts)));
// public properties
Utils.extend(this, {
startTime: now,
duration: null,
configuredDuration: null,
sound: 'ac_awake.opus',
vibrate: true
}, opts);
}
Timer.prototype.constructor = Timer;
/**
* request - get the persisted Timer object.
*
* @param {function} [callback] - called with (err, timer_raw).
*/
Timer.getFromStorage = function(callback) {
asyncStorage.getItem('active_timer', function(timer) {
if (timer) {
// Normalize the timer data. Pre-April-2014 code may have stored
// 'vibrate' and 'sound' as the string "0".
timer.sound = (timer.sound !== '0' ? timer.sound : null);
timer.vibrate = (timer.vibrate && timer.vibrate !== '0');
}
callback && callback(timer || null);
});
};
/**
* singleton - get the unique persisted Timer object.
*
* @param {function} [callback] - called with (err, timer).
*/
var timerSingleton = Utils.singleton(Timer);
Timer.singleton = function tm_singleton(callback) {
Timer.getFromStorage(function(err, obj) {
var ts = timerSingleton(obj);
callback && callback(null, ts);
});
};
function extractProtected(config) {
var ret = {};
var protectedProperties = new Set(['state']);
for (var i in config) {
if (protectedProperties.has(i)) {
ret[i] = config[i];
delete config[i];
}
}
return ret;
}
/**
* toSerializable - convert `this` to a serialized format.
*
* @return {object} - object representation of this Timer.
*/
Timer.prototype.toSerializable = function timerToSerializable() {
var timer = Utils.extend({}, this, timerPrivate.get(this));
// Normalize the data. TODO: Perform this normalization immediately
// at the getter/setter level when this class is refactored.
return {
startTime: timer.startTime,
duration: timer.duration,
configuredDuration: timer.configuredDuration,
sound: (timer.sound !== '0' ? timer.sound : null),
vibrate: (timer.vibrate !== '0' ? timer.vibrate : null),
state: timer.state
};
};
/**
* save - Save the timer to the database.
*
* @param {function} [callback] - callback to call after the timer
* has been saved.
*/
Timer.prototype.save = function timerSave(callback) {
asyncStorage.setItem('active_timer', this.toSerializable(), function() {
callback && callback(null, this);
}.bind(this));
};
/**
* register - Register the timer with mozAlarm API.
*
* @param {function} [callback] - callback to call after the timer
* has been registered.
*/
Timer.prototype.register = function timerRegister(callback) {
var data = {
type: 'timer'
};
var request;
// Remove previously-created mozAlarm for this alarm, if necessary.
this.unregister();
request = navigator.mozAlarms.add(
new Date(Date.now() + this.remaining), 'ignoreTimezone', data
);
request.onsuccess = (function(ev) {
this.id = ev.target.result;
callback && callback(null, this);
}.bind(this));
request.onerror = function(ev) {
callback && callback(ev.target.error);
};
};
/**
* commit - save and register the timer as necessary.
*
* @param {function} [callback] - callback to call after the timer
* has been registered.
*/
Timer.prototype.commit = function timerCommit(callback) {
var saveSelf = this.save.bind(this, callback);
if (this.state === Timer.STARTED) {
this.register(saveSelf);
} else {
this.unregister();
saveSelf();
}
};
Timer.prototype.unregister = function timerUnregister() {
if (typeof this.id === 'number') {
navigator.mozAlarms.remove(this.id);
}
};
Object.defineProperty(Timer.prototype, 'remaining', {
get: function() {
if (this.state === Timer.INITIAL) {
return this.configuredDuration;
} else if (this.state === Timer.PAUSED) {
return this.duration;
} else if (this.state === Timer.STARTED) {
if (typeof this.startTime === 'undefined' ||
typeof this.duration === 'undefined') {
return 0;
}
var r = (this.startTime + this.duration) - Date.now();
return r >= 0 ? r : 0;
}
}
});
Object.defineProperty(Timer.prototype, 'state', {
get: function() {
var priv = timerPrivate.get(this);
return priv.state;
}
});
Timer.prototype.start = function timerStart() {
if (this.state !== Timer.STARTED) {
var priv = timerPrivate.get(this);
priv.state = Timer.STARTED;
this.startTime = Date.now();
this.duration = (typeof this.duration === 'number') ? this.duration :
this.configuredDuration;
window.dispatchEvent(new CustomEvent('timer-start', {
detail: { remaining: this.remaining }
}));
this._tick();
}
};
Timer.prototype.pause = function timerPause() {
if (this.state === Timer.STARTED) {
this.duration = this.remaining; // remaining getter observes private state
var priv = timerPrivate.get(this);
priv.state = Timer.PAUSED;
this.startTime = null;
this._cancelTick();
window.dispatchEvent(new CustomEvent('timer-pause', {
detail: { remaining: this.remaining }
}));
}
};
Timer.prototype.cancel = function timerReset() {
if (this.state !== Timer.INITIAL) {
var priv = timerPrivate.get(this);
priv.state = Timer.INITIAL;
this.startTime = null;
this.duration = this.configuredDuration;
this._cancelTick();
this._dispatchEnd();
}
};
/**
* plus Increase the duration and extend the endAt time
*
* @param {Number} seconds The time in seconds to add.
*
* @return {Timer} Timer instance.
*/
Timer.prototype.plus = function timerPlus(seconds) {
// Convert to ms
var ms = seconds * 1000;
this.duration += ms;
return this;
};
Timer.prototype._dispatchEnd = function timerDispatchEnd() {
// cancel and end are basically the same thing
window.dispatchEvent(new CustomEvent('timer-end', {
detail: { remaining: 0 }
}));
};
Timer.prototype._tick = function timerTick() {
if (this.state !== Timer.STARTED) {
return;
}
// we cache the previous value posted to avoid triggering multiple "tick"
// messages for the same second
var remaining = this.remaining;
var current = Math.round(remaining / 1000);
if (current !== this._previousTick) {
this._previousTick = current;
window.dispatchEvent(new CustomEvent('timer-tick', {
detail: { remaining: remaining }
}));
}
if (remaining > 0) {
// use setTimeout instead of requestAnimationFrame because window will
// probably be hidden when handling connections
this._tickTimeout = window.setTimeout(() => this._tick(), 100);
} else {
this._dispatchEnd();
}
};
Timer.prototype._cancelTick = function timerCancelTick() {
window.clearTimeout(this._tickTimeout);
};
/**
* Static "const" Timer states.
*/
Object.defineProperties(Timer, {
INITIAL: { value: 0 },
STARTED: { value: 1 },
PAUSED: { value: 2 }
});
return Timer;
});