[Alarms & Timers] Full UI rewrite

pull/1822/head
Alessandro Cocco 2022-05-12 22:59:03 +02:00
parent ee663ef169
commit f22f113687
4 changed files with 288 additions and 209 deletions

View File

@ -26,3 +26,4 @@
Add "Enable All", "Disable All" and "Remove All" actions Add "Enable All", "Disable All" and "Remove All" actions
0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu 0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu
0.26: Add support for Monday as first day of the week (#1780) 0.26: Add support for Monday as first day of the week (#1780)
0.27: New UI!

View File

@ -1,20 +1,160 @@
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
// 0 = Sunday (default), 1 = Monday
const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0;
const WORKDAYS = 62
const WEEKEND = firstDayOfWeek ? 192 : 65;
const EVERY_DAY = firstDayOfWeek ? 254 : 127;
const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA==");
const iconAlarmOff = "\0" + (g.theme.dark
? atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=")
: atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="));
const iconTimerOn = "\0" + (g.theme.dark
? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=")
: atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="));
const iconTimerOff = "\0" + (g.theme.dark
? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=")
: atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="));
// An array of alarm objects (see sched/README.md) // An array of alarm objects (see sched/README.md)
var alarms = require("sched").getAlarms(); var alarms = require("sched").getAlarms();
// 0 = Sunday function handleFirstDayOfWeek(dow) {
// 1 = Monday if (firstDayOfWeek == 1) {
var firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0; if ((dow & 1) == 1) {
// In the scheduler API Sunday is 1.
// Here the week starts on Monday and Sunday is ON so
// when I read the dow I need to move Sunday to 128...
dow += 127;
} else if ((dow & 128) == 128) {
// ... and then when I write the dow I need to move Sunday back to 1.
dow -= 127;
}
}
return dow;
}
function getCurrentTime() { // Check the first day of week and update the dow field accordingly.
var time = new Date(); alarms.forEach(alarm => alarm.dow = handleFirstDayOfWeek(alarm.dow));
return (
time.getHours() * 3600000 + function showMainMenu() {
time.getMinutes() * 60000 + const menu = {
time.getSeconds() * 1000 "": { "title": /*LANG*/"Alarms & Timers" },
); "< Back": () => load(),
/*LANG*/"New...": () => showNewMenu()
};
alarms.forEach((e, index) => {
var label = e.timer
? require("time_utils").formatDuration(e.timer)
: require("time_utils").formatTime(e.t) + (e.dow > 0 ? (" " + decodeDOW(e)) : "");
menu[label] = {
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
};
});
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
E.showMenu(menu);
}
function showNewMenu() {
E.showMenu({
"": { "title": /*LANG*/"New..." },
"< Back": () => showMainMenu(),
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
});
}
function showEditAlarmMenu(selectedAlarm, alarmIndex) {
var isNew = alarmIndex === undefined;
var alarm = require("sched").newDefaultAlarm();
alarm.dow = handleFirstDayOfWeek(alarm.dow);
if (selectedAlarm) {
Object.assign(alarm, selectedAlarm);
}
var time = require("time_utils").decodeTime(alarm.t);
const menu = {
"": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
"< Back": () => {
saveAlarm(alarm, alarmIndex, time);
showMainMenu();
},
/*LANG*/"Hour": {
value: time.h,
format: v => ("0" + v).substr(-2),
min: 0,
max: 23,
wrap: true,
onchange: v => time.h = v
},
/*LANG*/"Minute": {
value: time.m,
format: v => ("0" + v).substr(-2),
min: 0,
max: 59,
wrap: true,
onchange: v => time.m = v
},
/*LANG*/"Enabled": {
value: alarm.on,
onchange: v => alarm.on = v
},
/*LANG*/"Repeat": {
value: decodeDOW(alarm),
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.dow, dow => {
alarm.rp = dow > 0;
alarm.dow = dow;
alarm.t = require("time_utils").encodeTime(time);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
})
},
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
/*LANG*/"Auto Snooze": {
value: alarm.as,
onchange: v => alarm.as = v
},
/*LANG*/"Cancel": () => showMainMenu()
};
if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
if (confirm) {
alarms.splice(alarmIndex, 1);
saveAndReload();
showMainMenu();
} else {
alarm.t = require("time_utils").encodeTime(time);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
}
});
};
}
E.showMenu(menu);
}
function saveAlarm(alarm, alarmIndex, time) {
alarm.t = require("time_utils").encodeTime(time);
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
if (alarmIndex === undefined) {
alarms.push(alarm);
} else {
alarms[alarmIndex] = alarm;
}
saveAndReload();
} }
function saveAndReload() { function saveAndReload() {
@ -23,249 +163,187 @@ function saveAndReload() {
require("sched").setAlarms(alarms); require("sched").setAlarms(alarms);
require("sched").reload(); require("sched").reload();
// Fix after save
alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek));
} }
function showMainMenu() { function decodeDOW(alarm) {
// Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w") return alarm.rp
// Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA") ? require("date_utils")
const menu = { .dows(firstDayOfWeek, 2)
'': { 'title': /*LANG*/'Alarms&Timers' }, .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
/*LANG*/'< Back': () => { load(); }, .join("")
/*LANG*/'New Alarm': () => editAlarm(-1), .toLowerCase()
/*LANG*/'New Timer': () => editTimer(-1) : "Once"
};
alarms.forEach((alarm, idx) => {
alarm.dow = handleFirstDayOfWeek(alarm.dow, firstDayOfWeek);
var type, txt; // a leading space is currently required (JS error in Espruino 2v12)
if (alarm.timer) {
type = /*LANG*/"Timer";
txt = " " + require("sched").formatTime(alarm.timer);
} else {
type = /*LANG*/"Alarm";
txt = " " + require("sched").formatTime(alarm.t);
}
if (alarm.rp) txt += "\0" + atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
// rename duplicate alarms
if (menu[type + txt]) {
var n = 2;
while (menu[type + " " + n + txt]) n++;
txt = type + " " + n + txt;
} else txt = type + txt;
// add to menu
menu[txt] = {
value: "\0" + atob(alarm.on ? "EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g" : "EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
onchange: function () {
setTimeout(alarm.timer ? editTimer : editAlarm, 10, idx, alarm);
}
};
});
if (alarms.some(e => !e.on)) {
menu[/*LANG*/"Enable All"] = () => enableAll(true);
}
if (alarms.some(e => e.on)) {
menu[/*LANG*/"Disable All"] = () => enableAll(false);
}
if (alarms.length > 0) {
menu[/*LANG*/"Delete All"] = () => deleteAll();
}
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
return E.showMenu(menu);
} }
function editDOW(dow, onchange) { function showEditRepeatMenu(dow, dowChangeCallback) {
var originalDow = dow;
var isCustom = dow > 0 && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
const menu = { const menu = {
'': { 'title': /*LANG*/'Days of Week' }, "": { "title": /*LANG*/"Repeat Alarm" },
/*LANG*/'< Back': () => onchange(dow) "< Back": () => dowChangeCallback(dow),
/*LANG*/"Once": { // No days set: the alarm will fire once
value: dow == 0,
onchange: () => dowChangeCallback(0)
},
/*LANG*/"Workdays": {
value: dow == WORKDAYS,
onchange: () => dowChangeCallback(WORKDAYS)
},
/*LANG*/"Weekends": {
value: dow == WEEKEND,
onchange: () => dowChangeCallback(WEEKEND)
},
/*LANG*/"Every Day": {
value: dow == EVERY_DAY,
onchange: () => dowChangeCallback(EVERY_DAY)
},
/*LANG*/"Custom": {
value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalDow)
}
};
E.showMenu(menu);
}
function showCustomDaysMenu(dow, dowChangeCallback, originalDow) {
const menu = {
"": { "title": /*LANG*/"Custom Days" },
"< Back": () => dowChangeCallback(dow),
}; };
require("date_utils").dows(firstDayOfWeek).forEach((day, i) => { require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
menu[day] = { menu[day] = {
value: !!(dow & (1 << (i + firstDayOfWeek))), value: !!(dow & (1 << (i + firstDayOfWeek))),
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek))) onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek)))
}; };
}); });
menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalDow, dowChangeCallback)
E.showMenu(menu); E.showMenu(menu);
} }
function editAlarm(alarmIndex, alarm) { function showEditTimerMenu(selectedTimer, timerIndex) {
var newAlarm = alarmIndex < 0; var isNew = timerIndex === undefined;
var a = require("sched").newDefaultAlarm();
a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek);
if (!newAlarm) Object.assign(a, alarms[alarmIndex]); var timer = require("sched").newDefaultTimer();
if (alarm) Object.assign(a, alarm);
var t = require("sched").decodeTime(a.t); if (selectedTimer) {
Object.assign(timer, selectedTimer);
}
var time = require("time_utils").decodeTime(timer.timer);
const menu = { const menu = {
'': { 'title': /*LANG*/'Alarm' }, "": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
/*LANG*/'< Back': () => { "< Back": () => {
saveAlarm(newAlarm, alarmIndex, a, t); saveTimer(timer, timerIndex, time);
showMainMenu(); showMainMenu();
}, },
/*LANG*/'Hours': { /*LANG*/"Hours": {
value: t.hrs, min: 0, max: 23, wrap: true, value: time.h,
onchange: v => t.hrs = v min: 0,
max: 23,
wrap: true,
onchange: v => time.h = v
}, },
/*LANG*/'Minutes': { /*LANG*/"Minutes": {
value: t.mins, min: 0, max: 59, wrap: true, value: time.m,
onchange: v => t.mins = v min: 0,
max: 59,
wrap: true,
onchange: v => time.m = v
}, },
/*LANG*/'Enabled': { /*LANG*/"Enabled": {
value: a.on, value: timer.on,
format: v => v ? /*LANG*/"On" : /*LANG*/"Off", onchange: v => timer.on = v
onchange: v => a.on = v
}, },
/*LANG*/'Repeat': { /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v),
value: a.rp,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: v => a.rp = v
},
/*LANG*/'Days': {
value: decodeDOW(a.dow),
onchange: () => setTimeout(editDOW, 100, a.dow, d => {
a.dow = d;
a.t = require("sched").encodeTime(t);
editAlarm(alarmIndex, a);
})
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v),
/*LANG*/'Auto Snooze': {
value: a.as,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: v => a.as = v
}
}; };
menu[/*LANG*/"Cancel"] = () => showMainMenu(); if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
if (!newAlarm) { E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
menu[/*LANG*/"Delete"] = function () {
alarms.splice(alarmIndex, 1);
saveAndReload();
showMainMenu();
};
}
return E.showMenu(menu);
}
function saveAlarm(newAlarm, alarmIndex, a, t) {
a.t = require("sched").encodeTime(t);
a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
if (newAlarm) {
alarms.push(a);
} else {
alarms[alarmIndex] = a;
}
saveAndReload();
}
function editTimer(alarmIndex, alarm) {
var newAlarm = alarmIndex < 0;
var a = require("sched").newDefaultTimer();
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a, alarm);
var t = require("sched").decodeTime(a.timer);
const menu = {
'': { 'title': /*LANG*/'Timer' },
/*LANG*/'< Back': () => {
saveTimer(newAlarm, alarmIndex, a, t);
showMainMenu();
},
/*LANG*/'Hours': {
value: t.hrs, min: 0, max: 23, wrap: true,
onchange: v => t.hrs = v
},
/*LANG*/'Minutes': {
value: t.mins, min: 0, max: 59, wrap: true,
onchange: v => t.mins = v
},
/*LANG*/'Enabled': {
value: a.on,
format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
onchange: v => a.on = v
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v),
};
menu[/*LANG*/"Cancel"] = () => showMainMenu();
if (!newAlarm) {
menu[/*LANG*/"Delete"] = function () {
alarms.splice(alarmIndex, 1);
saveAndReload();
showMainMenu();
};
}
return E.showMenu(menu);
}
function saveTimer(newAlarm, alarmIndex, a, t) {
a.timer = require("sched").encodeTime(t);
a.t = getCurrentTime() + a.timer;
a.last = 0;
if (newAlarm) {
alarms.push(a);
} else {
alarms[alarmIndex] = a;
}
saveAndReload();
}
function handleFirstDayOfWeek(dow, firstDayOfWeek) {
if (firstDayOfWeek == 1) {
if ((dow & 1) == 1) {
// By default 1 = Sunday.
// Here the week starts on Monday and Sunday is ON so move Sunday to 128.
dow += 127;
} else if ((dow & 128) == 128) {
dow -= 127;
}
}
return dow;
}
function decodeDOW(dow) {
return require("date_utils")
.dows(firstDayOfWeek, 2)
.map((day, index) => dow & (1 << (index + firstDayOfWeek)) ? day : "_")
.join("");
}
function enableAll(on) {
E.showPrompt(/*LANG*/"Are you sure?", {
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
}).then((confirm) => {
if (confirm) { if (confirm) {
alarms.forEach(alarm => alarm.on = on); alarms.splice(timerIndex, 1);
saveAndReload(); saveAndReload();
showMainMenu();
} else {
timer.timer = require("time_utils").encodeTime(time);
setTimeout(showEditTimerMenu, 10, timer, timerIndex)
}
});
};
} }
showMainMenu(); E.showMenu(menu);
}
function saveTimer(timer, timerIndex, time) {
timer.timer = require("time_utils").encodeTime(time);
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
timer.last = 0;
if (timerIndex === undefined) {
alarms.push(timer);
} else {
alarms[timerIndex] = timer;
}
saveAndReload();
}
function showAdvancedMenu() {
E.showMenu({
"": { "title": /*LANG*/"Advanced" },
"< Back": () => showMainMenu(),
/*LANG*/"Scheduler Settings": () => eval(require("Storage").read("sched.settings.js"))(() => showAdvancedMenu()),
/*LANG*/"Enable All": () => enableAll(true),
/*LANG*/"Disable All": () => enableAll(false),
/*LANG*/"Delete All": () => deleteAll()
}); });
} }
function enableAll(on) {
if (alarms.filter(e => e.on == !on).length == 0) {
E.showPrompt(on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable", {
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All",
buttons: { /*LANG*/"Ok": true }
}).then(() => showAdvancedMenu());
} else {
E.showPrompt(/*LANG*/"Are you sure?", { title: on ? "/*LANG*/Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
if (confirm) {
alarms.forEach(alarm => alarm.on = on);
saveAndReload();
showMainMenu();
} else {
showAdvancedMenu();
}
});
}
}
function deleteAll() { function deleteAll() {
if (alarms.length == 0) {
E.showPrompt(/*LANG*/"Nothing to delete", { title: /*LANG*/"Delete All", buttons: { /*LANG*/"Ok": true } }).then(() => showAdvancedMenu());
} else {
E.showPrompt(/*LANG*/"Are you sure?", { E.showPrompt(/*LANG*/"Are you sure?", {
title: /*LANG*/"Delete All" title: /*LANG*/"Delete All"
}).then((confirm) => { }).then((confirm) => {
if (confirm) { if (confirm) {
alarms = []; alarms = [];
saveAndReload(); saveAndReload();
}
showMainMenu(); showMainMenu();
} else {
showAdvancedMenu();
}
}); });
}
} }
showMainMenu(); showMainMenu();

View File

@ -2,7 +2,7 @@
"id": "alarm", "id": "alarm",
"name": "Alarms & Timers", "name": "Alarms & Timers",
"shortName": "Alarms", "shortName": "Alarms",
"version": "0.26", "version": "0.27",
"description": "Set alarms and timers on your Bangle", "description": "Set alarms and timers on your Bangle",
"icon": "app.png", "icon": "app.png",
"tags": "tool,alarm,widget", "tags": "tool,alarm,widget",

View File

@ -61,12 +61,12 @@ exports.reload = function() {
exports.newDefaultAlarm = function () { exports.newDefaultAlarm = function () {
const settings = exports.getSettings(); const settings = exports.getSettings();
let alarm = { var alarm = {
t: 12 * 3600000, // Default to 12:00 t: 12 * 3600000, // Default to 12:00
on: true, on: true,
rp: settings.defaultRepeat, rp: settings.defaultRepeat,
as: settings.defaultAutoSnooze, as: settings.defaultAutoSnooze,
dow: 0b1111111, dow: settings.defaultRepeat ? 0b1111111 : 0b0000000,
last: 0, last: 0,
vibrate: settings.defaultAlarmPattern, vibrate: settings.defaultAlarmPattern,
}; };
@ -79,7 +79,7 @@ exports.newDefaultAlarm = function () {
exports.newDefaultTimer = function () { exports.newDefaultTimer = function () {
const settings = exports.getSettings(); const settings = exports.getSettings();
let timer = { var timer = {
timer: 5 * 60 * 1000, // 5 minutes timer: 5 * 60 * 1000, // 5 minutes
on: true, on: true,
rp: false, rp: false,