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.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

523
gaia/apps/camera/js/app.js Executable file
View file

@ -0,0 +1,523 @@
define(function(require, exports, module) {
'use strict';
/**
* Dependencies
*/
var stopRecordingEvent = require('stop-recording-event');
var NotificationView = require('views/notification');
var LoadingView = require('views/loading-screen');
var orientation = require('lib/orientation');
var bindAll = require('lib/bind-all');
var debug = require('debug')('app');
var Pinch = require('lib/pinch');
var bind = require('lib/bind');
var model = require('model');
/**
* Exports
*/
module.exports = App;
/**
* Mixin `Model` API
*/
model(App.prototype);
/**
* Initialize a new `App`
*
* Options:
*
* - `root` The node to inject content into
*
* @param {Object} options
* @constructor
*/
function App(options) {
debug('initialize');
bindAll(this);
this.views = {};
this.dynamicLazy = {};
this.el = options.el;
this.win = options.win;
this.doc = options.doc;
this.perf = options.perf || {};
this.pinch = options.pinch || new Pinch(this.el); // Test hook
this.require = options.require || window.requirejs; // Test hook
this.LoadingView = options.LoadingView || LoadingView; // test hook
this.orientation = options.orientation || orientation; // test hook
this.inSecureMode = (this.win.location.hash === '#secure');
this.controllers = options.controllers;
this.geolocation = options.geolocation;
this.settings = options.settings;
this.camera = options.camera;
this.activity = {};
this.sounds = options.sounds;
debug('initialized');
}
/**
* Runs all the methods to boot the app.
*
* The loading screen is shown until the
* camera is 'ready', then it is taken down.
*
* @public
*/
App.prototype.boot = function() {
debug('boot');
if (this.booted) { return; }
this.showSpinner('requestingCamera');
this.bindEvents();
this.initializeViews();
this.runControllers();
// PERFORMANCE MARKER (1): navigationLoaded
// Designates that the app's *core* chrome or navigation interface
// exists in the DOM and is marked as ready to be displayed.
// PERFORMANCE MARKER (2): navigationInteractive
// Designates that the app's *core* chrome or navigation interface
// has its events bound and is ready for user interaction.
window.performance.mark('navigationLoaded');
window.performance.mark('navigationInteractive');
this.injectViews();
this.booted = true;
debug('booted');
};
App.prototype.dispatchEvent = function(name) {
this.win.dispatchEvent(new CustomEvent(name));
};
/**
* Runs controllers to glue all
* the parts of the app together.
*
* @private
*/
App.prototype.runControllers = function() {
debug('run controllers');
this.controllers.overlay(this);
this.controllers.battery(this);
this.controllers.settings(this);
this.controllers.activity(this);
this.controllers.camera(this);
this.controllers.viewfinder(this);
this.controllers.hud(this);
this.controllers.controls(this);
this.controllers.storage(this);
debug('controllers run');
};
/**
* Load an extra lazy controller. Used for
* controllers only needed based on configuration.
*
* @param {String} controller
*/
App.prototype.loadLazyController = function(controller) {
if (!this.criticalPathDone) {
this.controllers.lazy.push(controller);
return;
}
if (!this.dynamicLazy[controller]) {
this.dynamicLazy[controller] = this.loadLazyControllers([controller]);
}
return this.dynamicLazy[controller];
};
/**
* Load and run all the lazy controllers.
*
* @param {Function} done
*/
App.prototype.loadLazyControllers = function(controllers) {
debug('load lazy controllers');
var self = this;
return new Promise(function(resolve, reject) {
self.require(controllers, function() {
[].forEach.call(arguments, function(controller) { controller(self); });
debug('controllers loaded');
self.emit('lazyloaded');
resolve();
});
});
};
/**
* Initialize views.
*
* @private
*/
App.prototype.initializeViews = function() {
debug('initializing views');
this.views.notification = new NotificationView();
debug('views initialized');
};
/**
* Put views in the DOM.
*
* @private
*/
App.prototype.injectViews = function() {
debug('injecting views');
this.views.notification.appendTo(this.el);
debug('views injected');
};
/**
* Attaches event handlers.
*
* @private
*/
App.prototype.bindEvents = function() {
debug('binding events');
// App
this.once('storage:checked:healthy', this.geolocationWatch);
this.on('storage:checked', function() { this.storageChecked = true; }.bind(this));
this.once('viewfinder:visible', this.onCriticalPathDone);
this.once('camera:error', this.onCriticalPathDone);
this.once('activity', this.onActivity);
this.once('newthumbnail', this.onNewThumbnail);
this.once('preview', this.onPreview);
this.once('camera:willchange', this.onWillChange);
this.on('camera:willchange', this.firer('busy'));
this.on('ready', this.clearSpinner);
this.on('visible', this.onVisible);
this.on('hidden', this.onHidden);
this.on('reboot', this.onReboot);
this.on('busy', this.onBusy);
// Settings
if (this.settings.countdown.selected('key') !== 'off') {
this.onCountdown();
} else {
this.settings.countdown.once('change:selected', this.onCountdown);
}
// Pinch
this.pinch.on('changed', this.firer('pinch:changed'));
this.on('previewgallery:opened', this.pinch.disable);
this.on('previewgallery:closed', this.pinch.enable);
this.on('settings:opened', this.pinch.disable);
this.on('settings:closed', this.pinch.enable);
// DOM
bind(this.doc, 'visibilitychange', this.onVisibilityChange);
// we bind to window.onlocalized in order not to depend
// on l10n.js loading (which is lazy). See bug 999132
bind(this.doc, 'DOMRetranslated', this.firer('localized'));
bind(this.win, 'beforeunload', this.onBeforeUnload);
bind(this.win, 'keydown', this.onKeyDown);
bind(this.el, 'click', this.onClick);
debug('events bound');
};
/**
* Tasks to run when the
* app becomes visible.
*
* Check the storage again as users
* may have made changes since the
* app was minimised
*/
App.prototype.onVisible = function() {
this.geolocationWatch();
this.orientation.start();
this.orientation.lock();
debug('visible');
};
/**
* Tasks to run when the
* app is minimised/hidden.
*
* @private
*/
App.prototype.onHidden = function() {
this.geolocation.stopWatching();
this.orientation.stop();
debug('hidden');
};
/**
* Reboots the application
*
* @private
*/
App.prototype.onReboot = function() {
debug('reboot');
window.location.reload();
};
/**
* Emit a click event that other
* modules can listen to.
*
* @private
*/
App.prototype.onClick = function() {
debug('click');
this.emit('click');
};
/**
* Log when critical path has completed.
*
* @private
*/
App.prototype.onCriticalPathDone = function() {
if (this.criticalPathDone) { return; }
debug('critical path done');
// PERFORMANCE MARKER (3): visuallyLoaded
// Designates that the app is visually loaded (e.g.: all of the
// "above-the-fold" content exists in the DOM and is marked as
// ready to be displayed).
window.performance.mark('visuallyLoaded');
// Load non-critical modules
this.listenForStopRecordingEvent();
this.loadLazyModules();
this.perf.criticalPath = Date.now();
this.criticalPathDone = true;
this.emit('criticalpathdone');
};
/**
* Lazy load activity dependencies.
*
* @private
*/
App.prototype.onActivity = function() {
this.loadLazyController('controllers/confirm');
};
App.prototype.onCountdown = function() {
this.loadLazyController('controllers/countdown');
};
App.prototype.onNewThumbnail = function() {
this.loadLazyController('controllers/preview-gallery');
};
App.prototype.onPreview = function() {
var self = this;
this.emit('busy', 'lazyLoading');
this.loadLazyController('controllers/preview-gallery').then(function() {
self.emit('preview');
self.emit('ready');
});
};
App.prototype.onWillChange = function() {
this.loadLazyController('controllers/recording-timer');
};
App.prototype.loadLazyModules = function() {
debug('load lazy modules');
var self = this;
var load = this.loadLazyControllers(this.controllers.lazy);
var storage = new Promise(function(resolve, reject) {
if (self.storageChecked) { resolve(); }
else { self.once('storage:checked', resolve); }
});
// All done
Promise.all([load, storage]).then(function() {
debug('app fully loaded');
// PERFORMANCE MARKER (4): contentInteractive
// Designates that the app has its events bound for the minimum
// set of functionality to allow the user to interact with the
// "above-the-fold" content.
window.performance.mark('contentInteractive');
self.loaded = true;
self.emit('loaded');
self.perf.loaded = Date.now();
self.logPerf();
// PERFORMANCE MARKER (5): fullyLoaded
// Designates that the app is *completely* loaded and all relevant
// "below-the-fold" content exists in the DOM, is marked visible,
// has its events bound and is ready for user interaction. All
// required startup background processing should be complete.
window.performance.mark('fullyLoaded');
});
};
App.prototype.logPerf =function() {
var timing = window.performance.timing;
console.log('first module: %s',
this.perf.firstModule - this.perf.jsStarted + 'ms');
console.log('critical-path: %s',
this.perf.criticalPath - timing.domLoading + 'ms');
console.log('app-fully-loaded: %s',
this.perf.loaded - timing.domLoading + 'ms');
};
/**
* Begins watching location if not within
* a pending activity and the app isn't
* currently hidden.
*
* Watching is delayed by the `promptDelay`
* defined in settings.
*
* @private
*/
App.prototype.geolocationWatch = function() {
var shouldWatch = !this.activity.pick && !this.hidden;
if (shouldWatch) { this.geolocation.watch(); }
};
/**
* Responds to the `visibilitychange`
* event, emitting useful app events
* that allow us to perform relate
* work elsewhere.
*
* @private
*/
App.prototype.onVisibilityChange = function() {
this.hidden = this.doc.hidden;
this.emit(this.hidden ? 'hidden' : 'visible');
};
/**
* Runs just before the
* app is destroyed.
*
* @private
*/
App.prototype.onBeforeUnload = function() {
this.emit('beforeunload');
debug('beforeunload');
};
/**
* Shows the loading screen after the
* number of ms defined in config.js
*
* @param {String} type
* @private
*/
App.prototype.showSpinner = function(key) {
debug('show loading type: %s', key);
this.busy = true;
var view = this.views.loading;
if (view) {
return;
}
var ms = this.settings.spinnerTimeouts.get(key) || 0;
var self = this;
clearTimeout(this.spinnerTimeout);
this.spinnerTimeout = setTimeout(function() {
self.views.loading = new self.LoadingView();
self.views.loading.appendTo(self.el).show();
debug('loading shown');
}, ms);
};
/**
* Clears the loadings screen, or
* any pending loading screen.
*
* @private
*/
App.prototype.clearSpinner = function() {
debug('clear loading');
this.busy = false;
var view = this.views.loading;
clearTimeout(this.spinnerTimeout);
if (!view) { return; }
view.hide(view.destroy);
this.views.loading = null;
};
/**
* When the camera indicates it's busy it
* sometimes passes a `type` string. When
* this type matches one of our keys in the
* `spinnerTimeouts` config, we display the
* loading screen passing on the type.
*
* @param {String} type
* @private
*/
App.prototype.onBusy = function(type) {
debug('camera busy, type: %s', type);
this.busy = true;
var delay = this.settings.spinnerTimeouts.get(type);
if (delay) { this.showSpinner(type); }
};
/**
* If the system app is opening an attention screen (because
* of an incoming call or an alarm, e.g.) and if we are
* currently recording a video then we need to stop recording
* before the ringer or alarm starts sounding. We will be sent
* to the background shortly after this and will stop recording
* when that happens, but by that time it is too late and we
* have already recorded some sound. See bugs 995540 and 1006200.
*
* Similarly, if the user presses the Home button or switches to
* another app while recording, we need to stop recording right away,
* even if the system is overloaded and we don't get a visiblitychange
* event right away. See bug 1046167.
*
* To make this work, we rely on shared/js/stop_recording_event.js which
* abuses the settings API to allow the system app to broadcast a "you
* will soon be hidden" message to any certified apps that care. There
* ought to be a better way, but this is a quick way to fix a
* last-minute release blocker.
*
* @private
*/
App.prototype.listenForStopRecordingEvent = function() {
debug('listen for stop recording events');
// Start the module that generates
// the stoprecording events and listen
// for those custom DOM events and emit
// them using our internal event emitter
stopRecordingEvent.start();
addEventListener('stoprecording', this.firer('stoprecording'));
};
/**
* When the device's hardware keys
* are pressed we emit a global
* app event that other controllers
* can respond to.
*
* TIP: Check config.js for the map
* of key names to types.
*
* @param {Event} e
* @private
*/
App.prototype.onKeyDown = function(e) {
var key = e.key.toLowerCase();
var type = this.settings.keyDownEvents.get(key);
if (type) { this.emit('keydown:' + type, e); }
};
});