|
|
@ -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!
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,31 @@
|
||||||
Alarms & Timers
|
# Alarms & Timers
|
||||||
===============
|
|
||||||
|
|
||||||
This app allows you to add/modify any alarms and timers.
|
This app allows you to add/modify any alarms and timers.
|
||||||
|
|
||||||
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched)
|
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
||||||
to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
|
||||||
|
## Menu overview
|
||||||
|
|
||||||
|
- `New...`
|
||||||
|
- `New Alarm` → Configure a new alarm
|
||||||
|
- `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
|
||||||
|
- `New Timer` → Configure a new timer
|
||||||
|
- `Advanced`
|
||||||
|
- `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
|
||||||
|
- `Enable All` → Enable _all_ disabled alarms & timers
|
||||||
|
- `Disable All` → Disable _all_ enabled alarms & timers
|
||||||
|
- `Delete All` → Delete _all_ alarms & timers
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
- [Gordon Williams](https://github.com/gfwilliams)
|
||||||
|
|
||||||
|
## Main Contributors
|
||||||
|
|
||||||
|
- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
|
||||||
|
- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
|
||||||
|
- [storm64](https://github.com/storm64) - Fix redrawing in submenus
|
||||||
|
|
||||||
|
## Attributions
|
||||||
|
|
||||||
|
All icons used in this app are from [icons8](https://icons8.com).
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
.dows(firstDayOfWeek, 2)
|
||||||
|
.map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
|
||||||
|
.join("")
|
||||||
|
.toLowerCase()
|
||||||
|
: "Once"
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEditRepeatMenu(dow, dowChangeCallback) {
|
||||||
|
var originalDow = dow;
|
||||||
|
var isCustom = dow > 0 && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': /*LANG*/'Alarms&Timers' },
|
"": { "title": /*LANG*/"Repeat Alarm" },
|
||||||
/*LANG*/'< Back': () => { load(); },
|
"< Back": () => dowChangeCallback(dow),
|
||||||
/*LANG*/'New Alarm': () => editAlarm(-1),
|
/*LANG*/"Once": { // No days set: the alarm will fire once
|
||||||
/*LANG*/'New Timer': () => editTimer(-1)
|
value: dow == 0,
|
||||||
};
|
onchange: () => dowChangeCallback(0)
|
||||||
alarms.forEach((alarm, idx) => {
|
},
|
||||||
alarm.dow = handleFirstDayOfWeek(alarm.dow, firstDayOfWeek);
|
/*LANG*/"Workdays": {
|
||||||
|
value: dow == WORKDAYS,
|
||||||
var type, txt; // a leading space is currently required (JS error in Espruino 2v12)
|
onchange: () => dowChangeCallback(WORKDAYS)
|
||||||
if (alarm.timer) {
|
},
|
||||||
type = /*LANG*/"Timer";
|
/*LANG*/"Weekends": {
|
||||||
txt = " " + require("sched").formatTime(alarm.timer);
|
value: dow == WEEKEND,
|
||||||
} else {
|
onchange: () => dowChangeCallback(WEEKEND)
|
||||||
type = /*LANG*/"Alarm";
|
},
|
||||||
txt = " " + require("sched").formatTime(alarm.t);
|
/*LANG*/"Every Day": {
|
||||||
}
|
value: dow == EVERY_DAY,
|
||||||
if (alarm.rp) txt += "\0" + atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
|
onchange: () => dowChangeCallback(EVERY_DAY)
|
||||||
// rename duplicate alarms
|
},
|
||||||
if (menu[type + txt]) {
|
/*LANG*/"Custom": {
|
||||||
var n = 2;
|
value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
|
||||||
while (menu[type + " " + n + txt]) n++;
|
onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalDow)
|
||||||
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)) {
|
E.showMenu(menu);
|
||||||
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();
|
function showCustomDaysMenu(dow, dowChangeCallback, originalDow) {
|
||||||
return E.showMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
function editDOW(dow, onchange) {
|
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': /*LANG*/'Days of Week' },
|
"": { "title": /*LANG*/"Custom Days" },
|
||||||
/*LANG*/'< Back': () => onchange(dow)
|
"< 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();
|
||||||
|
showMainMenu();
|
||||||
|
} else {
|
||||||
|
timer.timer = require("time_utils").encodeTime(time);
|
||||||
|
setTimeout(showEditTimerMenu, 10, timer, timerIndex)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
saveAndReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
showMainMenu();
|
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();
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
@ -13,5 +13,18 @@
|
||||||
{ "name": "alarm.app.js", "url": "app.js" },
|
{ "name": "alarm.app.js", "url": "app.js" },
|
||||||
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
|
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
|
||||||
{ "name": "alarm.wid.js", "url": "widget.js" }
|
{ "name": "alarm.wid.js", "url": "widget.js" }
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
{ "url": "screenshot-1.png" },
|
||||||
|
{ "url": "screenshot-2.png" },
|
||||||
|
{ "url": "screenshot-3.png" },
|
||||||
|
{ "url": "screenshot-4.png" },
|
||||||
|
{ "url": "screenshot-5.png" },
|
||||||
|
{ "url": "screenshot-6.png" },
|
||||||
|
{ "url": "screenshot-7.png" },
|
||||||
|
{ "url": "screenshot-8.png" },
|
||||||
|
{ "url": "screenshot-9.png" },
|
||||||
|
{ "url": "screenshot-10.png" },
|
||||||
|
{ "url": "screenshot-11.png" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -7,3 +7,4 @@
|
||||||
0.07: Update settings
|
0.07: Update settings
|
||||||
Correct `decodeTime(t)` to return a more likely expected time
|
Correct `decodeTime(t)` to return a more likely expected time
|
||||||
0.08: add day of week check to getActiveAlarms()
|
0.08: add day of week check to getActiveAlarms()
|
||||||
|
0.09: Move some functions to new time_utils module
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -113,20 +113,3 @@ exports.getSettings = function () {
|
||||||
exports.setSettings = function(settings) {
|
exports.setSettings = function(settings) {
|
||||||
require("Storage").writeJSON("sched.settings.json", settings);
|
require("Storage").writeJSON("sched.settings.json", settings);
|
||||||
};
|
};
|
||||||
|
|
||||||
// time in ms -> { hrs, mins }
|
|
||||||
exports.decodeTime = function(t) {
|
|
||||||
t = Math.ceil(t / 60000); // sanitise to full minutes
|
|
||||||
let hrs = 0 | (t / 60);
|
|
||||||
return { hrs: hrs, mins: t - hrs * 60 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// time in { hrs, mins } -> ms
|
|
||||||
exports.encodeTime = function(o) {
|
|
||||||
return o.hrs * 3600000 + o.mins * 60000;
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.formatTime = function(t) {
|
|
||||||
let o = exports.decodeTime(t);
|
|
||||||
return o.hrs + ":" + ("0" + o.mins).substr(-2);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "sched",
|
"id": "sched",
|
||||||
"name": "Scheduler",
|
"name": "Scheduler",
|
||||||
"version": "0.08",
|
"version": "0.09",
|
||||||
"description": "Scheduling library for alarms and timers",
|
"description": "Scheduling library for alarms and timers",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "scheduler",
|
"type": "scheduler",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ function showAlarm(alarm) {
|
||||||
const settings = require("sched").getSettings();
|
const settings = require("sched").getSettings();
|
||||||
|
|
||||||
let msg = "";
|
let msg = "";
|
||||||
msg += alarm.timer ? require("sched").formatTime(alarm.timer) : require("sched").formatTime(alarm.t);
|
msg += require("time_utils").formatTime(alarm.timer ? alarm.timer : alarm.t);
|
||||||
if (alarm.msg) {
|
if (alarm.msg) {
|
||||||
msg += "\n"+alarm.msg;
|
msg += "\n"+alarm.msg;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -26,7 +26,7 @@ function showAlarm(alarm) {
|
||||||
|
|
||||||
E.showPrompt(msg,{
|
E.showPrompt(msg,{
|
||||||
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
|
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
|
||||||
buttons : {/*LANG*/"Snooze":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
|
buttons : {/*LANG*/"Snooze":true,/*LANG*/"Stop":false} // default is sleep so it'll come back in 10 mins
|
||||||
}).then(function(sleep) {
|
}).then(function(sleep) {
|
||||||
buzzCount = 0;
|
buzzCount = 0;
|
||||||
if (sleep) {
|
if (sleep) {
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@
|
||||||
0.06: Add logging
|
0.06: Add logging
|
||||||
use Layout library and display ETA
|
use Layout library and display ETA
|
||||||
0.07: Add check for day of week
|
0.07: Add check for day of week
|
||||||
|
0.08: Update to new time_utils module
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ function calc_ess(acc_magn) {
|
||||||
var nextAlarm;
|
var nextAlarm;
|
||||||
active.forEach(alarm => {
|
active.forEach(alarm => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const t = require("sched").decodeTime(alarm.t);
|
const time = require("time_utils").decodeTime(alarm.t);
|
||||||
var dateAlarm = new Date(now.getFullYear(), now.getMonth(), now.getDate(), t.hrs, t.mins);
|
var dateAlarm = new Date(now.getFullYear(), now.getMonth(), now.getDate(), time.h, time.m);
|
||||||
if (dateAlarm < now) { // dateAlarm in the past, add 24h
|
if (dateAlarm < now) { // dateAlarm in the past, add 24h
|
||||||
dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000));
|
dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "sleepphasealarm",
|
"id": "sleepphasealarm",
|
||||||
"name": "SleepPhaseAlarm",
|
"name": "SleepPhaseAlarm",
|
||||||
"shortName": "SleepPhaseAlarm",
|
"shortName": "SleepPhaseAlarm",
|
||||||
"version": "0.07",
|
"version": "0.08",
|
||||||
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
|
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "alarm",
|
"tags": "alarm",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
// module "time_utils"
|
||||||
|
//
|
||||||
|
// Utility functions useful to work with time and durations.
|
||||||
|
// Functions usually receive or return a {h, m} object or a
|
||||||
|
// number of milliseconds representing a time or a duration.
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} time {h, m}
|
||||||
|
* @returns the milliseconds contained in the passed time object
|
||||||
|
*/
|
||||||
|
exports.encodeTime = (time) => time.h * 3600000 + time.m * 60000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {int} millis the number of milliseconds
|
||||||
|
* @returns a time object {h, m} built from the milliseconds
|
||||||
|
*/
|
||||||
|
exports.decodeTime = (millis) => {
|
||||||
|
millis = Math.ceil(millis / 60000);
|
||||||
|
var h = 0 | (millis / 60);
|
||||||
|
return {
|
||||||
|
h: h,
|
||||||
|
m: millis - h * 60
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object|int} value {h,m} object or milliseconds
|
||||||
|
* @returns an human-readable time string like "10:25"
|
||||||
|
*/
|
||||||
|
exports.formatTime = (value) => {
|
||||||
|
var time = (value.h === undefined || value.m === undefined) ? exports.decodeTime(value) : value;
|
||||||
|
return time.h + ":" + ("0" + time.m).substr(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object|int} value {h,m} object or milliseconds
|
||||||
|
* @returns an human-readable duration string like "1h 10m"
|
||||||
|
*/
|
||||||
|
exports.formatDuration = (value) => {
|
||||||
|
var duration;
|
||||||
|
|
||||||
|
var time = (value.h === undefined || value.m === undefined) ? exports.decodeTime(value) : value;
|
||||||
|
|
||||||
|
if (time.h == 0) {
|
||||||
|
duration = time.m + "m"
|
||||||
|
} else {
|
||||||
|
duration = time.h + "h" + (time.m ? (" " + time.m + "m") : "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return duration
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getCurrentTimeMillis = () => {
|
||||||
|
var time = new Date();
|
||||||
|
return (time.getHours() * 3600 + time.getMinutes() * 60 + time.getSeconds()) * 1000;
|
||||||
|
}
|
||||||