Merge pull request #2781 from bobrippling/oneshot-alarms

multitimer: allow one-shot alarms (delete after)
master
Gordon Williams 2023-07-03 10:45:45 +01:00 committed by GitHub
commit e61eb4e88c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 165 deletions

View File

@ -1,3 +1,4 @@
0.01: Initial version 0.01: Initial version
0.02: Update for time_utils module 0.02: Update for time_utils module
0.03: Use default Bangle formatter for booleans 0.03: Use default Bangle formatter for booleans
0.04: Remove copied sched alarm.js & import newer features (oneshot alarms)

View File

@ -2,9 +2,10 @@
With this app, you can set timers and chronographs (stopwatches) and watch them count down/up in real time. You can also set alarms - swipe left or right to switch between the three functions. With this app, you can set timers and chronographs (stopwatches) and watch them count down/up in real time. You can also set alarms - swipe left or right to switch between the three functions.
"Hard mode" is also available for timers and alarms. It will double the number of buzz counts and you will have to swipe the screen five to eight times correctly - make a mistake, and you will need to start over. "Hard mode" is also available for timers and alarms. It will double the number of buzz counts and you will have to swipe the screen five to eight times correctly - make a mistake, and you will need to start over.
"Delete after expiration" can be set on a timer/alarm to have it delete itself once it's sounded (the same as the alarm app).
## WARNING ## WARNING
* Editing timers in another app (such as the default Alarm app) is not recommended. Editing alarms should not be a problem (in theory). * Editing timers in another app (such as the default Alarm app) is not recommended. Editing alarms should not be a problem (in theory).
* This app uses the [Scheduler library](https://banglejs.com/apps/?id=sched). * This app uses the [Scheduler library](https://banglejs.com/apps/?id=sched).
* To avoid potential conflicts with other apps that uses sched (especially ones that make use of the data and js field), this app only lists timers and alarms that it created - any made outside the app will be ignored. GB alarms are currently an exception as they do not make use of the data and js field. * To avoid potential conflicts with other apps that uses sched (especially ones that make use of the data and js field), this app only lists timers and alarms that it created - any made outside the app will be ignored. GB alarms are currently an exception as they do not make use of the data and js field.
* A keyboard app is only used for adding messages to timers and is therefore not strictly needed. * A keyboard app is only used for adding messages to timers and is therefore not strictly needed.

View File

@ -1,148 +1,89 @@
//sched.js, modified // called by getActiveAlarms(...)[0].js
// Chances are boot0.js got run already and scheduled *another* if (Bangle.SCHED) {
// 'load(sched.js)' - so let's remove it first! clearInterval(Bangle.SCHED);
if (Bangle.SCHED) { delete Bangle.SCHED;
clearInterval(Bangle.SCHED); }
delete Bangle.SCHED;
} function hardMode(tries, max) {
var R = Bangle.appRect;
function hardMode(tries, max) {
var R = Bangle.appRect; function adv() {
tries++;
function adv() { hardMode(tries, max);
tries++; }
hardMode(tries, max);
} if (tries < max) {
g.clear();
if (tries < max) { g.reset();
g.clear(); g.setClipRect(R.x,R.y,R.x2,R.y2);
g.reset(); var code = Math.abs(E.hwRand()%4);
g.setClipRect(R.x,R.y,R.x2,R.y2); if (code == 0) dir = "up";
var code = Math.abs(E.hwRand()%4); else if (code == 1) dir = "right";
if (code == 0) dir = "up"; else if (code == 2) dir = "down";
else if (code == 1) dir = "right"; else dir = "left";
else if (code == 2) dir = "down"; g.setFont("6x8:2").setFontAlign(0,0).drawString(tries+"/"+max+"\nSwipe "+dir, (R.x2-R.x)/2, (R.y2-R.y)/2);
else dir = "left"; var drag;
g.setFont("6x8:2").setFontAlign(0,0).drawString(tries+"/"+max+"\nSwipe "+dir, (R.x2-R.x)/2, (R.y2-R.y)/2); Bangle.setUI({
var drag; mode : "custom",
Bangle.setUI({ drag : e=>{
mode : "custom", if (!drag) { // start dragging
drag : e=>{ drag = {x: e.x, y: e.y};
if (!drag) { // start dragging } else if (!e.b) { // released
drag = {x: e.x, y: e.y}; const dx = e.x-drag.x, dy = e.y-drag.y;
} else if (!e.b) { // released drag = null;
const dx = e.x-drag.x, dy = e.y-drag.y; //horizontal swipes
drag = null; if (Math.abs(dx)>Math.abs(dy)+10) {
//horizontal swipes //left
if (Math.abs(dx)>Math.abs(dy)+10) { if (dx<0 && code == 3) adv();
//left //right
if (dx<0 && code == 3) adv(); else if (dx>0 && code == 1) adv();
//right //wrong swipe - reset
else if (dx>0 && code == 1) adv(); else startHM();
//wrong swipe - reset }
else startHM(); //vertical swipes
} else if (Math.abs(dy)>Math.abs(dx)+10) {
//vertical swipes //up
else if (Math.abs(dy)>Math.abs(dx)+10) { if (dy<0 && code == 0) adv();
//up //down
if (dy<0 && code == 0) adv(); else if (dy>0 && code == 2) adv();
//down //wrong swipe - reset
else if (dy>0 && code == 2) adv(); else startHM();
//wrong swipe - reset }
else startHM(); }
} }
} });
} }
}); else {
} if (!active[0].timer) active[0].last = (new Date()).getDate();
else { if (!active[0].rp) active[0].on = false;
if (!active[0].timer) active[0].last = (new Date()).getDate(); if (active[0].timer) active[0].timer = active[0].data.ot;
if (!active[0].rp) active[0].on = false; require("sched").setAlarms(alarms);
if (active[0].timer) active[0].timer = active[0].data.ot; load();
require("sched").setAlarms(alarms); }
load(); }
}
} function startHM() {
//between 5-8 random swipes
function startHM() { hardMode(0, Math.abs(E.hwRand()%4)+5);
//between 5-8 random swipes }
hardMode(0, Math.abs(E.hwRand()%4)+5);
} function buzz() {
const settings = require("sched").getSettings();
function showAlarm(alarm) { let buzzCount = 3 * settings.buzzCount;
const settings = require("sched").getSettings();
require("buzz").pattern(alarm.vibrate === undefined ? "::" : alarm.vibrate).then(() => {
let msg = ""; if (buzzCount--) {
if (alarm.timer) msg += require("time_utils").formatTime(alarm.timer); setTimeout(buzz, settings.buzzIntervalMillis);
if (alarm.msg) { } else if (alarm.as) { // auto-snooze
msg += "\n"+alarm.msg; buzzCount = settings.buzzCount;
} setTimeout(buzz, settings.defaultSnoozeMillis);
else msg = atob("ACQswgD//33vRcGHIQAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAAAP/wAAAAAAAAAP/wAAAAAAAAAqqoAPAAAAAAqqqqoP8AAAAKqqqqqv/AAACqqqqqqq/wAAKqqqlWqqvwAAqqqqlVaqrAACqqqqlVVqqAAKqqqqlVVaqgAKqaqqlVVWqgAqpWqqlVVVqoAqlWqqlVVVaoCqlV6qlVVVaqCqVVfqlVVVWqCqVVf6lVVVWqKpVVX/lVVVVqqpVVV/+VVVVqqpVVV//lVVVqqpVVVfr1VVVqqpVVVfr1VVVqqpVVVb/lVVVqqpVVVW+VVVVqqpVVVVVVVVVqiqVVVVVVVVWqCqVVVVVVVVWqCqlVVVVVVVaqAqlVVVVVVVaoAqpVVVVVVVqoAKqVVVVVVWqgAKqlVVVVVaqgACqpVVVVVqqAAAqqlVVVaqoAAAKqqVVWqqgAAACqqqqqqqAAAAAKqqqqqgAAAAAAqqqqoAAAAAAAAqqoAAAAA==")+" "+msg; }
});
Bangle.loadWidgets(); }
Bangle.drawWidgets();
let alarms = require("sched").getAlarms();
let buzzCount = settings.buzzCount; let active = require("sched").getActiveAlarms(alarms);
let alarm = active[0];
if (alarm.data.hm && alarm.data.hm == true) { // active[0] is a HM alarm (otherwise we'd have triggered sched.js instead of this file)
//hard mode extends auto-snooze time startHM();
buzzCount = buzzCount * 3; buzz();
startHM();
}
else {
E.showPrompt(msg,{
title: "TIMER!",
buttons : {"Snooze":true,"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;
if (sleep) {
if(alarm.ot===undefined) alarm.ot = alarm.t;
alarm.t += settings.defaultSnoozeMillis;
} else {
if (!alarm.timer) alarm.last = (new Date()).getDate();
if (alarm.ot!==undefined) {
alarm.t = alarm.ot;
delete alarm.ot;
}
if (!alarm.rp) alarm.on = false;
}
//reset timer value
if (alarm.timer) alarm.timer = alarm.data.ot;
// alarm is still a member of 'alarms', so writing to array writes changes back directly
require("sched").setAlarms(alarms);
load();
});
}
function buzz() {
if (settings.unlockAtBuzz) {
Bangle.setLocked(false);
}
require("buzz").pattern(alarm.vibrate === undefined ? "::" : alarm.vibrate).then(() => {
if (buzzCount--) {
setTimeout(buzz, settings.buzzIntervalMillis);
} else if (alarm.as) { // auto-snooze
buzzCount = settings.buzzCount;
setTimeout(buzz, settings.defaultSnoozeMillis);
}
});
}
if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1)
return;
buzz();
}
// Check for alarms
let alarms = require("sched").getAlarms();
let active = require("sched").getActiveAlarms(alarms);
if (active.length) {
// if there's an alarm, show it
showAlarm(active[0]);
} else {
// otherwise just go back to default app
setTimeout(load, 100);
}

View File

@ -56,6 +56,14 @@ function clearInt() {
timerInt2 = []; timerInt2 = [];
} }
function setHM(alarm, on) {
if (on)
alarm.js = "(require('Storage').read('multitimer.alarm.js') !== undefined) ? load('multitimer.alarm.js') : load('sched.js')";
else
delete alarm.js;
alarm.data.hm = on;
}
function drawTimers() { function drawTimers() {
layer = 0; layer = 0;
var timers = require("sched").getAlarms().filter(a => a.timer && a.appid == "multitimer"); var timers = require("sched").getAlarms().filter(a => a.timer && a.appid == "multitimer");
@ -228,12 +236,11 @@ function editTimer(idx, a) {
else a = timers[idx]; else a = timers[idx];
} }
if (!a.data) { if (!a.data) {
a.data = {}; a.data = { hm: false };
a.data.hm = false;
} }
var t = decodeTime(a.timer); var t = decodeTime(a.timer);
function editMsg(idx, a) { function editMsg(idx, a) {
g.clear(); g.clear();
idx < 0 ? msg = "" : msg = a.msg; idx < 0 ? msg = "" : msg = a.msg;
require("textinput").input({text:msg}).then(result => { require("textinput").input({text:msg}).then(result => {
@ -267,7 +274,10 @@ function editTimer(idx, a) {
}, },
"Enabled": { "Enabled": {
value: a.on, value: a.on,
onchange: v => a.on = v onchange: v => {
delete a.last;
a.on = v;
}
}, },
"Hours": { "Hours": {
value: t.hrs, min: 0, max: 23, wrap: true, value: t.hrs, min: 0, max: 23, wrap: true,
@ -292,9 +302,13 @@ function editTimer(idx, a) {
}, },
"Hard Mode": { "Hard Mode": {
value: a.data.hm, value: a.data.hm,
onchange: v => a.data.hm = v onchange: v => setHM(a, v),
}, },
"Vibrate": require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), "Vibrate": require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v),
"Delete After Expiration": {
value: !!a.del,
onchange: v => a.del = v
},
"Msg": { "Msg": {
value: !a.msg ? "" : a.msg.length > 6 ? a.msg.substring(0, 6)+"..." : a.msg, value: !a.msg ? "" : a.msg.length > 6 ? a.msg.substring(0, 6)+"..." : a.msg,
//menu glitch? setTimeout required here //menu glitch? setTimeout required here
@ -382,7 +396,7 @@ function swMenu(idx, a) {
}, 100 - (a.t % 100)); }, 100 - (a.t % 100));
} }
function editMsg(idx, a) { function editMsg(idx, a) {
g.clear(); g.clear();
msg = a.msg; msg = a.msg;
require("textinput").input({text:msg}).then(result => { require("textinput").input({text:msg}).then(result => {
@ -556,12 +570,11 @@ function editAlarm(idx, a) {
else a = require("sched").newDefaultAlarm(); else a = require("sched").newDefaultAlarm();
} }
if (!a.data) { if (!a.data) {
a.data = {}; a.data = { hm: false };
a.data.hm = false;
} }
var t = decodeTime(a.t); var t = decodeTime(a.t);
function editMsg(idx, a) { function editMsg(idx, a) {
g.clear(); g.clear();
idx < 0 ? msg = "" : msg = a.msg; idx < 0 ? msg = "" : msg = a.msg;
require("textinput").input({text:msg}).then(result => { require("textinput").input({text:msg}).then(result => {
@ -582,8 +595,6 @@ function editAlarm(idx, a) {
var menu = { var menu = {
"": { "title": "Alarm" }, "": { "title": "Alarm" },
"< Back": () => { "< Back": () => {
if (a.data.hm == true) a.js = "(require('Storage').read('multitimer.alarm.js') !== undefined) ? load('multitimer.alarm.js') : load('sched.js')";
if (a.data.hm == false && a.js) delete a.js;
if (idx >= 0) alarms[alarmIdx[idx]] = a; if (idx >= 0) alarms[alarmIdx[idx]] = a;
else alarms.push(a); else alarms.push(a);
require("sched").setAlarms(alarms); require("sched").setAlarms(alarms);
@ -592,7 +603,10 @@ function editAlarm(idx, a) {
}, },
"Enabled": { "Enabled": {
value: a.on, value: a.on,
onchange: v => a.on = v onchange: v => {
delete a.last;
a.on = v;
}
}, },
"Hours": { "Hours": {
value: t.hrs, min: 0, max: 23, wrap: true, value: t.hrs, min: 0, max: 23, wrap: true,
@ -618,9 +632,13 @@ function editAlarm(idx, a) {
}, },
"Hard Mode": { "Hard Mode": {
value: a.data.hm, value: a.data.hm,
onchange: v => a.data.hm = v onchange: v => setHM(a, v),
}, },
"Vibrate": require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), "Vibrate": require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v),
"Delete After Expiration": {
value: !!a.del,
onchange: v => a.del = v
},
"Auto Snooze": { "Auto Snooze": {
value: a.as, value: a.as,
onchange: v => a.as = v onchange: v => a.as = v
@ -653,7 +671,7 @@ Bangle.on("drag", e=>{
if (layer < 0) return; if (layer < 0) return;
if (!drag) { // start dragging if (!drag) { // start dragging
drag = {x: e.x, y: e.y}; drag = {x: e.x, y: e.y};
} }
else if (!e.b) { // released else if (!e.b) { // released
const dx = e.x-drag.x, dy = e.y-drag.y; const dx = e.x-drag.x, dy = e.y-drag.y;
drag = null; drag = null;

8
apps/multitimer/boot.js Normal file
View File

@ -0,0 +1,8 @@
{
const resetTimer = alarm => {
if (alarm.timer) alarm.timer = alarm.data.ot;
};
Bangle.on("alarmSnooze", resetTimer);
Bangle.on("alarmDismiss", resetTimer);
}

View File

@ -1,7 +1,7 @@
{ {
"id": "multitimer", "id": "multitimer",
"name": "Multi Timer", "name": "Multi Timer",
"version": "0.03", "version": "0.04",
"description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.", "description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.",
"icon": "app.png", "icon": "app.png",
"screenshots": [ "screenshots": [
@ -14,6 +14,7 @@
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"multitimer.app.js","url":"app.js"}, {"name":"multitimer.app.js","url":"app.js"},
{"name":"multitimer.boot.js","url":"boot.js"},
{"name":"multitimer.alarm.js","url":"alarm.js"}, {"name":"multitimer.alarm.js","url":"alarm.js"},
{"name":"multitimer.img","url":"app-icon.js","evaluate":true} {"name":"multitimer.img","url":"app-icon.js","evaluate":true}
], ],

View File

@ -102,7 +102,7 @@ function showAlarm(alarm) {
date = new Date(date.getFullYear() + rp.num, date.getMonth(), alarm.od); date = new Date(date.getFullYear() + rp.num, date.getMonth(), alarm.od);
if (date.getDate() != alarm.od) date.setDate(0); if (date.getDate() != alarm.od) date.setDate(0);
break; break;
default: default:
console.log(`sched: unknown repeat '${JSON.stringify(rp)}'`); console.log(`sched: unknown repeat '${JSON.stringify(rp)}'`);
break; break;
} }