// touch listener for specific areas function touchListener(b, c) { // check if inside any area for (var i = 0; i < aaa.length; i++) { if (!(c.x < aaa[i].x0 || c.x > aaa[i].x1 || c.y < aaa[i].y0 || c.y > aaa[i].y1)) { // check if drawing ongoing if (drawingID > 0) { // check if interrupt is set if (aaa[i].interrupt) { // stop ongoing drawing drawingID++; if (ATID) ATID = clearTimeout(ATID); } else { // do nothing return; } } // give feedback Bangle.buzz(25); // execute action aaa[i].funct(); } } } // swipe listener for switching the displayed day function swipeListener(h, v) { // give feedback Bangle.buzz(25); // set previous or next day prevDays += h; if (prevDays < -1) prevDays = -1; // redraw draw(); } // day selection function daySelection() { var date = Date(startDate - prevDays * 864E5).toString().split(" "); E.showPrompt(date.slice(0, 3).join(" ") + "\n" + date[3] + "\n" + prevDays + /*LANG*/" days before today", { title: /*LANG*/"Select Day", buttons: { "<<7": 7, "<1": 1, "Ok": 0, "1>": -1, "7>>": -7 } }).then(function(v) { if (v) { prevDays += v; if (prevDays < -1) prevDays = -1; daySelection(); } else { fromMenu(); } }); } // open settings menu function openSettings() { // disable back behaviour to prevent bouncing on return backListener = () => {}; // open settings menu eval(require("Storage").read("sleeplog.settings.js"))(fromMenu); } // draw progress as bar, increment on undefined percentage function drawProgress(progress) { g.fillRect(19, 147, 136 * progress + 19, 149); } // (re)draw info data function drawInfo() { // set active info type var info = infoData[infoType % infoData.length]; // draw info g.clearRect(0, 69, 175, 105).reset() .setFont("6x8").setFontAlign(-1, -1) .drawString(info[0][0], 10, 70) .drawString(info[1][0], 10, 90) .setFont("12x20").setFontAlign(1, -1) .drawString((info[0][1] / 60 | 0) + "h " + (info[0][1] % 60) + "min", 166, 70) .drawString((info[1][1] / 60 | 0) + "h " + (info[1][1] % 60) + "min", 166, 90); // free ram info = undefined; } // draw graph for log segment function drawGraph(log, date, pos) { // set y position var y = pos ? 144 : 110; // clear area g.reset().clearRect(0, y, width, y + 33); // draw x axis g.drawLine(0, y + 19, width, y + 19); // draw x label var stepWidth = width / 12; var startHour = date.getHours() + (pos ? 0 : 12); for (var x = 0; x < 12; x++) { g.fillRect(x * stepWidth, y + 20, x * stepWidth, y + 22); g.setFontAlign(-1, -1).setFont("6x8") .drawString((startHour + x) % 24, x * stepWidth + 1, y + 24); } // set height and color values: // status: unknown, not worn, awake, light sleep, deep sleep, consecutive // color: black, red, green, cyan, blue, violet var heights = [0, 0.4, 0.6, 0.8, 1]; var colors = [0, 63488, 2016, 2047, 31, 32799]; // cycle through log log.forEach((entry, index, log) => { // calculate positions var x1 = Math.ceil((entry[0] - log[0][0]) / 72 * width); var x2 = Math.floor(((log[index + 1] || [date / 6E5])[0] - log[0][0]) / 72 * width); // calculate y2 position var y2 = y + 18 * (1 - heights[entry[1]]); // set color depending on status and consecutive sleep g.setColor(colors[entry[2] === 2 ? 5 : entry[1]]); // clear area, draw bar and top line g.clearRect(x1, y, x2, y + 18); g.fillRect(x1, y + 18, x2, y2).reset(); if (y + 18 !== y2) g.fillRect(x1, y2, x2, y2); }); } // draw information in an interruptable cycle function drawingCycle(calcDate, thisID, cycle, log) { // reset analysis timeout ID ATID = undefined; // check drawing ID to continue if (thisID !== drawingID) return; // check cycle if (!cycle) { /* read log on first cycle */ // set initial cycle cycle = 1; // read log log = slMod.readLog(calcDate - 864E5, calcDate); // draw progress drawProgress(0.6); } else if (cycle === 2) { /* draw stats on second cycle */ // read stats and process into info data infoData = slMod.getStats(calcDate, 0, log); infoData = [ [ [ /*LANG*/"consecutive\nsleeping", infoData.consecSleep], [ /*LANG*/"true\nsleeping", infoData.deepSleep + infoData.lightSleep] ], [ [ /*LANG*/"deep\nsleep", infoData.deepSleep], [ /*LANG*/"light\nsleep", infoData.lightSleep] ], [ [ /*LANG*/"awake", infoData.awakeTime], [ /*LANG*/"not worn", infoData.notWornTime] ] ]; // draw info drawInfo(); // draw progress drawProgress(0.9); } else if (cycle === 3) { /* segment log on third cycle */ // calculate segmentation date in 10min steps and index of the segmentation var segmDate = calcDate / 6E5 - 72; var segmIndex = log.findIndex(entry => entry[0] >= segmDate); // check if segmentation neccessary if (segmIndex > 0) { // split log log = [log.slice(segmIndex), log.slice(0, segmIndex)]; // add entry at segmentation point if (log[0][0] !== segmDate) log[0].unshift([segmDate, log[1][segmIndex - 1][1], log[1][segmIndex - 1][2]]); } else if (segmIndex < 0) { // set log as second log entry log = [ [], log ]; } else { // add entry at segmentation point if (log[0] !== segmDate) log.unshift([segmDate, 0, 0]); // set log as first log entry log = [log, []]; } // draw progress drawProgress(1); } else if (cycle === 4) { /* draw upper graph on fourth cycle */ drawGraph(log[0], calcDate, 0); } else if (cycle === 5) { /* draw upper graph on fifth cycle */ drawGraph(log[1], calcDate, 1); } else { /* stop cycle and set drawing finished */ drawingID = 0; // give feedback Bangle.buzz(25); } // initiate next cycle if defined if (thisID === drawingID) ATID = setTimeout(drawingCycle, 10, calcDate, drawingID, ++cycle, log); } // return from a menu function fromMenu() { // reset UI to custom mode Bangle.setUI(customUI); // enable back behaviour delayed to prevent bouncing setTimeout(() => backListener = load, 500); // redraw app draw(); } // draw app function draw() { // stop ongoing drawing drawingID++; if (ATID) ATID = clearTimeout(ATID); // clear app area g.reset().clearRect(0, 24, width, width); // set date to calculate data for var calcDate = new Date(startDate - prevDays * 864E5); // draw title g.setFont("12x20").setFontAlign(0, -1) .drawString( /*LANG*/"Night to " + require('locale').dow(calcDate, 1) + "\n" + require('locale').date(calcDate, 1), 87, 28); // reset graphics and define image string g.reset(); var imgStr = ""; // check which icon to set if (!global.sleeplog || sleeplog.conf.enabled !== true) { // set color and disabled service icon g.setColor(1, 0, 0); imgStr = "FBSBAOAAfwAP+AH3wD4+B8Hw+A+fAH/gA/wAH4AB+AA/wAf+APnwHw+D4Hx8A++AH/AA/gAH"; } else if (sleeplog.debug) { // set debugging icon imgStr = typeof sleeplog.debug === "object" ? "FBSBAB/4AQDAF+4BfvAX74F+CBf+gX/oFJKBf+gUkoF/6BSSgX/oFJ6Bf+gX/oF/6BAAgf/4" : // file "FBSBAP//+f/V///4AAGAABkAAZgAGcABjgAYcAGDgBhwAY4AGcABmH+ZB/mAABgAAYAAH///"; // console } // draw service and settings icon if (imgStr) g.drawImage(atob(imgStr), 2, 36); g.reset().drawImage(atob("FBSBAAAeAAPgAHwAB4AA8AAPAwDwcA+PAP/wH/4D/8B/8A/gAfwAP4AH8AD+AA/AAPgABwAA"), width - 22, 36); // show loading info with progresss bar g.reset().drawRect(7, 117, width - 8, 157) .setFont("6x8").setFontAlign(0, 0) .drawString( /*LANG*/ "calculating data ...\nplease be patient :)", 87, 133) .drawRect(17, 145, 157, 151); // draw first progress drawProgress(0.1); // initiate drawing cycle ATID = setTimeout(drawingCycle, 10, calcDate, drawingID, 0); } // define sleeplog module var slMod = require("sleeplog"); // read app timeout from settings var appTimeout = (require("Storage").readJSON("sleeplog.json", true) || {}).appTimeout; // set listener for back button var backListener = load; // define custom UI mode var customUI = { mode: "custom", back: backListener, touch: touchListener, swipe: swipeListener }; // define start values var startDate = slMod.getLastBreak(); // date to start from var prevDays = 0; // number of previous days to display var infoType = 0; // type of info data to display var infoData; // storage for info data var ATID; // analysis timeout ID var drawingID = 0; // drawing ID for ongoing process // get screen width and center (zero based) var width = g.getWidth() - 1; var center = width / 2 - 1; // set areas and actions array var aaa = [ // day selection { x0: 26, x1: width - 26, y0: 24, y1: 68, interrupt: true, funct: () => daySelection() }, // open settings { x0: width - 26, x1: width, y0: 24, y1: 68, interrupt: true, funct: () => openSettings() }, // change info type { x0: 0, x1: width, y0: 69, y1: 105, funct: () => { // change info type infoType++; // redraw info drawInfo(); } } ]; // clear and reset screen g.clear(true); // load and draw widgets Bangle.loadWidgets(); Bangle.drawWidgets(); // set UI in custom mode Bangle.setUI(customUI); // set app timeout if defined if (appTimeout) Bangle.setOptions({ lockTimeout: appTimeout, backlightTimeout: appTimeout }); // draw app draw();