mirror of https://github.com/espruino/BangleApps
355 lines
9.5 KiB
JavaScript
355 lines
9.5 KiB
JavaScript
// 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();
|