mirror of https://github.com/espruino/BangleApps
Merge branch 'master' of github.com:espruino/BangleApps
commit
625e52514d
|
@ -10,6 +10,12 @@ Standard # of drag handlers: 0-10 (Default: 0, must be changed for backswipe to
|
|||
|
||||
Standard # of handlers settings are used to fine tune when backswipe should trigger the back function. E.g. when using a keyboard that works on drags, we don't want the backswipe to trigger when we just wanted to select a letter. This might not be able to cover all cases however.
|
||||
|
||||
To get an indication for standard # of handlers `Bangle["#onswipe"]` and `Bangle["#ondrag"]` can be entered in the [Espruino Web IDE](https://www.espruino.com/ide) console field. They return `undefined` if no handler is active, a function if one is active, or a list of functions if multiple are active. Calling this on the clock app is a good start.
|
||||
|
||||
## TODO
|
||||
|
||||
- Possibly add option to tweak standard # of handlers on per app basis.
|
||||
|
||||
## Creator
|
||||
Kedlub
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial fork from messages_light
|
|
@ -0,0 +1,24 @@
|
|||
# Messages overlay app
|
||||
|
||||
This app handles the display of messages and message notifications as an overlay pop up.
|
||||
|
||||
It is a GUI replacement for the `messages` apps.
|
||||
|
||||
Messages are ephemeral and not stored on the Bangle.
|
||||
|
||||
## Usage
|
||||
|
||||
Close app by tapping the X and scroll by swiping. The border of the pop up changes color if the Bangle is locked. The color depends on your currently active theme.
|
||||
|
||||
## Firmware hint
|
||||
Current stable firmware draws incorrect colors for emojis. Nightly firmware builds correct this.
|
||||
|
||||
## Low memory mode
|
||||
|
||||
If free memory is below 2000 blocks, the overlay automatically only uses 1 bit depth. Default uses roundabout 1300 blocks, while low memory mode uses about 600.
|
||||
|
||||
## Creator
|
||||
|
||||
[halemmerich](https://github.com/halemmerich)
|
||||
Forked from messages_light by Rarder44
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,7 @@
|
|||
//override require to filter require("message")
|
||||
global.require_real=global.require;
|
||||
global.require = (_require => file => {
|
||||
if (file==="messages") file = "messagesoverlay";
|
||||
return _require(file);
|
||||
})(require);
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
/* MESSAGES is a list of:
|
||||
{id:int,
|
||||
src,
|
||||
title,
|
||||
subject,
|
||||
body,
|
||||
sender,
|
||||
tel:string,
|
||||
new:true // not read yet
|
||||
}
|
||||
*/
|
||||
|
||||
const ovrx = 10;
|
||||
const ovry = 10;
|
||||
const ovrw = g.getWidth()-2*ovrx;
|
||||
const ovrh = g.getHeight()-2*ovry;
|
||||
let _g = g;
|
||||
|
||||
let lockListener;
|
||||
let quiet;
|
||||
|
||||
let LOG = function() {
|
||||
//print.apply(null, arguments);
|
||||
};
|
||||
|
||||
let isQuiet = function(){
|
||||
if (quiet == undefined) quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet;
|
||||
return quiet;
|
||||
};
|
||||
|
||||
let settings = {
|
||||
fontSmall:"6x8",
|
||||
fontMedium:"Vector:14",
|
||||
fontBig:"Vector:20",
|
||||
fontLarge:"Vector:30",
|
||||
};
|
||||
|
||||
let eventQueue = [];
|
||||
let callInProgress = false;
|
||||
|
||||
let show = function(ovr){
|
||||
let img = ovr;
|
||||
if (ovr.getBPP() == 1) {
|
||||
img = ovr.asImage();
|
||||
img.palette = new Uint16Array([_g.theme.fg,_g.theme.bg]);
|
||||
}
|
||||
Bangle.setLCDOverlay(img, ovrx, ovry);
|
||||
};
|
||||
|
||||
let manageEvent = function(ovr, event) {
|
||||
event.new = true;
|
||||
|
||||
LOG("manageEvent");
|
||||
if (event.id == "call") {
|
||||
showCall(ovr, event);
|
||||
return;
|
||||
}
|
||||
switch (event.t) {
|
||||
case "add":
|
||||
eventQueue.unshift(event);
|
||||
|
||||
if (!callInProgress)
|
||||
showMessage(ovr, event);
|
||||
break;
|
||||
|
||||
case "modify":
|
||||
let find = false;
|
||||
eventQueue.forEach(element => {
|
||||
if (element.id == event.id) {
|
||||
find = true;
|
||||
Object.assign(element, event);
|
||||
}
|
||||
});
|
||||
if (!find)
|
||||
eventQueue.unshift(event);
|
||||
|
||||
if (!callInProgress)
|
||||
showMessage(ovr, event);
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
if (eventQueue.length == 0 && !callInProgress)
|
||||
next(ovr);
|
||||
|
||||
if (!callInProgress && eventQueue[0] !== undefined && eventQueue[0].id == event.id)
|
||||
next(ovr);
|
||||
|
||||
else {
|
||||
eventQueue.length = 0; // empty existing queue
|
||||
eventQueue.forEach(element => {
|
||||
if (element.id != event.id)
|
||||
neweventQueue.push(element);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case "musicstate":
|
||||
case "musicinfo":
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let roundedRect = function(ovr, x,y,w,h,filled){
|
||||
var poly = [
|
||||
x,y+4,
|
||||
x+4,y,
|
||||
x+w-5,y,
|
||||
x+w-1,y+4,
|
||||
x+w-1,y+h-5,
|
||||
x+w-5,y+h-1,
|
||||
x+4,y+h-1,
|
||||
x,y+h-5,
|
||||
x,y+4
|
||||
];
|
||||
ovr.drawPoly(poly,true);
|
||||
if (filled) ovr.fillPoly(poly,true);
|
||||
};
|
||||
|
||||
let drawScreen = function(ovr, title, titleFont, src, iconcolor, icon){
|
||||
ovr.setBgColor(ovr.theme.bg2);
|
||||
ovr.clearRect(2,2,ovr.getWidth()-3,37);
|
||||
|
||||
ovr.setColor(ovr.theme.fg2);
|
||||
ovr.setFont(settings.fontSmall);
|
||||
ovr.setFontAlign(0,-1);
|
||||
|
||||
let textCenter = (ovr.getWidth()+35-26)/2;
|
||||
|
||||
if (src) {
|
||||
let shortened = src;
|
||||
while (ovr.stringWidth(shortened) > ovr.getWidth()-80) shortened = shortened.substring(0,shortened.length-2);
|
||||
if (shortened.length != src.length) shortened += "...";
|
||||
ovr.drawString(shortened, textCenter, 2);
|
||||
}
|
||||
|
||||
ovr.setFontAlign(0,0);
|
||||
ovr.setFont(titleFont);
|
||||
if (title) ovr.drawString(title, textCenter, 38/2 + 5);
|
||||
|
||||
ovr.setColor(ovr.theme.fg2);
|
||||
|
||||
ovr.setFont(settings.fontMedium);
|
||||
roundedRect(ovr, ovr.getWidth()-26,5,22,30,false);
|
||||
ovr.setFont("Vector:16");
|
||||
ovr.drawString("X",ovr.getWidth()-14,21);
|
||||
|
||||
ovr.setColor("#888");
|
||||
roundedRect(ovr, 5,5,30,30,true);
|
||||
ovr.setColor(ovr.getBPP() != 1 ? iconcolor : ovr.theme.bg2);
|
||||
ovr.drawImage(icon,8,8);
|
||||
};
|
||||
|
||||
let showMessage = function(ovr, msg) {
|
||||
LOG("showMessage");
|
||||
ovr.setBgColor(ovr.theme.bg);
|
||||
|
||||
if (typeof msg.CanscrollDown === "undefined")
|
||||
msg.CanscrollDown = false;
|
||||
if (typeof msg.CanscrollUp === "undefined")
|
||||
msg.CanscrollUp = false;
|
||||
|
||||
// Normal text message display
|
||||
let title = msg.title,
|
||||
titleFont = settings.fontLarge,
|
||||
lines;
|
||||
if (title) {
|
||||
let w = ovr.getWidth() - 35 - 26;
|
||||
if (ovr.setFont(titleFont).stringWidth(title) > w)
|
||||
titleFont = settings.fontMedium;
|
||||
if (ovr.setFont(titleFont).stringWidth(title) > w) {
|
||||
lines = ovr.wrapString(title, w);
|
||||
title = (lines.length > 2) ? lines.slice(0, 2).join("\n") + "..." : lines.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
drawScreen(ovr, title, titleFont, msg.src || /*LANG*/ "Message", require("messageicons").getColor(msg), require("messageicons").getImage(msg));
|
||||
|
||||
if (!isQuiet() && msg.new) {
|
||||
msg.new = false;
|
||||
Bangle.buzz();
|
||||
}
|
||||
|
||||
drawMessage(ovr, msg);
|
||||
};
|
||||
|
||||
let drawBorder = function(ovr) {
|
||||
if (Bangle.isLocked())
|
||||
ovr.setColor(ovr.theme.fgH);
|
||||
else
|
||||
ovr.setColor(ovr.theme.fg);
|
||||
ovr.drawRect(0,0,ovr.getWidth()-1,ovr.getHeight()-1);
|
||||
ovr.drawRect(1,1,ovr.getWidth()-2,ovr.getHeight()-2);
|
||||
show(ovr);
|
||||
if (!isQuiet()) Bangle.setLCDPower(1);
|
||||
};
|
||||
|
||||
let showCall = function(ovr, msg) {
|
||||
LOG("showCall");
|
||||
LOG(msg);
|
||||
|
||||
if (msg.t == "remove") {
|
||||
LOG("hide call screen");
|
||||
next(ovr); //dont shift
|
||||
return;
|
||||
}
|
||||
|
||||
callInProgress = true;
|
||||
|
||||
let title = msg.title,
|
||||
titleFont = settings.fontLarge,
|
||||
lines;
|
||||
if (title) {
|
||||
let w = ovr.getWidth() - 35 -26;
|
||||
if (ovr.setFont(titleFont).stringWidth(title) > w)
|
||||
titleFont = settings.fontMedium;
|
||||
if (ovr.setFont(titleFont).stringWidth(title) > w) {
|
||||
lines = ovr.wrapString(title, w);
|
||||
title = (lines.length > 2) ? lines.slice(0, 2).join("\n") + "..." : lines.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
drawScreen(ovr, title, titleFont, msg.src || /*LANG*/ "Message", require("messageicons").getColor(msg), require("messageicons").getImage(msg));
|
||||
|
||||
stopCallBuzz();
|
||||
if (!isQuiet()) {
|
||||
if (msg.new) {
|
||||
msg.new = false;
|
||||
if (callBuzzTimer) clearInterval(callBuzzTimer);
|
||||
callBuzzTimer = setInterval(function() {
|
||||
Bangle.buzz(500);
|
||||
}, 1000);
|
||||
|
||||
Bangle.buzz(500);
|
||||
}
|
||||
}
|
||||
drawMessage(ovr, msg);
|
||||
};
|
||||
|
||||
let next = function(ovr) {
|
||||
LOG("next");
|
||||
stopCallBuzz();
|
||||
|
||||
if (!callInProgress)
|
||||
eventQueue.shift();
|
||||
|
||||
callInProgress = false;
|
||||
if (eventQueue.length == 0) {
|
||||
LOG("no element in queue - closing");
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
showMessage(ovr, eventQueue[0]);
|
||||
};
|
||||
|
||||
let showMapMessage = function(ovr, msg) {
|
||||
ovr.clearRect(2,2,ovr.getWidth()-3,ovr.getHeight()-3);
|
||||
drawMessage(ovr, {
|
||||
body: "Not implemented!"
|
||||
});
|
||||
};
|
||||
|
||||
let callBuzzTimer = null;
|
||||
let stopCallBuzz = function() {
|
||||
if (callBuzzTimer) {
|
||||
clearInterval(callBuzzTimer);
|
||||
callBuzzTimer = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
let drawTriangleUp = function(ovr) {
|
||||
ovr.reset();
|
||||
ovr.fillPoly([ovr.getWidth()-9, 46,ovr.getWidth()-14, 56,ovr.getWidth()-4, 56]);
|
||||
};
|
||||
|
||||
let drawTriangleDown = function(ovr) {
|
||||
ovr.reset();
|
||||
ovr.fillPoly([ovr.getWidth()-9, ovr.getHeight()-6, ovr.getWidth()-14, ovr.getHeight()-16, ovr.getWidth()-4, ovr.getHeight()-16]);
|
||||
};
|
||||
|
||||
let scrollUp = function(ovr) {
|
||||
msg = eventQueue[0];
|
||||
LOG("up", msg);
|
||||
if (typeof msg.FirstLine === "undefined")
|
||||
msg.FirstLine = 0;
|
||||
if (typeof msg.CanscrollUp === "undefined")
|
||||
msg.CanscrollUp = false;
|
||||
|
||||
if (!msg.CanscrollUp) return;
|
||||
|
||||
msg.FirstLine = msg.FirstLine > 0 ? msg.FirstLine - 1 : 0;
|
||||
|
||||
drawMessage(ovr, msg);
|
||||
};
|
||||
|
||||
let scrollDown = function(ovr) {
|
||||
msg = eventQueue[0];
|
||||
LOG("down", msg);
|
||||
if (typeof msg.FirstLine === "undefined")
|
||||
msg.FirstLine = 0;
|
||||
if (typeof msg.CanscrollDown === "undefined")
|
||||
msg.CanscrollDown = false;
|
||||
|
||||
if (!msg.CanscrollDown) return;
|
||||
|
||||
msg.FirstLine = msg.FirstLine + 1;
|
||||
drawMessage(ovr, msg);
|
||||
};
|
||||
|
||||
let drawMessage = function(ovr, msg) {
|
||||
let MyWrapString = function(str, maxWidth) {
|
||||
str = str.replace("\r\n", "\n").replace("\r", "\n");
|
||||
return ovr.wrapString(str, maxWidth);
|
||||
};
|
||||
|
||||
if (typeof msg.FirstLine === "undefined") msg.FirstLine = 0;
|
||||
|
||||
let bodyFont = typeof msg.bodyFont === "undefined" ? settings.fontMedium : msg.bodyFont;
|
||||
let Padding = 3;
|
||||
if (typeof msg.lines === "undefined") {
|
||||
ovr.setFont(bodyFont);
|
||||
msg.lines = MyWrapString(msg.body, ovr.getWidth() - (Padding * 2));
|
||||
if (msg.lines.length <= 2) {
|
||||
bodyFont = ovr.getFonts().includes("Vector") ? "Vector:20" : "6x8:3";
|
||||
ovr.setFont(bodyFont);
|
||||
msg.lines = MyWrapString(msg.body, ovr.getWidth() - (Padding * 2));
|
||||
msg.bodyFont = bodyFont;
|
||||
}
|
||||
}
|
||||
|
||||
let NumLines = 7;
|
||||
|
||||
let linesToPrint = (msg.lines.length > NumLines) ? msg.lines.slice(msg.FirstLine, msg.FirstLine + NumLines) : msg.lines;
|
||||
|
||||
let yText = 40;
|
||||
|
||||
ovr.setBgColor(ovr.theme.bg);
|
||||
ovr.setColor(ovr.theme.fg);
|
||||
ovr.clearRect(2, yText, ovrw-3, ovrh-3);
|
||||
let xText = Padding;
|
||||
yText += Padding;
|
||||
ovr.setFont(bodyFont);
|
||||
let HText = ovr.getFontHeight();
|
||||
|
||||
yText = ((ovrh - yText) / 2) - (linesToPrint.length * HText / 2) + yText;
|
||||
|
||||
if (linesToPrint.length <= 3) {
|
||||
ovr.setFontAlign(0, -1);
|
||||
xText = ovr.getWidth() / 2;
|
||||
} else
|
||||
ovr.setFontAlign(-1, -1);
|
||||
|
||||
|
||||
linesToPrint.forEach((line, i) => {
|
||||
ovr.drawString(line, xText, yText + HText * i);
|
||||
});
|
||||
|
||||
if (msg.FirstLine != 0) {
|
||||
msg.CanscrollUp = true;
|
||||
drawTriangleUp(ovr);
|
||||
} else
|
||||
msg.CanscrollUp = false;
|
||||
|
||||
if (msg.FirstLine + linesToPrint.length < msg.lines.length) {
|
||||
msg.CanscrollDown = true;
|
||||
drawTriangleDown(ovr);
|
||||
} else
|
||||
msg.CanscrollDown = false;
|
||||
show(ovr);
|
||||
if (!isQuiet()) Bangle.setLCDPower(1);
|
||||
};
|
||||
|
||||
let getSwipeHandler = function(ovr){
|
||||
return (lr, ud) => {
|
||||
if (ud == 1) {
|
||||
scrollUp(ovr);
|
||||
} else if (ud == -1){
|
||||
scrollDown(ovr);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let getTouchHandler = function(ovr){
|
||||
return (_, xy) => {
|
||||
if (xy.y < ovry + 40){
|
||||
next(ovr);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let touchHandler;
|
||||
let swipeHandler;
|
||||
|
||||
let restoreHandler = function(event){
|
||||
if (backup[event]){
|
||||
Bangle["#on" + event]=backup[event];
|
||||
backup[event] = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
let backupHandler = function(event){
|
||||
if (eventQueue.length > 1 && ovr) return; // do not backup, overlay is already up
|
||||
backup[event] = Bangle["#on" + event];
|
||||
Bangle.removeAllListeners(event);
|
||||
};
|
||||
|
||||
let cleanup = function(){
|
||||
if (lockListener) {
|
||||
Bangle.removeListener("lock", lockListener);
|
||||
lockListener = undefined;
|
||||
}
|
||||
restoreHandler("touch");
|
||||
restoreHandler("swipe");
|
||||
restoreHandler("drag");
|
||||
|
||||
if (touchHandler) {
|
||||
Bangle.removeListener("touch", touchHandler);
|
||||
touchHandler = undefined;
|
||||
}
|
||||
if (swipeHandler) {
|
||||
Bangle.removeListener("swipe", swipeHandler);
|
||||
swipeHandler = undefined;
|
||||
}
|
||||
Bangle.setLCDOverlay();
|
||||
ovr = undefined;
|
||||
quiet = undefined;
|
||||
};
|
||||
|
||||
let backup = {};
|
||||
|
||||
let main = function(ovr, event) {
|
||||
LOG("Main", event, settings);
|
||||
|
||||
if (!lockListener) {
|
||||
lockListener = function (){
|
||||
drawBorder(ovr);
|
||||
};
|
||||
Bangle.on('lock', lockListener);
|
||||
}
|
||||
backupHandler("touch");
|
||||
backupHandler("swipe");
|
||||
backupHandler("drag");
|
||||
|
||||
if (touchHandler) Bangle.removeListener("touch",touchHandler);
|
||||
if (swipeHandler) Bangle.removeListener("swipe",swipeHandler);
|
||||
touchHandler = getTouchHandler(ovr);
|
||||
swipeHandler = getSwipeHandler(ovr);
|
||||
Bangle.on('touch', touchHandler);
|
||||
Bangle.on('swipe', swipeHandler);
|
||||
|
||||
if (event !== undefined){
|
||||
drawBorder(ovr);
|
||||
manageEvent(ovr, event);
|
||||
} else {
|
||||
LOG("No event given");
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
let ovr;
|
||||
|
||||
exports.pushMessage = function(event) {
|
||||
if( event.id=="music") return require_real("messages").pushMessage(event);
|
||||
|
||||
bpp = 4;
|
||||
if (process.memory().free < 2000) bpp = 1;
|
||||
|
||||
if (!ovr) {
|
||||
ovr = Graphics.createArrayBuffer(ovrw, ovrh, bpp, {
|
||||
msb: true
|
||||
});
|
||||
} else {
|
||||
ovr.clear();
|
||||
}
|
||||
|
||||
g = ovr;
|
||||
|
||||
if (bpp == 4)
|
||||
ovr.theme = g.theme;
|
||||
else
|
||||
ovr.theme = { fg:0, bg:1, fg2:1, bg2:0, fgH:1, bgH:0 };
|
||||
|
||||
main(ovr, event);
|
||||
|
||||
g = _g;
|
||||
};
|
||||
|
||||
|
||||
//Call original message library
|
||||
exports.clearAll = function() { return require_real("messages").clearAll();};
|
||||
exports.getMessages = function() { return require_real("messages").getMessages();};
|
||||
exports.status = function() { return require_real("messages").status();};
|
||||
exports.buzz = function() { return require_real("messages").buzz(msgSrc);};
|
||||
exports.stopBuzz = function() { return require_real("messages").stopBuzz();};
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "messagesoverlay",
|
||||
"name": "Messages Overlay",
|
||||
"version": "0.01",
|
||||
"description": "An overlay based implementation of a messages UI (display notifications from iOS and Gadgetbridge/Android)",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
"tags": "tool,system",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies" : { "messageicons":"module","messages":"app" },
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"messagesoverlay.settings.js","url":"settings.js"},
|
||||
{"name":"messagesoverlay","url":"lib.js"},
|
||||
{"name":"messagesoverlay.boot.js","url":"boot.js"}
|
||||
],
|
||||
"screenshots": [{"url":"screen_call.png"} ,{"url":"screen_message.png"} ]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
|
@ -0,0 +1 @@
|
|||
eval(require("Storage").read("messages.settings.js"));
|
|
@ -7,3 +7,5 @@
|
|||
0.06: Allow logging of some things using power
|
||||
Add widget for live monitoring of power use
|
||||
0.07: Convert Yes/No On/Off in settings to checkboxes
|
||||
0.08: Fix the wrapping of intervals/timeouts with parameters
|
||||
Fix the widget drawing if widgets are hidden and Bangle.setLCDBrightness is called
|
|
@ -19,6 +19,14 @@ You can switch on logging in the options to diagnose unexpected power use. Curre
|
|||
|
||||
Do not use trace logging for extended time, it uses a lot of storage and can fill up the flash quite quick.
|
||||
|
||||
### TODO
|
||||
|
||||
* Wrap functions given as strings to setTimeout/setInterval
|
||||
* Handle eval in setTimeout/setInterval
|
||||
* Track functions executed as event handlers
|
||||
* Track buzzer
|
||||
* Modify browser interface to estimate power use like widget does
|
||||
|
||||
## Internals
|
||||
|
||||
Battery calibration offset is set by writing `batFullVoltage` in setting.json
|
||||
|
|
|
@ -77,14 +77,14 @@
|
|||
|
||||
let functions = {};
|
||||
let wrapDeferred = ((o,t) => (a) => {
|
||||
if (a == eval){
|
||||
if (a == eval || typeof a == "string") {
|
||||
return o.apply(this, arguments);
|
||||
} else {
|
||||
let wrapped = a;
|
||||
if (!a.__wrapped){
|
||||
wrapped = ()=>{
|
||||
let start = Date.now();
|
||||
let result = a.apply(undefined, arguments.slice(1));
|
||||
let result = a.apply(undefined, arguments.slice(2)); // function arguments for deferred calls start at index 2, first is function, second is time
|
||||
let end = Date.now()-start;
|
||||
let f = a.toString().substring(0,100);
|
||||
if (settings.logDetails) logDeferred(t, end, f);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "powermanager",
|
||||
"name": "Power Manager",
|
||||
"shortName": "Power Manager",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Allow configuration of warnings and thresholds for battery charging and display.",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
|
@ -20,6 +20,7 @@
|
|||
"data": [
|
||||
{"name":"powermanager.hw.json"},
|
||||
{"name":"powermanager.def.json"},
|
||||
{"name":"powermanager.json"},
|
||||
{"name":"powermanager.log"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ currently-running apps */
|
|||
let brightnessSetting = settings.brightness || 1;
|
||||
Bangle.setLCDBrightness = ((o) => (a) => {
|
||||
brightnessSetting = a;
|
||||
draw(WIDGETS.powermanager);
|
||||
WIDGETS.powermanager.draw(WIDGETS.powermanager);
|
||||
return o(a);
|
||||
})(Bangle.setLCDBrightness);
|
||||
|
||||
|
|
|
@ -13,5 +13,7 @@
|
|||
0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11
|
||||
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
|
||||
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working
|
||||
0.15: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonnen (curtesy of FTeacher at https://github.com/f-teacher)
|
||||
0.15: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher)
|
||||
Keep run state between runs (allowing you to exit and restart the app)
|
||||
0.16: Don't clear zone 2b indicator segment when updating HRM reading.
|
||||
Write to correct settings file, fixing settings not working.
|
||||
|
|
|
@ -67,3 +67,10 @@ app loader, the module is automatically included in the app's source. However
|
|||
when developing via the IDE the module won't get pulled in by default.
|
||||
|
||||
There are some options to fix this easily - please check out the [modules README.md file](https://github.com/espruino/BangleApps/blob/master/modules/README.md)
|
||||
## Contributors (Run and Run+)
|
||||
gfwilliams
|
||||
hughbarney
|
||||
GrandVizierOlaf
|
||||
BartS23
|
||||
f-teacher
|
||||
thyttan
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
let wu = require("widget_utils");
|
||||
|
||||
let runInterval;
|
||||
let karvonnenActive = false;
|
||||
let karvonenActive = false;
|
||||
// Run interface wrapped in a function
|
||||
let ExStats = require("exstats");
|
||||
let B2 = process.env.HWVERSION===2;
|
||||
|
@ -63,7 +63,6 @@ function setStatus(running) {
|
|||
function onStartStop() {
|
||||
let running = !exs.state.active;
|
||||
let prepPromises = [];
|
||||
|
||||
// start/stop recording
|
||||
// Do this first in case recorder needs to prompt for
|
||||
// an overwrite before we start tracking exstats
|
||||
|
@ -155,7 +154,7 @@ Bangle.on("GPS", function(fix) {
|
|||
}
|
||||
});
|
||||
|
||||
// run() function used to switch between traditional run UI and karvonnen UI
|
||||
// run() function used to switch between traditional run UI and karvonen UI
|
||||
function run() {
|
||||
wu.show();
|
||||
layout.lazy = false;
|
||||
|
@ -165,35 +164,35 @@ function run() {
|
|||
if (!runInterval){
|
||||
runInterval = setInterval(function() {
|
||||
layout.clock.label = locale.time(new Date(),1);
|
||||
if (!isMenuDisplayed && !karvonnenActive) layout.render();
|
||||
if (!isMenuDisplayed && !karvonenActive) layout.render();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
run();
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Karvonnen
|
||||
// Karvonen
|
||||
///////////////////////////////////////////////
|
||||
|
||||
function stopRunUI() {
|
||||
// stop updating and drawing the traditional run app UI
|
||||
clearInterval(runInterval);
|
||||
runInterval = undefined;
|
||||
karvonnenActive = true;
|
||||
karvonenActive = true;
|
||||
}
|
||||
|
||||
function stopKarvonnenUI() {
|
||||
function stopKarvonenUI() {
|
||||
g.reset().clear();
|
||||
clearInterval(karvonnenInterval);
|
||||
karvonnenInterval = undefined;
|
||||
karvonnenActive = false;
|
||||
clearInterval(karvonenInterval);
|
||||
karvonenInterval = undefined;
|
||||
karvonenActive = false;
|
||||
}
|
||||
|
||||
let karvonnenInterval;
|
||||
let karvonenInterval;
|
||||
// Define the function to go back and forth between the different UI's
|
||||
function swipeHandler(LR,_) {
|
||||
if (LR==-1 && karvonnenActive && !isMenuDisplayed) {stopKarvonnenUI(); run();}
|
||||
if (LR==1 && !karvonnenActive && !isMenuDisplayed) {stopRunUI(); karvonnenInterval = eval(require("Storage").read("runplus_karvonnen"))(settings.HRM, exs.stats.bpm);}
|
||||
if (LR==-1 && karvonenActive && !isMenuDisplayed) {stopKarvonenUI(); run();}
|
||||
if (LR==1 && !karvonenActive && !isMenuDisplayed) {stopRunUI(); karvonenInterval = eval(require("Storage").read("runplus_karvonen"))(settings.HRM, exs.stats.bpm);}
|
||||
}
|
||||
// Listen for swipes with the swipeHandler
|
||||
Bangle.on("swipe", swipeHandler);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(function karvonnen(hrmSettings, exsHrmStats) {
|
||||
(function karvonen(hrmSettings, exsHrmStats) {
|
||||
//This app is an extra feature implementation for the Run.app of the bangle.js. It's called run+
|
||||
//The calculation of the Heart Rate Zones is based on the Karvonnen method. It requires to know maximum and minimum heart rates. More precise calculation methods require a lab.
|
||||
//The calculation of the Heart Rate Zones is based on the Karvonen method. It requires to know maximum and minimum heart rates. More precise calculation methods require a lab.
|
||||
//Other methods are even more approximative.
|
||||
let wu = require("widget_utils");
|
||||
wu.hide();
|
||||
|
@ -56,7 +56,7 @@
|
|||
let hr = exsHrmStats.getValue();
|
||||
let hr1 = hr;
|
||||
// These letiables display next and previous HR zone.
|
||||
//get the hrzones right. The calculation of the Heart rate zones here is based on the Karvonnen method
|
||||
//get the hrzones right. The calculation of the Heart rate zones here is based on the Karvonen method
|
||||
//60-70% of HRR+minHR = zone2. //70-80% of HRR+minHR = zone3. //80-90% of HRR+minHR = zone4. //90-99% of HRR+minHR = zone5. //=>99% of HRR+minHR = serious risk of heart attack
|
||||
let minzone2 = hrr * 0.6 + minhr;
|
||||
let maxzone2 = hrr * 0.7 + minhr;
|
||||
|
@ -67,7 +67,7 @@
|
|||
// HR data: large, readable, in the middle of the screen
|
||||
function drawHR() {
|
||||
g.setFontAlign(-1,0,0);
|
||||
g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2,Rdiv(y,2)+25);
|
||||
g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2-14,Rdiv(y,2)+25);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector",50);
|
||||
g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4);
|
||||
|
@ -207,9 +207,9 @@
|
|||
initDraw();
|
||||
|
||||
// check for updates every second.
|
||||
karvonnenInterval = setInterval(function() {
|
||||
if (!isMenuDisplayed && karvonnenActive) updateUI();
|
||||
karvonenInterval = setInterval(function() {
|
||||
if (!isMenuDisplayed && karvonenActive) updateUI();
|
||||
}, 1000);
|
||||
|
||||
return karvonnenInterval;
|
||||
return karvonenInterval;
|
||||
})
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"id": "runplus",
|
||||
"name": "Run+",
|
||||
"version": "0.15",
|
||||
"version": "0.16",
|
||||
"description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.",
|
||||
"icon": "app.png",
|
||||
"tags": "run,running,fitness,outdoors,gps,karvonnen",
|
||||
"tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen",
|
||||
"supports": [
|
||||
"BANGLEJS2"
|
||||
],
|
||||
|
@ -29,8 +29,8 @@
|
|||
"url": "settings.js"
|
||||
},
|
||||
{
|
||||
"name": "runplus_karvonnen",
|
||||
"url": "karvonnen.js"
|
||||
"name": "runplus_karvonen",
|
||||
"url": "karvonen.js"
|
||||
}
|
||||
],
|
||||
"data": [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "run.json";
|
||||
const SETTINGS_FILE = "runplus.json";
|
||||
var ExStats = require("exstats");
|
||||
var statsList = ExStats.getList();
|
||||
statsList.unshift({name:"-",id:""}); // add blank menu item
|
||||
|
|
|
@ -6,3 +6,5 @@
|
|||
Update to match the default alarm widget, and not show itself when an alarm is hidden.
|
||||
0.04: Fix check for active alarm
|
||||
0.05: Convert Yes/No On/Off in settings to checkboxes
|
||||
0.06: Remember next alarm to reduce calculation amount
|
||||
Redraw only every hour when no alarm in next 24h
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "widalarmeta",
|
||||
"name": "Alarm & Timer ETA",
|
||||
"shortName": "Alarm ETA",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
|
|
@ -1,24 +1,43 @@
|
|||
(() => {
|
||||
require("Font5x9Numeric7Seg").add(Graphics);
|
||||
const alarms = require("Storage").readJSON("sched.json",1) || [];
|
||||
const config = Object.assign({
|
||||
maxhours: 24,
|
||||
drawBell: false,
|
||||
showSeconds: 0, // 0=never, 1=only when display is unlocked, 2=for less than a minute
|
||||
}, require("Storage").readJSON("widalarmeta.json",1) || {});
|
||||
|
||||
function draw() {
|
||||
const times = alarms
|
||||
.map(alarm =>
|
||||
alarm.hidden !== true
|
||||
&& require("sched").getTimeToAlarm(alarm)
|
||||
)
|
||||
.filter(a => a !== undefined);
|
||||
const next = times.length > 0 ? Math.min.apply(null, times) : 0;
|
||||
function getNextAlarm(date) {
|
||||
const alarms = (require("Storage").readJSON("sched.json",1) || []).filter(alarm => alarm.on && alarm.hidden !== true);
|
||||
WIDGETS["widalarmeta"].numActiveAlarms = alarms.length;
|
||||
const times = alarms.map(alarm => require("sched").getTimeToAlarm(alarm, date) || Number.POSITIVE_INFINITY);
|
||||
const eta = times.length > 0 ? Math.min.apply(null, times) : 0;
|
||||
if (eta !== Number.POSITIVE_INFINITY) {
|
||||
const idx = times.indexOf(eta);
|
||||
const alarm = alarms[idx];
|
||||
delete alarm.msg; delete alarm.id; delete alarm.data; // free some memory
|
||||
return alarm;
|
||||
}
|
||||
} // getNextAlarm
|
||||
|
||||
function draw(fromInterval) {
|
||||
if (this.nextAlarm === undefined) {
|
||||
let alarm = getNextAlarm();
|
||||
if (alarm === undefined) {
|
||||
// try again with next hour
|
||||
const nextHour = new Date();
|
||||
nextHour.setHours(nextHour.getHours()+1);
|
||||
alarm = getNextAlarm(nextHour);
|
||||
}
|
||||
if (alarm !== undefined) {
|
||||
this.nextAlarm = alarm;
|
||||
}
|
||||
}
|
||||
const next = this.nextAlarm !== undefined ? require("sched").getTimeToAlarm(this.nextAlarm) : 0;
|
||||
|
||||
let calcWidth = 0;
|
||||
let drawSeconds = false;
|
||||
|
||||
if (next > 0 && next < config.maxhours*60*60*1000) {
|
||||
if (next > 0 && next <= config.maxhours*60*60*1000) {
|
||||
const hours = Math.floor((next-1) / 3600000).toString();
|
||||
const minutes = Math.floor(((next-1) % 3600000) / 60000).toString();
|
||||
const seconds = Math.floor(((next-1) % 60000) / 1000).toString();
|
||||
|
@ -39,10 +58,14 @@
|
|||
if (drawSeconds) {
|
||||
calcWidth += 3*5;
|
||||
}
|
||||
} else if (config.drawBell && alarms.some(alarm=>alarm.on&&(alarm.hidden!==true))) {
|
||||
// next alarm too far in future, draw only widalarm bell
|
||||
g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
this.bellVisible = false;
|
||||
} else if (config.drawBell && this.numActiveAlarms > 0) {
|
||||
calcWidth = 24;
|
||||
// next alarm too far in future, draw only widalarm bell
|
||||
if (this.bellVisible !== true || fromInterval !== true) {
|
||||
g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
this.bellVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.width !== calcWidth) {
|
||||
|
@ -51,8 +74,8 @@
|
|||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
// redraw next full minute or second
|
||||
const period = drawSeconds ? 1000 : 60000;
|
||||
// redraw next hour when no alarm else full minute or second
|
||||
const period = next === 0 ? 3600000 : (drawSeconds ? 1000 : 60000);
|
||||
let timeout = next > 0 ? next % period : period - (Date.now() % period);
|
||||
if (timeout === 0) {
|
||||
timeout += period;
|
||||
|
@ -62,8 +85,8 @@
|
|||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
this.timeoutId = setTimeout(()=>{
|
||||
this.timeoutId = undefined;
|
||||
this.draw();
|
||||
WIDGETS["widalarmeta"].timeoutId = undefined;
|
||||
WIDGETS["widalarmeta"].draw(true);
|
||||
}, timeout);
|
||||
} /* draw */
|
||||
|
||||
|
|
|
@ -15,3 +15,4 @@
|
|||
0.16: Increase screen update rate when charging
|
||||
0.17: Add option 'Remove Jitter'='Drop only' to prevent percentage from getting up again when not charging
|
||||
Add option to disable vibration when charger connects
|
||||
0.18: Only redraw when values change
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "widbatpc",
|
||||
"name": "Battery Level Widget (with percentage)",
|
||||
"shortName": "Battery Widget",
|
||||
"version": "0.17",
|
||||
"version": "0.18",
|
||||
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
return changed;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
function draw(fromInterval) {
|
||||
// if hidden, don't draw
|
||||
if (!WIDGETS["batpc"].width) return;
|
||||
// else...
|
||||
|
@ -103,6 +103,14 @@
|
|||
l = prevMin;
|
||||
}
|
||||
}
|
||||
|
||||
if (fromInterval === true && this.prevLevel === l && this.prevCharging === Bangle.isCharging()) {
|
||||
return; // unchanged, do nothing
|
||||
}
|
||||
|
||||
this.prevLevel = l;
|
||||
this.prevCharging = Bangle.isCharging();
|
||||
|
||||
const c = levelColor(l);
|
||||
|
||||
if (Bangle.isCharging() && setting('charger')) {
|
||||
|
@ -173,7 +181,7 @@
|
|||
if (on) update();
|
||||
});
|
||||
|
||||
var id = setInterval(()=>WIDGETS["batpc"].draw(), intervalLow);
|
||||
var id = setInterval(()=>WIDGETS["batpc"].draw(true), intervalLow);
|
||||
|
||||
WIDGETS["batpc"]={area:"tr",width:40,draw:draw,reload:reload};
|
||||
setWidth();
|
||||
|
|
|
@ -44,6 +44,7 @@ declare module ClockInfo {
|
|||
w: number,
|
||||
h: number,
|
||||
draw(itm: MenuItem, info: Item, options: InteractiveOptions): void,
|
||||
app?: string, // used to remember clock_info locations, per app
|
||||
};
|
||||
|
||||
type InteractiveOptions =
|
||||
|
|
|
@ -8688,6 +8688,15 @@ interface ObjectConstructor {
|
|||
*/
|
||||
entries(object: any): Array<[string, any]>;
|
||||
|
||||
/**
|
||||
* Transforms an array of key-value pairs into an object
|
||||
*
|
||||
* @param {any} entries - An array of `[key,value]` pairs to be used to create an object
|
||||
* @returns {any} An object containing all the specified pairs
|
||||
* @url http://www.espruino.com/Reference#l_Object_fromEntries
|
||||
*/
|
||||
fromEntries(entries: any): any;
|
||||
|
||||
/**
|
||||
* Creates a new object with the specified prototype object and properties.
|
||||
* properties are currently unsupported.
|
||||
|
@ -8709,6 +8718,15 @@ interface ObjectConstructor {
|
|||
*/
|
||||
getOwnPropertyDescriptor(obj: any, name: any): any;
|
||||
|
||||
/**
|
||||
* Get information on all properties in the object (from `Object.getOwnPropertyDescriptor`), or just `{}` if no properties
|
||||
*
|
||||
* @param {any} obj - The object
|
||||
* @returns {any} An object containing all the property descriptors of an object
|
||||
* @url http://www.espruino.com/Reference#l_Object_getOwnPropertyDescriptors
|
||||
*/
|
||||
getOwnPropertyDescriptors(obj: any): any;
|
||||
|
||||
/**
|
||||
* Add a new property to the Object. 'Desc' is an object with the following fields:
|
||||
* * `configurable` (bool = false) - can this property be changed/deleted (not
|
||||
|
@ -8945,32 +8963,32 @@ interface Function {
|
|||
/**
|
||||
* This executes the function with the supplied 'this' argument and parameters
|
||||
*
|
||||
* @param {any} this - The value to use as the 'this' argument when executing the function
|
||||
* @param {any} thisArg - The value to use as the 'this' argument when executing the function
|
||||
* @param {any} params - Optional Parameters
|
||||
* @returns {any} The return value of executing this function
|
||||
* @url http://www.espruino.com/Reference#l_Function_call
|
||||
*/
|
||||
call(this: any, ...params: any[]): any;
|
||||
call(thisArg: any, ...params: any[]): any;
|
||||
|
||||
/**
|
||||
* This executes the function with the supplied 'this' argument and parameters
|
||||
*
|
||||
* @param {any} this - The value to use as the 'this' argument when executing the function
|
||||
* @param {any} thisArg - The value to use as the 'this' argument when executing the function
|
||||
* @param {any} args - Optional Array of Arguments
|
||||
* @returns {any} The return value of executing this function
|
||||
* @url http://www.espruino.com/Reference#l_Function_apply
|
||||
*/
|
||||
apply(this: any, args: any): any;
|
||||
apply(thisArg: any, args: ArrayLike<any>): any;
|
||||
|
||||
/**
|
||||
* This executes the function with the supplied 'this' argument and parameters
|
||||
*
|
||||
* @param {any} this - The value to use as the 'this' argument when executing the function
|
||||
* @param {any} thisArg - The value to use as the 'this' argument when executing the function
|
||||
* @param {any} params - Optional Default parameters that are prepended to the call
|
||||
* @returns {any} The 'bound' function
|
||||
* @url http://www.espruino.com/Reference#l_Function_bind
|
||||
*/
|
||||
bind(this: any, ...params: any[]): any;
|
||||
bind(thisArg: any, ...params: any[]): any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue