diff --git a/apps/keytimer/ChangeLog b/apps/keytimer/ChangeLog index c819919ed..1c3954fc7 100644 --- a/apps/keytimer/ChangeLog +++ b/apps/keytimer/ChangeLog @@ -1,2 +1,3 @@ 0.01: New app! -0.02: Submitted to the app loader \ No newline at end of file +0.02: Submitted to the app loader +0.03: Rewrote to use scheduler library \ No newline at end of file diff --git a/apps/keytimer/app.js b/apps/keytimer/app.js index 7d235f9a8..820de1c76 100644 --- a/apps/keytimer/app.js +++ b/apps/keytimer/app.js @@ -1,27 +1,27 @@ -Bangle.keytimer_ACTIVE = true; +const storage = require('Storage'); const common = require("keytimer-com.js"); -const storage = require("Storage"); const keypad = require("keytimer-keys.js"); const timerView = require("keytimer-tview.js"); +Bangle.KEYTIMER = true; + Bangle.loadWidgets(); Bangle.drawWidgets(); -//Save our state when the app is closed E.on('kill', () => { - storage.writeJSON(common.STATE_PATH, common.state); + storage.writeJSON('keytimer.json', common.state); }); -//Handle touch here. I would implement these separately in each view, but I can't figure out how to clear the event listeners. +// Handle touch here. I would implement these separately in each view, but I can't figure out how to clear the event listeners. Bangle.on('touch', (button, xy) => { - if (common.state.wasRunning) timerView.touch(button, xy); + if (common.timerExists()) timerView.touch(button, xy); else keypad.touch(button, xy); }); Bangle.on('swipe', dir => { - if (!common.state.wasRunning) keypad.swipe(dir); + if (!common.timerExists()) keypad.swipe(dir); }); -if (common.state.wasRunning) timerView.show(common); +if (common.timerExists()) timerView.show(common); else keypad.show(common); diff --git a/apps/keytimer/boot.js b/apps/keytimer/boot.js deleted file mode 100644 index f202bcbdf..000000000 --- a/apps/keytimer/boot.js +++ /dev/null @@ -1,11 +0,0 @@ -const keytimer_common = require("keytimer-com.js"); - -//Only start the timeout if the timer is running -if (keytimer_common.state.running) { - setTimeout(() => { - //Check now to avoid race condition - if (Bangle.keytimer_ACTIVE === undefined) { - load('keytimer-ring.js'); - } - }, keytimer_common.getTimeLeft()); -} \ No newline at end of file diff --git a/apps/keytimer/common.js b/apps/keytimer/common.js index 8c702de66..6550fc118 100644 --- a/apps/keytimer/common.js +++ b/apps/keytimer/common.js @@ -1,42 +1,49 @@ -const storage = require("Storage"); -const heatshrink = require("heatshrink"); +const sched = require('sched'); +const storage = require('Storage'); -exports.STATE_PATH = "keytimer.state.json"; - -exports.BUTTON_ICONS = { - play: heatshrink.decompress(atob("jEYwMAkAGBnACBnwCBn+AAQPgAQPwAQP8AQP/AQXAAQPwAQP8AQP+AQgICBwQUCEAn4FggyBHAQ+CIgQ")), - pause: heatshrink.decompress(atob("jEYwMA/4BBAX4CEA")), - reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI=")) +exports.running = function () { + return sched.getAlarm('keytimer') != undefined; }; -//Store the minimal amount of information to be able to reconstruct the state of the timer at any given time. -//This is necessary because it is necessary to write to flash to let the timer run in the background, so minimizing the writes is necessary. -exports.STATE_DEFAULT = { - wasRunning: false, //If the timer ever was running. Used to determine whether to display a reset button - running: false, //Whether the timer is currently running - startTime: 0, //When the timer was last started. Difference between this and now is how long timer has run continuously. - pausedTime: 0, //When the timer was last paused. Used for expiration and displaying timer while paused. - elapsedTime: 0, //How much time the timer had spent running before the current start time. Update on pause or user skipping stages. - setTime: 0, //How long the user wants the timer to run for - inputString: '0' //The string of numbers the user typed in. -}; -exports.state = storage.readJSON(exports.STATE_PATH); -if (!exports.state) { - exports.state = exports.STATE_DEFAULT; +exports.timerExists = function () { + return exports.running() || (exports.state.timeLeft != 0); } //Get the number of milliseconds until the timer expires exports.getTimeLeft = function () { - if (!exports.state.wasRunning) { - //If the timer never ran, the time left is just the set time - return exports.setTime - } else if (exports.state.running) { - //If the timer is running, the time left is current time - start time + preexisting time - var runningTime = (new Date()).getTime() - exports.state.startTime + exports.state.elapsedTime; + if (exports.running()) { + return sched.getTimeToAlarm(sched.getAlarm('keytimer')); } else { - //If the timer is not running, the same as above but use when the timer was paused instead of now. - var runningTime = exports.state.pausedTime - exports.state.startTime + exports.state.elapsedTime; + return exports.state.timeLeft; } +} - return exports.state.setTime - runningTime; +exports.state = storage.readJSON('keytimer.json') || { + inputString: '0', + timeLeft: 0 +}; + +exports.startTimer = function (time) { + let timer = sched.newDefaultTimer(); + + timer.timer = time; + common.state.timeLeft = time; + timer.del = true; + timer.appid = 'keytimer'; + timer.js = "load('keytimer-ring.js')"; + + sched.setAlarm('keytimer', timer); + sched.reload(); +} + +exports.pauseTimer = function () { + exports.state.timeLeft = exports.getTimeLeft(); + sched.setAlarm('keytimer'); + sched.reload(); +} + +exports.deleteTimer = function () { + sched.setAlarm('keytimer'); + exports.state.timeLeft = 0; + sched.reload(); } \ No newline at end of file diff --git a/apps/keytimer/keypad.js b/apps/keytimer/keypad.js index a5edeb2f2..5844c9a63 100644 --- a/apps/keytimer/keypad.js +++ b/apps/keytimer/keypad.js @@ -34,7 +34,6 @@ class NumberButton { onclick() { if (common.state.inputString == '0') common.state.inputString = this.label; else common.state.inputString += this.label; - common.state.setTime = inputStringToTime(common.state.inputString); feedback(true); updateDisplay(); } @@ -44,7 +43,6 @@ let ClearButton = { label: 'Clr', onclick: () => { common.state.inputString = '0'; - common.state.setTime = 0; updateDisplay(); feedback(true); } @@ -53,10 +51,7 @@ let ClearButton = { let StartButton = { label: 'Go', onclick: () => { - common.state.startTime = (new Date()).getTime(); - common.state.elapsedTime = 0; - common.state.wasRunning = true; - common.state.running = true; + common.startTimer(inputStringToTime(common.state.inputString)); feedback(true); require('keytimer-tview.js').show(common); } diff --git a/apps/keytimer/metadata.json b/apps/keytimer/metadata.json index a982594f1..0188df4eb 100644 --- a/apps/keytimer/metadata.json +++ b/apps/keytimer/metadata.json @@ -1,7 +1,7 @@ { "id": "keytimer", "name": "Keypad Timer", - "version": "0.02", + "version": "0.03", "description": "A timer with a keypad that runs in the background", "icon": "icon.png", "type": "app", @@ -20,10 +20,6 @@ "url": "icon.js", "evaluate": true }, - { - "name": "keytimer.boot.js", - "url": "boot.js" - }, { "name": "keytimer-com.js", "url": "common.js" diff --git a/apps/keytimer/ring.js b/apps/keytimer/ring.js index c42c11394..9f4c01b30 100644 --- a/apps/keytimer/ring.js +++ b/apps/keytimer/ring.js @@ -1,28 +1,95 @@ -const common = require('keytimer-com.js'); - -Bangle.loadWidgets() -Bangle.drawWidgets() - -Bangle.setLocked(false); -Bangle.setLCDPower(true); - -let brightness = 0; - -setInterval(() => { - Bangle.buzz(200); - Bangle.setLCDBrightness(1 - brightness); - brightness = 1 - brightness; -}, 400); -Bangle.buzz(200); - -function stopTimer() { - common.state.wasRunning = false; - common.state.running = false; - require("Storage").writeJSON(common.STATE_PATH, common.state); +// Chances are boot0.js got run already and scheduled *another* +// 'load(sched.js)' - so let's remove it first! +if (Bangle.SCHED) { + clearInterval(Bangle.SCHED); + delete Bangle.SCHED; } -E.showAlert("Timer expired!").then(() => { - stopTimer(); - load(); -}); -E.on('kill', stopTimer); \ No newline at end of file +function showAlarm(alarm) { + const alarmIndex = alarms.indexOf(alarm); + const settings = require("sched").getSettings(); + + let message = ""; + if (alarm.msg) { + message += alarm.msg; + } else { + message = (alarm.timer + ? atob("ACQswgD//33vRcGHIQAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAAAP/wAAAAAAAAAP/wAAAAAAAAAqqoAPAAAAAAqqqqoP8AAAAKqqqqqv/AAACqqqqqqq/wAAKqqqlWqqvwAAqqqqlVaqrAACqqqqlVVqqAAKqqqqlVVaqgAKqaqqlVVWqgAqpWqqlVVVqoAqlWqqlVVVaoCqlV6qlVVVaqCqVVfqlVVVWqCqVVf6lVVVWqKpVVX/lVVVVqqpVVV/+VVVVqqpVVV//lVVVqqpVVVfr1VVVqqpVVVfr1VVVqqpVVVb/lVVVqqpVVVW+VVVVqqpVVVVVVVVVqiqVVVVVVVVWqCqVVVVVVVVWqCqlVVVVVVVaqAqlVVVVVVVaoAqpVVVVVVVqoAKqVVVVVVWqgAKqlVVVVVaqgACqpVVVVVqqAAAqqlVVVaqoAAAKqqVVWqqgAAACqqqqqqqAAAAAKqqqqqgAAAAAAqqqqoAAAAAAAAqqoAAAAA==") + : atob("AC0swgF97///RcEpMlVVVVVVf9VVVVVVVVX/9VVf9VVf/1VVV///1Vf9VX///VVX///VWqqlV///1Vf//9aqqqqpf//9V///2qqqqqqn///V///6qqqqqqr///X//+qqoAAKqqv//3//6qoAAAAKqr//3//qqAAAAAAqq//3/+qoAADwAAKqv/3/+qgAADwAACqv/3/aqAAADwAAAqp/19qoAAADwAAAKqfV1qgAAADwAAACqXVWqgAAADwAAACqlVWqAAAADwAAAAqlVWqAAAADwAAAAqlVWqAAAADwAAAAqlVaoAAAADwAAAAKpVaoAAAADwAAAAKpVaoAAAADwAAAAKpVaoAAAAOsAAAAKpVaoAAAAOsAAAAKpVaoAAAAL/AAAAKpVaoAAAAgPwAAAKpVaoAAACAD8AAAKpVWqAAAIAA/AAAqlVWqAAAgAAPwAAqlVWqAACAAADwAAqlVWqgAIAAAAAACqlVVqgAgAAAAAACqVVVqoAAAAAAAAKqVVVaqAAAAAAAAqpVVVWqgAAAAAACqlVVVWqoAAAAAAKqlVVVVqqAAAAAAqqVVVVVaqoAAAAKqpVVVVVeqqoAAKqqtVVVVV/6qqqqqqr/VVVVX/2qqqqqqn/1VVVf/VaqqqqpV/9VVVf9VVWqqlVVf9VVVf1VVVVVVVVX9VQ==") + ) + /*LANG*/" TIMER" + } + + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + let buzzCount = settings.buzzCount; + + E.showPrompt(message, { + title: alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!", + buttons: { /*LANG*/"Snooze": true, /*LANG*/"Stop": false } // default is sleep so it'll come back in some mins + }).then(function (sleep) { + buzzCount = 0; + + if (sleep) { + if (alarm.ot === undefined) { + alarm.ot = alarm.t; + } + alarm.t += settings.defaultSnoozeMillis; + } else { + let del = alarm.del === undefined ? settings.defaultDeleteExpiredTimers : alarm.del; + if (del) { + alarms.splice(alarmIndex, 1); + let state = require('Storage').readJSON('keytimer.json'); + state.timeLeft = 0; + require('Storage').writeJSON('keytimer.json', state); + } 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; + } + } + } + + // The updated 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); + } + + const pattern = alarm.vibrate || (alarm.timer ? settings.defaultTimerPattern : settings.defaultAlarmPattern); + require("buzz").pattern(pattern).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(); +} + +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); +} \ No newline at end of file diff --git a/apps/keytimer/timerview.js b/apps/keytimer/timerview.js index 48c896ba0..4910d805f 100644 --- a/apps/keytimer/timerview.js +++ b/apps/keytimer/timerview.js @@ -1,3 +1,10 @@ +const heatshrink = require("heatshrink"); +const BUTTON_ICONS = { + play: heatshrink.decompress(atob("jEYwMAkAGBnACBnwCBn+AAQPgAQPwAQP8AQP/AQXAAQPwAQP8AQP+AQgICBwQUCEAn4FggyBHAQ+CIgQ")), + pause: heatshrink.decompress(atob("jEYwMA/4BBAX4CEA")), + reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI=")) +}; + let common; function drawButtons() { @@ -10,11 +17,11 @@ function drawButtons() { .drawLine(g.getWidth() / 2, BAR_TOP, g.getWidth() / 2, g.getHeight()) //Draw the buttons - .drawImage(common.BUTTON_ICONS.reset, g.getWidth() / 4, BAR_TOP); - if (common.state.running) { - g.drawImage(common.BUTTON_ICONS.pause, g.getWidth() * 3 / 4, BAR_TOP); + .drawImage(BUTTON_ICONS.reset, g.getWidth() / 4, BAR_TOP); + if (common.running()) { + g.drawImage(BUTTON_ICONS.pause, g.getWidth() * 3 / 4, BAR_TOP); } else { - g.drawImage(common.BUTTON_ICONS.play, g.getWidth() * 3 / 4, BAR_TOP); + g.drawImage(BUTTON_ICONS.play, g.getWidth() * 3 / 4, BAR_TOP); } } @@ -38,8 +45,6 @@ function drawTimer() { if (hours >= 1) return `${parseInt(hours)}:${pad(minutes)}:${pad(seconds)}`; else return `${parseInt(minutes)}:${pad(seconds)}`; })(), g.getWidth() / 2, g.getHeight() / 2) - - if (timeLeft <= 0) load('keytimer-ring.js'); } let timerInterval; @@ -51,14 +56,14 @@ function setupTimerInterval() { setTimeout(() => { timerInterval = setInterval(drawTimer, 1000); drawTimer(); - }, common.timeLeft % 1000); + }, common.getTimeLeft() % 1000); } exports.show = function (callerCommon) { common = callerCommon; drawButtons(); drawTimer(); - if (common.state.running) { + if (common.running()) { setupTimerInterval(); } } @@ -71,37 +76,22 @@ function clearTimerInterval() { } exports.touch = (button, xy) => { - if (xy.y < 152) return; + if (xy !== undefined && xy.y < 152) return; if (button == 1) { //Reset the timer - let setTime = common.state.setTime; - let inputString = common.state.inputString; - common.state = common.STATE_DEFAULT; - common.state.setTime = setTime; - common.state.inputString = inputString; + common.deleteTimer(); clearTimerInterval(); require('keytimer-keys.js').show(common); } else { - if (common.state.running) { - //Record the exact moment that we paused - let now = (new Date()).getTime(); - common.state.pausedTime = now; - - //Stop the timer - common.state.running = false; + if (common.running()) { + common.pauseTimer(); clearTimerInterval(); - drawTimer(); - drawButtons(); } else { - //Start the timer and record when we started - let now = (new Date()).getTime(); - common.state.elapsedTime += common.state.pausedTime - common.state.startTime; - common.state.startTime = now; - common.state.running = true; - drawTimer(); + common.startTimer(common.getTimeLeft()); setupTimerInterval(); - drawButtons(); } + drawTimer(); + drawButtons(); } }; \ No newline at end of file