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

View file

@ -0,0 +1,364 @@
'use strict';
/* global KeyEvent, mozIntl, IntlHelper */
define(function(require) {
var Alarm = require('alarm');
var ClockView = require('panels/alarm/clock_view');
var AudioManager = require('audio_manager');
var FormButton = require('form_button');
var Sounds = require('sounds');
var Utils = require('utils');
var Panel = require('panel');
var html = require('text!panels/alarm_edit/panel.html');
var constants = require('constants');
IntlHelper.define('weekday-long', 'datetime', {
weekday: 'long'
});
var AlarmEdit = function() {
Panel.apply(this, arguments);
this.element.innerHTML = html;
var handleDomEvent = this.handleDomEvent.bind(this);
this.element.addEventListener('panel-visibilitychange',
this.handleVisibilityChange.bind(this));
this.selects = {};
[
'time', 'repeat', 'sound', 'snooze'
].forEach(function(id) {
this.selects[id] = this.element.querySelector('#' + id + '-select');
}, this);
this.inputs = {
name: this.element.querySelector('#alarm-name'),
volume: this.element.querySelector('#alarm-volume-input')
};
this.headers = {
header: this.element.querySelector('#alarm-header')
};
this.buttons = {};
[
'delete', 'done'
].forEach(function(id) {
this.buttons[id] = this.element.querySelector('#alarm-' + id);
}, this);
this.checkboxes = {
vibrate: this.element.querySelector('#vibrate-checkbox')
};
this.buttons.time = new FormButton(this.selects.time, {
formatLabel: function(value) {
var date = new Date();
// This split(':') is locale-independent per HTML5 <input type=time>
var splitValue = value.split(':');
date.setHours(splitValue[0]);
date.setMinutes(splitValue[1]);
return { raw: Utils.getLocalizedTimeText(date) };
}.bind(this)
});
this.buttons.repeat = new FormButton(this.selects.repeat, {
id: 'repeat-menu',
formatLabel: Utils.summarizeDaysOfWeek
});
this.buttons.sound = new FormButton(this.selects.sound, {
id: 'sound-menu',
formatLabel: Sounds.formatLabel
});
this.buttons.snooze = new FormButton(this.selects.snooze, {
id: 'snooze-menu',
formatLabel: function(snooze) {
var numFormatter = IntlHelper.get('digit-nopadding');
return {
id: 'alarmMinutes',
args: {minutes: numFormatter.format(parseInt(snooze))}
};
}
});
this.scrollList = this.element.querySelector('#edit-alarm');
this.repeatSelect = this.element.querySelector('#repeat-select');
// When the system pops up the ValueSelector, it inadvertently
// messes with the scrollTop of the current panel. This is a
// workaround for bug 981255 until the Edit panel becomes a new
// window per bug 922651.
this.element.addEventListener('scroll', function() {
this.element.scrollTop = 0;
}.bind(this));
// When the language changes, the value of 'firstDayOfTheWeek'
// might change and we need to update translations of weekdays
this.updateL10n();
document.addEventListener('DOMRetranslated', this.updateL10n.bind(this));
this.headers.header.addEventListener('action', handleDomEvent);
this.buttons.done.addEventListener('click', handleDomEvent);
this.selects.sound.addEventListener('change', handleDomEvent);
this.selects.sound.addEventListener('blur', handleDomEvent);
this.selects.repeat.addEventListener('change', handleDomEvent);
this.buttons.delete.addEventListener('click', handleDomEvent);
this.inputs.name.addEventListener('keypress', this.handleNameInput);
this.inputs.volume.addEventListener('change', handleDomEvent);
this.isSaving = false;
// If the phone locks during preview, or an alarm fires, pause the sound.
// TODO: When this is no longer a singleton, unbind the listener.
window.addEventListener('visibilitychange', function() {
if (document.hidden) {
this.stopPreviewSound();
// Ensure the keyboard goes away.
document.activeElement.blur();
}
}.bind(this));
};
AlarmEdit.prototype = Object.create(Panel.prototype);
Utils.extend(AlarmEdit.prototype, {
alarm: null,
ringtonePlayer: AudioManager.createAudioPlayer(),
handleNameInput: function(evt) {
// If the user presses enter on the name label, dismiss the
// keyboard to allow them to continue filling out the other
// fields. This is not in the `handleEvent` function because we
// only want to call `.preventDefault` sometimes.
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
evt.preventDefault();
evt.target.blur();
}
},
updateL10n: function() {
// Move the weekdays around to properly account for the firstDay.
mozIntl.calendarInfo('firstDayOfTheWeek').then(firstDay => {
var formatter = IntlHelper.get('weekday-long');
var options = [...this.repeatSelect.querySelectorAll('option')];
var optionsByDay = {};
options.forEach(option => {
var value = option.getAttribute('value');
optionsByDay[value] = option;
this.repeatSelect.removeChild(option);
});
for (var i = 0; i < 7; i++) {
var day = (i + firstDay) % options.length;
var option = optionsByDay[day.toString()];
var dayDate = new Date(constants.KNOWN_SUNDAY);
dayDate.setDate(constants.KNOWN_SUNDAY.getDate() + firstDay + i);
option.textContent = formatter.format(dayDate);
this.repeatSelect.appendChild(option);
}
});
var numFormatter = IntlHelper.get('digit-nopadding');
var elements = [...document.querySelectorAll('#snooze-select > option')];
elements.forEach(element => {
var value = parseInt(element.value);
element.setAttribute('data-l10n-args',
JSON.stringify({minutes: numFormatter.format(value)}));
});
},
// The name `handleEvent` is already defined by the Panel class, so this
// method must be named uniquely to avoid overriding that functionality.
handleDomEvent: function aev_handleDomEvent(evt) {
evt.preventDefault();
var input = evt.target;
if (!input) {
return;
}
switch (input) {
case this.headers.header:
ClockView.show();
break;
case this.buttons.done:
ClockView.show();
this.save();
break;
case this.selects.sound:
switch (evt.type) {
case 'change':
this.previewSound();
break;
case 'blur':
this.stopPreviewSound();
break;
}
break;
case this.buttons.delete:
ClockView.show();
this.delete();
break;
case this.selects.repeat:
this.alarm.repeat = this.buttons.repeat.value;
break;
case this.inputs.volume:
// Alarm Volume is applied to all alarms.
AudioManager.setAlarmVolume(this.getAlarmVolumeValue());
break;
}
},
focusMenu: function aev_focusMenu(menu) {
setTimeout(function() { menu.focus(); }, 10);
},
handleVisibilityChange: function aev_show(evt) {
var isVisible = evt.detail.isVisible;
var alarm;
if (!isVisible) {
return;
}
// `navData` is set by the App module in `navigate`.
alarm = this.navData;
// scroll to top of form list
this.scrollList.scrollTop = 0;
this.element.classList.toggle('new', !alarm);
this.alarm = new Alarm(alarm); // alarm may be null
// Set to empty string if the Alarm doesn't have an ID,
// otherwise dataset will automatically stringify it
// to be "undefined" rather than "".
this.element.dataset.id = this.alarm.id || '';
this.inputs.name.value = this.alarm.label;
AudioManager.requestAlarmVolume().then(function(volume) {
this.inputs.volume.value = AudioManager.getAlarmVolume();
}.bind(this));
// Init time, repeat, sound, snooze selection menu.
this.initTimeSelect();
this.initRepeatSelect();
this.initSoundSelect();
this.initSnoozeSelect();
this.checkboxes.vibrate.checked = this.alarm.vibrate;
// Update the labels for any FormButton dropdowns that have
// changed, because setting <select>.value does not fire a change
// event.
for (var key in this.buttons) {
var button = this.buttons[key];
if (button instanceof FormButton) {
button.refresh();
}
}
location.hash = '#alarm-edit-panel';
},
initTimeSelect: function aev_initTimeSelect() {
// HTML5 <input type=time> expects 24-hour HH:MM format.
var hour = parseInt(this.alarm.hour, 10);
var minute = parseInt(this.alarm.minute, 10);
this.selects.time.value = (hour < 10 ? '0' : '') + hour +
':' + (minute < 10 ? '0' : '') + minute;
},
getTimeSelect: function aev_getTimeSelect() {
// HTML5 <input type=time> returns data in 24-hour HH:MM format.
var splitTime = this.selects.time.value.split(':');
return { hour: splitTime[0], minute: splitTime[1] };
},
initRepeatSelect: function aev_initRepeatSelect() {
this.buttons.repeat.value = this.alarm.repeat;
},
initSoundSelect: function aev_initSoundSelect() {
this.buttons.sound.value = this.alarm.sound;
},
getSoundSelect: function aev_getSoundSelect() {
return this.buttons.sound.value !== '0' && this.buttons.sound.value;
},
previewSound: function aev_previewSound() {
var ringtoneName = this.getSoundSelect();
this.ringtonePlayer.playRingtone(ringtoneName);
},
stopPreviewSound: function aev_stopPreviewSound() {
this.ringtonePlayer.pause();
},
initSnoozeSelect: function aev_initSnoozeSelect() {
this.buttons.snooze.value = this.alarm.snooze;
},
getSnoozeSelect: function aev_getSnoozeSelect() {
return this.buttons.snooze.value;
},
getRepeatSelect: function aev_getRepeatSelect() {
return this.buttons.repeat.value;
},
getAlarmVolumeValue: function() {
return parseFloat(this.inputs.volume.value);
},
save: function aev_save(callback) {
if (this.isSaving) {
// Ignore double-taps on the "Save" button. When this view gets
// refactored, we should opt for a more coherent way of managing
// UI state to avoid glitches like this.
return;
}
var alarm = this.alarm;
if (this.element.dataset.id && this.element.dataset.id !== '') {
alarm.id = parseInt(this.element.dataset.id, 10);
} else {
delete alarm.id;
}
alarm.label = this.inputs.name.value;
var time = this.getTimeSelect();
alarm.hour = time.hour;
alarm.minute = time.minute;
alarm.repeat = this.buttons.repeat.value;
alarm.sound = this.getSoundSelect();
alarm.vibrate = this.checkboxes.vibrate.checked;
alarm.snooze = parseInt(this.getSnoozeSelect(), 10);
AudioManager.setAlarmVolume(this.getAlarmVolumeValue());
this.isSaving = true;
alarm.schedule('normal').then(() => {
this.isSaving = false;
window.dispatchEvent(new CustomEvent('alarm-changed', {
detail: { alarm: alarm, showBanner: true }
}));
callback && callback(null, alarm);
});
},
delete: function aev_delete(callback) {
if (!this.alarm.id) {
setTimeout(callback.bind(null, new Error('no alarm id')), 0);
return;
}
this.alarm.delete().then(callback);
}
});
return AlarmEdit;
});

View file

@ -0,0 +1,69 @@
<gaia-header ignore-dir id="alarm-header" action="close">
<h1 class="new-alarm-title" data-l10n-id="newAlarm"></h1>
<h1 class="edit-alarm-title" data-l10n-id="editAlarm"></h1>
<button id="alarm-done" data-l10n-id="done"></button>
</gaia-header>
<ul id="edit-alarm" class="compact">
<li>
<input type="text" name="alarm.label" id="alarm-name" data-l10n-id="alarmName" placeholder="Alarm name" maxLength="50" dir="auto" />
</li>
<li>
<label data-l10n-id="time"></label>
<input id="time-select" type="time" />
</li>
<li>
<label data-l10n-id="repeat"></label>
<select id="repeat-select" multiple="true">
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
<option value="6"></option>
<option value="0"></option>
</select>
</li>
<li>
<label data-l10n-id="sound"></label>
<select id="sound-select">
<option value="0" data-l10n-id="noSound"></option>
<option value="ac_awake.opus" data-l10n-id="ac_awake_opus"></option>
<option value="ac_crystalize.opus" data-l10n-id="ac_crystalize_opus"></option>
<option value="ac_cycle.opus" data-l10n-id="ac_cycle_opus"></option>
<option value="ac_digicloud.opus" data-l10n-id="ac_digicloud_opus"></option>
<option value="ac_humming_waves.opus" data-l10n-id="ac_humming_waves_opus"></option>
<option value="ac_into_the_void.opus" data-l10n-id="ac_into_the_void_opus"></option>
<option value="ac_lightly.opus" data-l10n-id="ac_lightly_opus"></option>
<option value="ac_mobile.opus" data-l10n-id="ac_mobile_opus"></option>
<option value="ac_pinger.opus" data-l10n-id="ac_pinger_opus"></option>
<option value="ac_skip.opus" data-l10n-id="ac_skip_opus"></option>
<option value="ac_tri.opus" data-l10n-id="ac_tri_opus"></option>
<option value="ac_universal.opus" data-l10n-id="ac_universal_opus"></option>
</select>
</li>
<li>
<gaia-switch id="vibrate-checkbox" checked>
<label data-l10n-id="vibrate"></label>
</gaia-switch>
</li>
<li>
<label class="view-alarm-lbl snooze-lbl" data-l10n-id="snooze-label">Snooze</label>
<select id="snooze-select">
<option data-l10n-id="alarmMinutes" data-l10n-args='{"minutes": "5"}' value="5"></option>
<option data-l10n-id="alarmMinutes" data-l10n-args='{"minutes": "10"}' value="10"></option>
<option data-l10n-id="alarmMinutes" data-l10n-args='{"minutes": "15"}' value="15"></option>
<option data-l10n-id="alarmMinutes" data-l10n-args='{"minutes": "20"}' value="20"></option>
</select>
</li>
<li>
<label class="alarm-volume-lbl"
id="alarm-volume-lbl"
data-l10n-id="alarm-volume-label"></label>
<!-- 0.0625 is 1/16th, which matches the step size in System Settings. -->
<input id="alarm-volume-input" step="0.0625" min="0" value="1" max="1"
type="range">
</li>
<li id="delete-menu">
<button id="alarm-delete" class="danger full" data-l10n-id="delete"></button>
</li>
</ul>