{ Bangle.loadWidgets(); require("widget_utils").hide(); const resetImg = require("heatshrink").decompress(atob("kkkwIJGgwCBhkP/4EB4f//gIB/8P+EA/+Ah4BB8EAvkA/EAgPgh4EBh4RCgFwDAUHAwIABnADCgPAAgUcBBY9BAYUOuAEC8YDChgkCgPDFoUcBA88EgUDwJUBgF4hwDBg8AJYICBJoUegE+BgPAgPwgJkC+CCCn//J4X/DgUAsCbFA==")); const deleteImg = require("heatshrink").decompress(atob("mUywIROgP/8AGD//8AocwgYFDhkA4AFCBQMwAgMH/4ACwAjBAAQmBjAVCsEAgwFCBIMBwAFBBIQOBBIYYssEBwIYCjkA8AYCgeAuAYDuE4DAcOjgxDgPOGIk/GIkHwAYDgPgJQihBDAdzwAYDnFwDAcchwYDAgPADAUHgE4DARaBgyV1DBTzD/AIBAoRxBABgA==")); const okImg = require("heatshrink").decompress(atob("mEwwIWThACBiACBjgCBnACBnwCBvgCB/wCBv8AgP8AoMfwED+EPAIPAg/gv/+AYMP4P//+DBoOHAoPh+EHCwXw8AgB4ED/AmBn4CBFgV+AQg+CIgQUCj4CB+ACBh5HB+EwhEPwEB8F4jkHAoV8nwFBgfAn18AYIBBj/4AoZGBAoZdBApIRFDoopBg/AGopBCjwFCJohZFMoxxFPoqJFSopGBj4mCg6tBUwKEBAoI0BaoTZCDAb/MABg=")); const pauseImg = require("heatshrink").decompress(atob("mUywI2zh/8AIIFBgf/AIIMC//AAIIFBn/wAIIY/DH4Y9AGA=")); const playImg = require("heatshrink").decompress(atob("mUywIpmgYGF+AFEn4FEh/gDAn+Bgn/Bgk//gYE//ADAf/Bgn/Bgk///4DAn/wAYDBggFB/4YE/5TCDAQMCDARGDAoRTCDAQMCDAYsBDAYyCAooYCLAQYCAoQYCMgYwEDAQFDDAIFDDAR8FSogFEDAKuEQIYYCAok/AokPUAYYBAokAAooAkA")); const timerImg = require("heatshrink").decompress(atob("mEwwIoln////8AqcD4ABBArXgg4FCgIpBwAFHgAFBJwd//xUEIAQABj/4AocP+AFDg/gApIRFDos+vgFDvk+Aof4j4ECgPwh5TDL4IFCMopxG4ODAofD4YFD4/jApC/XAEQ=")); const alarmImg = require("heatshrink").decompress(atob("mEwwIWThACBiACBjgCBnACBnwCBvgCB/wCBv8AgP8AoMfwED+EPAIPAg/gv/+AYMP4P//+DBoOHAoPh+EHCwXw8AgB4ED/AmBn4CBFgV+AQl8gfAIgX4AoMfAoPwAoMPI4IFDwEB8AFBg4FGAYIBeEoQBCIYQBBgJ3BAYJnBv+HHYRtBIIXwgB6BwBZBLgJZBMoKhCWAhxCPoqJFSoqfBj4mCg6tBHAKEBAoI0BaoTZCDAb/MABg")); const alarmOnImg = require("heatshrink").decompress(atob("mEwwJC/ABMf////AFBAgIABgEBAoeAgYFD4EH8AUBAYMPwAFBgPwj4mC+H4jwEBgf8vAFCg/+vE8Aoc8AoUP/4FVDoQpFGopBFJopZFMopxFPoqJGAH4AGA=")); const alarmOffImg = require("heatshrink").decompress(atob("mEwwJC/ABMf////AFBAgIABgEBAoeAgYFD4EH8AUBAYMPwAFBgPwj8PE4X4j0/4AFBvEev4YCvE8Aoc8nn/+AFVDogpFGopBFJopZFMopxFPoqJGAH4AGA==")); const sched = require("sched"); const settings = sched.getSettings(); const TimePicker = require("timepicker").TimePicker; const Scroller = require("scroller").Scroller; const dowPicker = require("dowpicker").dowPicker; const appId = "mtimer"; let intID; let alarms; let timerIndexes = []; let alarmIndexes = []; let numActiveTimers = 0; let numActiveAlarms = 0; let mode = "timer"; // "timer" or "alarm" const hms2ms = function(hms) { let t = (hms.h * 3600000) + (hms.m * 60000) + (hms.s * 1000); if (hms.meridiem === "PM") { t += (3600000 * 12); // add 12 hours if we are p.m. } return t; }; const ms2hms = function(ms, mode) { const h = Math.floor(ms / 3600000); const m = Math.floor((ms / 60000) % 60); const s = Math.floor((ms / 1000) % 60); let meridiem = undefined; if (mode === "alarm") { if (ms >= 43200000) { //12h * 60min * 60sec * 1000ms is 12:00pm meridiem = "PM"; hms.h -= 12; } else { meridiem = "AM"; } } return { h: h, m: m, s: s, meridiem: meridiem }; }; const ms2String = function(ms, mode, includeSeconds) { const hms = ms2hms(ms, mode); if (hms.m < 10) hms.m = "0" + hms.m; if (hms.s < 10) hms.s = "0" + hms.s; if (includeSeconds === undefined) includeSeconds = true; if (mode === "timer" || mode === undefined) { return {time: (hms.h > 0 ? hms.h + ":" : "") + hms.m + (hms.m > 0 ? (includeSeconds ? ":" + hms.s : "") : ":" + hms.s), meridiem: undefined}; } else if (mode === "alarm") { return {time: hms.h + ":" + hms.m, meridiem: hms.meridiem}; } else if (mode === "timeto") { return {time: (hms.h > 0 ? hms.h + "h" : "") + hms.m + "m" + (hms.m > 0 ? (includeSeconds ? hms.s + "s" : "") : hms.s + "s"), meridiem: undefined}; } }; // Common: const loadAlarmsAndTimers = function() { sched.reload(); alarms = sched.getAlarms(); timerIndexes = []; alarmIndexes = []; numActiveTimers = 0; numActiveAlarms = 0; if (alarms.length > 0) { for (let i = alarms.length - 1; i >= 0; i--) { if (alarms[i].appid === appId) { if (alarms[i].timer != undefined) { timerIndexes.push(i); if (alarms[i].on === true) numActiveTimers++; } else { alarmIndexes.push(i); if (alarms[i].on === true) numActiveAlarms++; } } } } }; loadAlarmsAndTimers(); const saveAlarms = function() { sched.setAlarms(alarms); sched.reload(); }; const getCurrentTime = function() { const time = new Date(); return (time.getHours() * 3600000) + (time.getMinutes() * 60000) + (time.getSeconds() * 1000); }; const cancel = function() { let s = mode === "timer" ? timerScroller : alarmScroller; reMakeScroller(s); showScroller(s); }; const remove = function(idx) { let alarm; if (mode === "timer") { removeScroller(timerScroller); alarm = getTimer(idx); } else if (mode === "alarm") { removeScroller(alarmScroller); alarm = getAlarm(idx); } sched.setAlarm(alarm.id, undefined); loadAlarmsAndTimers(); if (mode === "timer") { reMakeScroller(timerScroller); showScroller(timerScroller); } else { reMakeScroller(alarmScroller); showScroller(alarmScroller); } }; const timeup = function() { currentTime = getCurrentTime(); for (let i = 0; i < timerIndexes.length; i++) { timer = getTimer(i); if (timer.on && timer.t <= currentTime) { //timer.on = false; timer.timer = 0; timer.on = false; s.scrollToCenter(i + 1); buzz(); } } sched.setAlarms(alarms); sched.reload(); }; const startRedraws = function(s) { if (intID) clearTimeout(intID); const redraw = function() { g.clearRect(Bangle.appRect); s.drawAll(); intID = setTimeout(redraw, 1000); } redraw(); }; const stopRedraws = function() { if (intID) clearTimeout(intID); intID = undefined; }; const showScroller = function(s) { stopRedraws(); if ((mode === "timer" && timerIndexes.length > 0) || (mode === "alarm" && alarmIndexes.length > 0)) { startRedraws(s); } s.reload(); s.drawAll(); } const removeScroller = function(s) { //if (mode === "timer") { stopRedraws(); //} s.remove(); g.reset(); g.clearRect(Bangle.appRect); }; const reMakeScroller = function(s) { s.c = mode === "timer" ? timerIndexes.length : alarmIndexes.length; s.reload(); }; const topBar = { minH: 24, maxH: 54, drawMin: (r) => drawTbMin(r), drawMax: (r) => drawTbMax(r), handler: (d, r) => topBarHandler(d, r), }; const drawTbMin = function(r) { g.clearRect(r.x, r.y, r.x2, r.y2); const h = r.y2 - r.y; g.setFont("Vector:" + h).setFontAlign(-1, -1).drawString(mode === "timer" ? "Timers" : "Alarms"); g.setColor(0, 0.5, 0.5).fillRect(r.x, r.y2, r.x2, r.y2 - 3).setColor(g.theme.fg); }; const drawTbMax = function(r) { g.clearRect(r.x, r.y, r.x2, r.y2); //g.setColor(0, 0, 0.25).fillRect(r.x, r.y, r.x + 96, r.y2); g.setColor(0, 1, 1); if (mode === "timer") { g.fillRect({x: r.x, y: r.y, x2: r.x + 47, y2: r.y2, r: 10}); g.setColor(0, 0, 0).fillRect({x: r.x + 3, y: r.y + 3, x2: r.x + 44, y2: r.y2, r: 8}); } else { g.fillRect({x: r.x + 48, y: r.y, x2: r.x + 99, y2: r.y2, r: 10}); g.setColor(0, 0, 0).fillRect({x: r.x + 51, y: r.y + 3, x2: r.x + 96, y2: r.y2, r: 8}); } g.setColor(0, 1, 1).fillRect(r.x, r.y2, r.x2, r.y2 - 3); // bottom line const midY = (r.y + r.y2) / 2; const midX = (r.x2 - 20); g.setColor(g.theme.fg); if (mode !== "timer") g.setColor(0.5, 0.5, 0.5); g.drawImage(timerImg, r.x, midY - 24).setColor(g.theme.fg); if (mode !== "alarm") g.setColor(0.5, 0.5, 0.5); g.drawImage(alarmImg, r.x + 50, midY - 24).setColor(g.theme.fg); g.setFontAlign(0, 0).setFont("Vector:40").drawString("+", midX + 3, midY + 3); g.setBgColor(g.theme.bg); }; const topBarHandler = function(d, r) { if (d.x < r.x + 50) { // timer tab if (mode !== "timer") { removeScroller(alarmScroller); mode = "timer"; showScroller(timerScroller); } } else if (d.x > r.x + 50 && d.x < r.x + 100) { // alarm tab if (mode !== "alarm") { removeScroller(timerScroller); mode = "alarm"; showScroller(alarmScroller); } } else if (d.x > r.x2 - 50) { removeScroller(mode === "timer" ? timerScroller : alarmScroller); if (mode === "timer") { timerTimePicker.setTime({h:0, m:0, s: 0, meridiem: undefined}); timerTimePicker.draw(); } else if (mode === "alarm") { newAlarmTimePicker.setTime({h: 8, m: 0, s: 0, meridiem: "AM"}); //TODO change this to a default alarm time variable newAlarmTimePicker.draw(); } } }; const drawButton1 = function(r) { const midX = (r.x + r.x2) / 2; const midY = (r.y + r.y2) / 2; const halfImg = 25; g.setColor(1, 0, 0).fillRect({ x: r.x, y: r.y + 4, x2: r.x2 - 3, y2: r.y2 - 3}).setColor(g.theme.fg); g.drawImage(deleteImg, midX - halfImg, midY - halfImg); }; const drawButton2 = function(r) { const midX = (r.x + r.x2) / 2; const midY = (r.y + r.y2) / 2; const halfImg = 16; g.setColor(0, 0, 1).fillRect({ x: r.x, y: r.y + 4, x2: r.x2 - 3, y2: r.y2 - 3}).setColor(g.theme.fg); g.drawImage(resetImg, midX - halfImg, midY - halfImg); }; const drawEmpty = function(r, mode) { const midX = (r.x2 + r.x) / 2; const midY = (r.y2 + r.y) / 2; g.setColor(0.25, 0.25, 0.25); // g.setFontAlign(0, 0).setFont("Vector:80"); // g.drawString("o", midX, midY); // g.drawString("/", midX, midY); // g.drawImage(nullImg, midX, midY); g.setColor(-1); g.drawCircle(midX, midY - 20, 30); g.drawLine(midX + 30, midY - 50, midX - 30, midY + 10); let str; if (mode === "timer") str = "No Timers"; else if (mode === "alarm") str = "No Alarms"; g.setColor(0.5, 0.5, 0.5).setFont("Vector:20").drawString(str, midX, r.y2 - 30); }; const drawTimeUp = function(timer, r) { const midY = r.y + (r.h / 2); g.clearRect(r.x, r.y, r.x2, r.y2); g.setColor(0, 0.5, 0.5).fillRect({ x: r.x, y: r.y, x2: r.x2, y2: r.y2, r: 12 }); g.setColor(g.theme.bg).fillRect({ x: r.x + 5, y: r.y + 6, x2: r.x2 - 5, y2: r.y2 - 6, r: 7 }).setColor(g.theme.fg); g.drawImage(okImg, r.x2 - 51, midY - 24); const time = getCurrentTime() - timer.t; const timeStr = ms2String(time, "timer"); const timerStr = ms2String(timer.data.ot, "timer"); g.setFontAlign(-1, -1).setFont("Vector:20").drawString(timerStr.time, r.x + 6, r.y + 7); g.setFontAlign(-1, 1).setFont("Vector:27").drawString(timeStr.time, r.x + 6, r.y2 - 3); g.setBgColor(g.theme.bg).setColor(g.theme.fg); }; const buzz = function() { if (settings.unlockAtBuzz) Bangle.setLocked(false); const pattern = settings.defaultAlarmPattern; Bangle.buzzCount = settings.buzzCount; const doBuzz = function() { require("buzz").pattern(pattern).then(() => { Bangle.buzzCount--; if (Bangle.buzzId) clearTimeout(Bangle.buzzId); if (Bangle.buzzCount > 0) { Bangle.buzzId = setTimeout(doBuzz, settings.buzzIntervalMillis); } else { stopBuzz(); return; } }); }; doBuzz(); }; const stopBuzz = function() { if (Bangle.buzzId) { clearTimeout(Bangle.buzzId); delete Bangle.buzzId; } if (Bangle.buzzCount) delete Bangle.buzzCount; return; }; // Timers: const getTimer = function(idx) { return alarms[timerIndexes[idx]]; }; const timerTimePicker = new TimePicker({ meridiem: undefined, start: (hms) => setTimer(hms2ms(hms)), cancel: cancel, }); const drawTimerStart = function(x, y) { g.drawImage(playImg, x - 24, y - 24); }; const drawTimerPause = function(x, y) { g.drawImage(pauseImg, x - 24, y - 24); }; const drawTimerProgress = function(value, maxValue, x, y) { const len = 115; const t = 16; const x2 = x + len; const y2 = y + t; const progLen = ((len - 4) / maxValue) * value; g.setColor(0, 1, 1).fillRect({ x: x, y: y, x2: x2, y2: y2, r: t }); g.setColor(g.theme.bg).fillRect({ x: x + 2, y: y + 2, x2: x2 - 2, y2: y2 - 2, r: t - 2 }).setColor(g.theme.fg); g.setColor(0, 1, 1).fillRect({ x: x + 4, y: y + 4, x2: x + progLen, y2: y2 - 4, r: t - 4 }); g.setColor(g.theme.fg); }; const drawTimer = function(idx, r, dragging) { const timer = getTimer(idx); if (timer.timer === 0) { drawTimeUp(timer, r); return; } const midY = r.y + (r.h / 2); g.clearRect(r.x, r.y, r.x2, r.y2); g.setColor(g.theme.bg2).fillRect(r.x + 3, r.y + 4, r.x2 - 3, r.y2 - 3).setColor(g.theme.fg); let time; if (timer.on) { drawTimerPause(r.x2 - 25, midY); time = sched.getTimeToAlarm(timer); } else { drawTimerStart(r.x2 - 25, midY); time = timer.timer; } const str = ms2String(time, "timer"); g.setColor(0, 0, 1); drawTimerProgress(time, timer.data.ot, r.x + 4, r.y2 - 30); g.setColor(g.theme.fg); g.setFontAlign(-1, -1).setFont("Vector:27").drawString(str.time, r.x + 4, r.y + 10); }; const playPauseTimer = function(timer) { if (timer.on) { changeTimerTime(timer); timer.on = false; numActiveTimers--; } else { changeTimerTime(timer); timer.on = true; numActiveTimers++; } saveAlarms(); showScroller(timerScroller); }; const changeTimerTime = function(timer, newTime) { const currentTime = getCurrentTime(); const timerTime = sched.getTimeToAlarm(timer); if (timerTime) { timer.timer = newTime === undefined ? timerTime : newTime; } timer.t = (currentTime + timer.timer) % 86400000; }; const timerTouchHandler = function(idx, r, x) { const timer = getTimer(idx); if (x > r.x2 - 40) { if (timer.timer === 0) dismissTimer(timer); else playPauseTimer(timer); } if (timer.on === false) { if (x < r.x + 40) { editTimer(timer); } } }; const setTimer = function(t) { if (t > 1000) { // don't make timers less than 1 second const id = Math.round(Date.now()); sched.setAlarm(id, { appid: appId, timer: t, on: true, del: settings.defaultDeleteExpiredTimers, rp: false, as: false, dow: 0b1111111, last: 0, data: { ot: t }, js: `try { if (__FILE__ === 'mtimer.app.js') { Bangle.emit("timeup"); } else { load("sched.js"); } } catch { load("sched.js"); }` }); //ot: original time } loadAlarmsAndTimers(); reMakeScroller(timerScroller); showScroller(timerScroller); }; const resetTimer = function(timer) { changeTimerTime(timer, timer.data.ot); if (timer.on) playPauseTimer(timer); saveAlarms(); showScroller(timerScroller); }; const editTimer = function(timer) { console.log("edit function, timer: " + timer); }; const timerScroller = new Scroller({ h: 75, c: timerIndexes.length, draw: (i, r, d) => drawTimer(i, r, d), drawEmpty: (r) => drawEmpty(r, "timer"), select: (i, r, x) => timerTouchHandler(i, r, x), button1: (i) => remove(i), button2: (i) => resetTimer(i), drawButton1: (r) => drawButton1(r), drawButton2: (r) => drawButton2(r), topBar: topBar, }); const dismissTimer = function(timer) { stopBuzz(); timer.timer = timer.data.ot; sched.setAlarms(alarms); sched.reload(); }; // Alarms: const getAlarm = function(idx) { return alarms[alarmIndexes[idx]]; }; const newAlarmTimePicker = new TimePicker({ meridiem: "AM", start: (hms) => setAlarm(hms2ms(hms)), cancel: cancel }); const changeAlarmTime = function(alarm) { const hms = ms2hms(alarm.t, "alarm"); const tp = new TimePicker({ meridiem: hms.meridiem, h: hms.h, m: hms.m, s: 0, start: (hms) => { alarm.t = hms2ms(hms); saveAlarms(); loadAlarmsAndTimers(); g.clearRect(Bangle.appRect); //showScroller(alarmScroller); showDowPicker(alarm); }, cancel: cancel }) tp.draw(); }; const drawAlarm = function(idx, r, dragging) { const alarm = getAlarm(idx); g.setColor(g.theme.bg2).fillRect(r.x + 3, r.y + 4, r.x2 - 3, r.y2 - 3).setColor(g.theme.fg); let timeToAlarm; if (alarm.on) { g.setColor(0, 1, 0.5).drawImage(alarmOnImg, r.x2 - 50, r.y + 5).setColor(-1); timeToAlarm = sched.getTimeToAlarm(alarm); } else { g.setColor(0.5, 0.5, 0.5).drawImage(alarmOffImg, r.x2 - 50, r.y + 5).setColor(-1); timeToAlarm = 0; } const timeStr = ms2String(alarm.t, "alarm"); g.setColor(g.theme.fg).setFontAlign(-1, 1).setFont("Vector:35"); const sw = g.stringWidth(timeStr.time); g.drawString(timeStr.time, r.x + 4, r.y + 40); g.setFont("Vector:18").drawString(timeStr.meridiem, r.x + 4 + sw, r.y + 35); if (!dragging) { const timeToAlarmStr = timeToAlarm > 0 ? "In " + ms2String(timeToAlarm, "timeto", false).time : ""; g.setFont("Vector:20"); g.drawString(timeToAlarmStr, r.x + 4, r.y + 55); if (alarm.rp) { const dow = ["S", "M", "T", "W", "T", "F", "S"]; g.setFontAlign(0, 1); g.setColor(-1); for (let i = 0, x = 13; i <= 6; i++, x += 25) { if ((alarm.dow >> i) & 1) { // check bit g.setColor(-1); } else { g.setColor(0.25, 0.5, 0.5); } g.drawString(dow[i], r.x + x, r.y2-5); } } } g.setBgColor(g.theme.bg).setColor(g.theme.fg); }; const showDowPicker = function(alarm) { dowPicker(alarm, () => { saveAlarms(); loadAlarmsAndTimers(); g.clearRect(Bangle.appRect); showScroller(alarmScroller); }, () => changeAlarmTime(alarm) ) }; const alarmTouchHandler = function(idx, r, x) { if (x < r.x2 - 40) { stopRedraws(); Bangle.setUI(); removeScroller(alarmScroller); showDowPicker(alarms[alarmIndexes[idx]]) } else if (x > r.x2 - 40) { alarms[alarmIndexes[idx]].on = !alarms[alarmIndexes[idx]].on; saveAlarms(); loadAlarmsAndTimers(); showScroller(alarmScroller); } }; const setAlarm = function(t) { const id = Math.round(Date.now()); sched.setAlarm(id, { appid: appId, t: t, on: true, rp: false, del: false, dow: 0b1111111, as: settings.defaultAutoSnooze, vibrate: settings.defaultAlarmPattern, last: 0, js: `try { if (__FILE__ === 'mtimer.app.js') { Bangle.emit("timeup"); } else { load("sched.js"); } } catch { load("sched.js"); }` }); //ot: original time loadAlarmsAndTimers(); reMakeScroller(alarmScroller); showScroller(alarmScroller); }; const alarmScroller = new Scroller({ h: 95, c: alarmIndexes.length, draw: (i, r, d) => drawAlarm(i, r, d), drawEmpty: (r) => drawEmpty(r, "alarm"), select: (i, r, x) => alarmTouchHandler(i, r, x), button1: (i) => remove(i), button2: undefined, drawButton1: (r) => drawButton1(r), drawButton2: undefined, topBar: topBar, }); // Start Bangle.on("timeup", timeup); showScroller(timerScroller); }