diff --git a/apps/backswipe/README.md b/apps/backswipe/README.md index 1611263bb..21aa357b3 100644 --- a/apps/backswipe/README.md +++ b/apps/backswipe/README.md @@ -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 diff --git a/apps/messagesoverlay/ChangeLog b/apps/messagesoverlay/ChangeLog new file mode 100644 index 000000000..d557bc5e1 --- /dev/null +++ b/apps/messagesoverlay/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial fork from messages_light +0.02: Fix touch/drag/swipe handlers not being restored correctly if a message is removed diff --git a/apps/messagesoverlay/README.md b/apps/messagesoverlay/README.md new file mode 100644 index 000000000..8ce1cc64d --- /dev/null +++ b/apps/messagesoverlay/README.md @@ -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 + diff --git a/apps/messagesoverlay/app.png b/apps/messagesoverlay/app.png new file mode 100644 index 000000000..1f738504d Binary files /dev/null and b/apps/messagesoverlay/app.png differ diff --git a/apps/messagesoverlay/boot.js b/apps/messagesoverlay/boot.js new file mode 100644 index 000000000..7731d608a --- /dev/null +++ b/apps/messagesoverlay/boot.js @@ -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); + diff --git a/apps/messagesoverlay/lib.js b/apps/messagesoverlay/lib.js new file mode 100644 index 000000000..2d5feae94 --- /dev/null +++ b/apps/messagesoverlay/lib.js @@ -0,0 +1,486 @@ +/* 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 restoreHandler = function(event){ + LOG("Restore", event, backup[event]); + Bangle.removeAllListeners(event); + Bangle["#on" + event]=backup[event]; + backup[event] = undefined; +}; + +let backupHandler = function(event){ + if (backupDone) return; // do not backup, overlay is already up + backup[event] = Bangle["#on" + event]; + LOG("Backed up", backup[event]); + Bangle.removeAllListeners(event); +}; + +let cleanup = function(){ + if (lockListener) { + Bangle.removeListener("lock", lockListener); + lockListener = undefined; + } + restoreHandler("touch"); + restoreHandler("swipe"); + restoreHandler("drag"); + + Bangle.setLCDOverlay(); + backupDone = false; + ovr = undefined; + quiet = undefined; +}; + +let backup = {}; + +let backupDone = false; + +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 (!backupDone){ + Bangle.on('touch', getTouchHandler(ovr)); + Bangle.on('swipe', getSwipeHandler(ovr)); + } + backupDone=true; + + 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();}; \ No newline at end of file diff --git a/apps/messagesoverlay/metadata.json b/apps/messagesoverlay/metadata.json new file mode 100644 index 000000000..2877b0cf1 --- /dev/null +++ b/apps/messagesoverlay/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "messagesoverlay", + "name": "Messages Overlay", + "version": "0.02", + "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","url":"lib.js"}, + {"name":"messagesoverlay.boot.js","url":"boot.js"} + ], + "screenshots": [{"url":"screen_call.png"} ,{"url":"screen_message.png"} ] +} diff --git a/apps/messagesoverlay/screen_call.png b/apps/messagesoverlay/screen_call.png new file mode 100644 index 000000000..45326b37e Binary files /dev/null and b/apps/messagesoverlay/screen_call.png differ diff --git a/apps/messagesoverlay/screen_message.png b/apps/messagesoverlay/screen_message.png new file mode 100644 index 000000000..f3cc3b6e5 Binary files /dev/null and b/apps/messagesoverlay/screen_message.png differ diff --git a/apps/powermanager/ChangeLog b/apps/powermanager/ChangeLog index a43558c3f..ee31195b2 100644 --- a/apps/powermanager/ChangeLog +++ b/apps/powermanager/ChangeLog @@ -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 \ No newline at end of file diff --git a/apps/powermanager/README.md b/apps/powermanager/README.md index 043b5ca8c..804e986e7 100644 --- a/apps/powermanager/README.md +++ b/apps/powermanager/README.md @@ -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 diff --git a/apps/powermanager/boot.js b/apps/powermanager/boot.js index c931a243b..f3e3f718f 100644 --- a/apps/powermanager/boot.js +++ b/apps/powermanager/boot.js @@ -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); diff --git a/apps/powermanager/metadata.json b/apps/powermanager/metadata.json index ca5a9f00b..5487c2278 100644 --- a/apps/powermanager/metadata.json +++ b/apps/powermanager/metadata.json @@ -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"} ] } diff --git a/apps/powermanager/widget.js b/apps/powermanager/widget.js index 116f1703e..3147c40ac 100644 --- a/apps/powermanager/widget.js +++ b/apps/powermanager/widget.js @@ -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); diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog index 454b12b6d..0e19b5559 100644 --- a/apps/runplus/ChangeLog +++ b/apps/runplus/ChangeLog @@ -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. diff --git a/apps/runplus/README.md b/apps/runplus/README.md index 7f645b518..659cd964d 100644 --- a/apps/runplus/README.md +++ b/apps/runplus/README.md @@ -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 diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 633e47983..9d7010e6c 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -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); diff --git a/apps/runplus/karvonnen.js b/apps/runplus/karvonen.js similarity index 95% rename from apps/runplus/karvonnen.js rename to apps/runplus/karvonen.js index 7e7c07050..de81494bb 100644 --- a/apps/runplus/karvonnen.js +++ b/apps/runplus/karvonen.js @@ -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; }) diff --git a/apps/runplus/metadata.json b/apps/runplus/metadata.json index 15492e20c..017a52ac3 100644 --- a/apps/runplus/metadata.json +++ b/apps/runplus/metadata.json @@ -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": [ diff --git a/apps/runplus/settings.js b/apps/runplus/settings.js index 050eed4b8..cd72022be 100644 --- a/apps/runplus/settings.js +++ b/apps/runplus/settings.js @@ -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 diff --git a/apps/tempmonitor/ChangeLog b/apps/tempmonitor/ChangeLog index 99fe6a77d..af69e6e0a 100644 --- a/apps/tempmonitor/ChangeLog +++ b/apps/tempmonitor/ChangeLog @@ -1,2 +1,3 @@ 0.01: 1st version: saves values to csv 0.02: added HTML interface +0.03: Added Stop/start recording, change BG color, filesize info diff --git a/apps/tempmonitor/README.md b/apps/tempmonitor/README.md index a956f0e0f..e50ac07cb 100644 --- a/apps/tempmonitor/README.md +++ b/apps/tempmonitor/README.md @@ -9,6 +9,12 @@ Bangle JS1 ![](photo_banglejs1.jpg) +UI for bangleJS1 +![](bangle.js_UI.png) + +UI for bangleJS2 +![](bangle.js2_UI.png) + Screenshot BJS2 ![](ss_emul_bjs2.png) @@ -30,18 +36,35 @@ Screenshot data file content Open and see a temperature in the screen Download the CSV file and process in your favourite spreadsheet software +if you have any problem enable the modedebug in code; v_mode_debug=1 or 2 ## Features -Colours, all inputs , graph, widgets loaded -Counter for Times Display +- Cross compatibility (JS1,JS2) and widgets compatibility +- BG/FG Colour, Export to file and counter of saved records per session +- File operations: Info, delete (no yet) +## Pending/future Features +- Buttons layout: btn txt(BJS1) , on screen button (BJS2) +- Long press touch to delete file (BJS1,BJS2) +- File operations: Delete -## Controls - -exit: left side +## Controls/UI + - Left area: Back/Exit/launcher + - BTN3 (long press)(BJS1): default Exit/kill app + - BTN1 (BJS2): "Launcher" / open "Messages" + - BTN2 (BJS1): "Launcher" / open "Messages" + - BTN1 (BJS1): Change FG Color + - BTN3 (BJS1): Change BG Color + - Right area: Change FG Color + - Swipe left: Change BG Color + - Swipe right: Increase/Decrease Hour circle/Points + + ## Creator -Daniel Perez \ No newline at end of file +Daniel Perez +For suggestions or feedback +https://github.com/dapgo/my_espruino_smartwatch_things \ No newline at end of file diff --git a/apps/tempmonitor/bangle.js2_UI.png b/apps/tempmonitor/bangle.js2_UI.png new file mode 100644 index 000000000..ecfc31da8 Binary files /dev/null and b/apps/tempmonitor/bangle.js2_UI.png differ diff --git a/apps/tempmonitor/bangle.js_UI.png b/apps/tempmonitor/bangle.js_UI.png new file mode 100644 index 000000000..4b114b72c Binary files /dev/null and b/apps/tempmonitor/bangle.js_UI.png differ diff --git a/apps/tempmonitor/metadata.json b/apps/tempmonitor/metadata.json index dafb70f27..45d4d2c35 100644 --- a/apps/tempmonitor/metadata.json +++ b/apps/tempmonitor/metadata.json @@ -1,7 +1,7 @@ { "id": "tempmonitor", "name": "Temperature monitor", - "version": "0.02", + "version": "0.03", "description": "Displays the current temperature and stores in a CSV file", "icon": "app.png", "tags": "tool", diff --git a/apps/tempmonitor/tempmonitor.app.js b/apps/tempmonitor/tempmonitor.app.js index 62a4fee67..9a6e85645 100644 --- a/apps/tempmonitor/tempmonitor.app.js +++ b/apps/tempmonitor/tempmonitor.app.js @@ -1,42 +1,69 @@ // Temperature monitor that saves a log of measures // standalone ver for developer, to remove testing lines // delimiter ; (excel) or , (oldscool) +/* REFACTOR and remove commented code related to +SetUI, Layout, and setWatch( function(b) { }, BTN1, { repeat: true, edge:'falling' }) +*/ { var v_mode_debug=0; //, 0=no, 1 min, 2 prone detail //var required for drawing with dynamic screen var rect = Bangle.appRect; var history = []; -var readFreq=5000; //ms //PEND add to settings -var saveFreq=60000; //ms 1min +var readFreq=4000; //ms //PEND add to settings +if (v_mode_debug>0) var saveFreq=6000; //ms for testin 6sec +else var saveFreq=60000; //ms 1min var v_saveToFile= new Boolean(true); //true save //false //with upload file º is not displayed properly //with upload RAM º is displayed var v_t_symbol="";//ºC var v_saved_entries=0; -var filename ="temphistory.csv"; +var v_filename ="temphistory.csv"; var lastMeasure = new String(); var v_model=process.env.BOARD; +var v_color_erase=g.getBgColor(); //original BG color overwritten on SetVariables +var v_color=g.getColor();//original FG color +var id_rec_intv; //var for the recording interval +if (readFreq>saveFreq) console.log("Read refresh freq should be higher than saving"); +if (v_mode_debug>0) console.log("original BG/FG color="+v_color_erase+" / "+v_color); + + + +function SetVariables(){ //EMSCRIPTEN,EMSCRIPTEN2 if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') { v_font_size1=16; - v_font_size2=60; - //g.setColor("#0ff"); //light color + v_font_size2=50; }else{ - v_font_size1=11; - v_font_size2=40; - //g.setColor("#000"); //black or dark + //Banglejs2 or others + v_font_size1=11; //too small? + v_font_size2=40; } + //overwriting default BG, is better detect? + if (g.theme.dark==1) v_color_erase=0x0000; //dynamic; //bg black + else if (g.theme.dark==0) v_color_erase=0xFFFF; //dynamic; //bg white +} -function onTemperature(v_temp) { - if (v_mode_debug>1) console.log("v_temp in "+v_temp); +//print result +function printTemperature(v_temp) { + if (v_mode_debug>1) console.log("v_temp in "+v_temp+" entries "+v_saved_entries); ClearBox(); //g.setFont("6x8",2).setFontAlign(0,0); g.setFontVector(v_font_size1).setFontAlign(0,0); - var x = (rect.x+rect.x2)/2; + var x = (rect.x+(rect.x2-60))/2;//-60 space for graph and layout buttons var y = (rect.y+rect.y2)/2 + 20; - g.drawString("Records: "+v_saved_entries, x, rect.y+35); - g.drawString("Temperature:", x, rect.y+37+v_font_size1); + + if (v_saveToFile==true) { + // if (v_mode_debug>0) console.log("prev color="+v_color); + printInfo("Recording : "+v_saved_entries, '#CC3333',x,rect.y+30); + //g.setColor('#CC3333'); //red + // g.drawString("Recording : "+v_saved_entries, x, rect.y+35); + //g.setColor(v_color);//restore default color + } + else printInfo("Rec paused : "+v_saved_entries, v_color,x,rect.y+30); + //else g.drawString("Rec paused : "+v_saved_entries, x, rect.y+35); + //space for printing info + g.drawString("Temperature:", x, rect.y+45+(v_font_size1*2)); //dynamic font (g.getWidth() > 200 ? 60 : 40) g.setFontVector(v_font_size2).setFontAlign(0,0); // Avg of temperature readings @@ -48,33 +75,68 @@ function onTemperature(v_temp) { lastMeasure=avrTemp.toString(); if (lastMeasure.length>4) lastMeasure=lastMeasure.substr(0,4); //DRAW temperature in the center - g.drawString(" ", x-20, y); - g.drawString(v_temp+v_t_symbol, x-20, y); + //remove g.drawString(" ", x-20, y); + g.drawString(v_temp+v_t_symbol, x, y); g.flip(); } // from: BJS2 pressure sensor, BJS1 inbuilt thermistor -function drawTemperature() { +function getTemperature() { if(v_model.substr(0,10)!='EMSCRIPTEN'){ if (Bangle.getPressure) { - Bangle.getPressure().then(p =>{if (p) onTemperature(p);}); - } else onTemperature(E.getTemperature()); + Bangle.getPressure().then(p =>{if (p) printTemperature(p);}); + } else printTemperature(E.getTemperature()); } - else onTemperature(11);//fake temp for emulators + else printTemperature(11.25);//fake temperature medition for emulators } -function saveToFile() { +/* Note that it changes BG and also FG to an opposite*/ +function changeBGcolor(){ + //pend to refactor + if (v_mode_debug>1) console.log("before BG/FG "+v_color_erase+" /"+v_color); + v_color_erase=0xFFFF-v_color_erase; + v_color=0xFFFF-v_color; + if (v_mode_debug>1) console.log("after result BG/FG "+v_color_erase+" /"+v_color); + //g.setColor(color_result); + g.setBgColor(v_color_erase);// 0 white, 1 black + g.setColor(v_color); + //move to event? + ClearScreen(); + ClearBox(); + drawGraph(); + getTemperature(); + //setDrawLayout(); //uncomment if layout can work with setUI + //g.clear();//impact on widgets +} + +function saveToFile(){ //input global vars: lastMeasure var a=new Date(); var strlastSaveTime=new String(); strlastSaveTime=a.toISOString(); //strlastSaveTime=strlastSaveTime.concat(a.getFullYear(),a.getMonth()+1,a.getDate(),a.getHours(),a.getMinutes());; - if (v_mode_debug==1) console.log("saving="+strlastSaveTime+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure); + if (v_mode_debug>1) console.log("saving="+strlastSaveTime+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure); if (v_saveToFile==true){ - //write(strlastSaveTime+";"+ - require("Storage").open(filename,"a").write((a.getMonth()+1)+";"+a.getDate()+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure+"\n"); - //(getTime()+","); - v_saved_entries=v_saved_entries+1; + //write(strlastSaveTime+";"+ + //var f = require("Storage").open(v_filename,"r"); + // f=require("Storage").read(v_filename+"\1");//suffix required load completely!! + //note that .read uses Storage Class .open uses StorageFile Class , difference in file chunks + // if (v_mode_debug>0) console.log("f "+f); + var f = require("Storage").open(v_filename,"r"); + if ((v_mode_debug>0) && (v_saved_entries==0)) console.log("file info:"+f); + if (f.len>0) { + if (!f) { + require("Storage").open(v_filename,"w").write("Month;Day;Time;Temp"+"\n"); + if (v_mode_debug>0) console.log("not exist but created "+f); + } + else{ + require("Storage").open(v_filename,"a").write((a.getMonth()+1)+";"+a.getDate()+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure+"\n"); + //(getTime()+","); + v_saved_entries=v_saved_entries+1; + if (v_mode_debug>1) console.log("append to already exist "+f.name+" , "+v_saved_entries); + } + } } + else if (v_mode_debug>0) console.log("recording mode stopped"); } function drawGraph(){ @@ -83,17 +145,19 @@ function drawGraph(){ transparent : 0, buffer : require("heatshrink").decompress(atob("AEFt2AMKm3bsAMJjdt23ABhEB+/7tgaJ///DRUP//7tuADRP923YDRXbDRfymwaJhu/koaK7eyiwaK3cLDRlWDRY1NKBY1Ztu5kjmJg3cyVI7YMHgdu5Mkyu2fxHkyVJjdgDRFJkmRDRPsDQNbDQ5QBGoONKBJrBoxQIQwO2eRcbtu24AMIFIQLJAH4AMA==")) }; - g.drawImage(img_obj_thermo,rect.x2-50,rect.y2/2); + g.drawImage(img_obj_thermo,rect.x2-60,rect.y2/2); g.flip(); } function ClearScreen(){ //avoid widget areas - g.reset(1).clearRect(rect.x, rect.y+24, rect.x2, rect.y2-24); + g.setBgColor(v_color_erase); + g.clearRect(rect.x, rect.y+24, rect.x2, rect.y2-24); g.flip(); } function ClearBox(){ //custom boxarea , left space for static graph at right - g.reset(1).clearRect(rect.x, rect.y+24, rect.x2-50, rect.y2-24); + g.setBgColor(v_color_erase); + g.clearRect(rect.x, rect.y+24, rect.x2-60, rect.y2-24); g.flip(); } function introPage(){ @@ -109,30 +173,140 @@ function introPage(){ g.drawString("Read freq(ms): "+readFreq, x, y ); g.drawString("Save to file: "+v_saveToFile, x, y+ ((v_font_size1*1)+2) ); g.drawString("Save freq(ms):"+saveFreq, x, y+((v_font_size1*2)+2) ); - fr=require("Storage").read(filename+"\1");//suffix required - if (fr) g.drawString("Current filesize:"+fr.length.toString()+"kb", x, y+((v_font_size1*3)+2) ); + fr=require("Storage").read(v_filename+"\1");//suffix required + if (fr) g.drawString("Filesize:"+fr.length.toString()+"kb", x, y+((v_font_size1*3)+2) ); else g.drawString("File not exist", x, y+((v_font_size1*3)+2)); } +function printInfo(pmsg, pcolor,px,py){ + g.setColor(pcolor); + g.setFontVector(v_font_size1).setFontAlign(0,0); + g.drawString(pmsg, px,py+v_font_size1); + g.setColor(v_color);//restore default color +} +function toggleRecMode(duration, exectime){ + //bydefault float, standard epoch requires *1000 + if (v_mode_debug>0) console.log("duration"+duration); + if (duration>2) { //delete file + var x = (rect.x+(rect.x2-60))/2; + printInfo("Deleting file",'#CC3333',x, rect.y+32+v_font_size1); + // g.setColor('#CC3333'); //red + + //too long "Deleting file: "+v_filename, + // for StorageFiles created with require("Storage").open(filename, ...) + //require("Storage").erase(v_filename); + //TODO refactor in a new function + //var mifile = require("Storage").open(v_filename,"w"); + var mifile = require("Storage").open("temphistory.csv","w"); + var v_output=mifile.erase(); + //mifile.StorageFile.erase(); + if (v_mode_debug>0) console.log("output"+v_output); + setTimeout(function() { if (v_mode_debug>0) console.log("pause for 1 sec");},1000); + return; //leave this function + } + if (v_saveToFile) v_saveToFile=false; + else v_saveToFile=true; + if (v_mode_debug>0) console.log("recording? "+v_saveToFile); + setRecordingFreq(); +} + +function setRecordingFreq(){ + if (v_saveToFile==true) { //TODO now start on false btn will no enable + id_rec_intv=setInterval(function() { + saveToFile(); + }, saveFreq); //ms + if (v_mode_debug>0) console.log("interval id / frq"+id_rec_intv+" / "+saveFreq); + } + else if (id_rec_intv){ + clearInterval(id_rec_intv); + if (v_mode_debug>0) console.log("rec interval removed, id "+id_rec_intv); + id_rec_intv=0; // to reset var + } +} + +function UserInput(){ + //theoretically incompatible with Layout + Bangle.setUI({ + mode : "custom", + //adds a back icon on top widget area + back : function() {load();}, + //touch : function(n,e) {}, // optional - handler for 'touch' events + // righ/Left 1/-1 , updown + swipe : function(dir_rl,dir_ud) { + if(dir_rl == 1) { + if (v_mode_debug>0) console.log("swipe right: "); + getFileInfo(v_filename); + } + else if (dir_rl == -1){ + if (v_mode_debug>0) console.log("swipe left: "); + changeBGcolor(); + } + }, + touch : function(tzone,tobj){ + if ((process.env.HWVERSION == 2)&&(v_mode_debug>0)){ + console.log("tobj x,y,type : "+tobj.x+" "+tobj.y+" "+tobj.type); + } + switch(tzone){ + //case 1: //left , back managed by setUI + case 2: // right disable/enable recording + toggleRecMode(0); //toggleRecMode(duration, exectime) + break; + // case 3: console.log("Touch 3 aka 1+2 not for BJS1 emul");//center 1+2 + // break; + } + }, + //inferior to + btn : function(btn) { + if(btn == 1) { + if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') toggleRecMode(1); //console.log("btn1 BJS1"); + else mainBtnShortcut(); //console.log("btn1 BJS2"); + } + else if (btn == 2) mainBtnShortcut(); //console.log("btn2 BJS1"); + else if (btn == 3) changeBGcolor(); //console.log("btn3 BJS1"); + } + }); //endof setUI + +} + +function mainBtnShortcut() { + //if messages app installed shortcut otherwise default access to launcher + if (require("Storage").read("messagegui.app.js")===undefined) + { + if (require("Storage").read("messagelist.app.js")===undefined) Bangle.showLauncher(); // implies btn2(js1) btn(js2)- launcher + else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagelist.app.js"); + else load("messagelist.app.js"); + } + else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagegui.app.js"); + else load("messagegui.app.js"); + } + + +// Show file size +function getFileInfo(v_filename) { + var f = require("Storage").open(v_filename,"r"); + //todo refactor and reuse common code + g.setFontVector(v_font_size1).setFontAlign(0,0); + var x = (rect.x+(rect.x2-60))/2; + printInfo("file size:"+f.len,v_color,x, rect.y+32+v_font_size1); + // g.drawString("file size:"+f.len, x, rect.y+37+v_font_size1); + if (v_mode_debug>0) console.log("file "+v_filename+" size: "+f.len); +}// not used + + //MAIN +SetVariables(); Bangle.loadWidgets(); -Bangle.setUI({ - mode : "custom", - back : function() {load();} -}); ClearScreen(); introPage(); +//setDrawLayout(); //uncomment if layout can work with setUI + +UserInput(); //inc SetUI and back icon + setInterval(function() { - drawTemperature(); + getTemperature(); }, readFreq); //ms -if (v_saveToFile==true) { - setInterval(function() { - saveToFile(); - }, saveFreq); //ms -} -setTimeout(ClearScreen, 3500); -setTimeout(drawGraph,4000); -setTimeout(drawTemperature,4500); +setRecordingFreq(); + } \ No newline at end of file diff --git a/apps/tempmonitor/tempmonitor.info b/apps/tempmonitor/tempmonitor.info index 1824c5c86..f31704b57 100644 --- a/apps/tempmonitor/tempmonitor.info +++ b/apps/tempmonitor/tempmonitor.info @@ -1 +1 @@ -{"id":"tempmonitor","name":"tempmonitor","src":"tempmonitor.app.js","icon":"tempmonitor.img","version":"0.01","files":"tempmonitor.info,tempmonitor.app.js,tempmonitor.img"} \ No newline at end of file +{"id":"tempmonitor","name":"tempmonitor","src":"tempmonitor.app.js","icon":"tempmonitor.img","version":"0.03","files":"tempmonitor.info,tempmonitor.app.js,tempmonitor.img"} \ No newline at end of file diff --git a/apps/widChargingStatus/widget.ts b/apps/widChargingStatus/widget.ts index 02f3cd8d4..a161d5408 100644 --- a/apps/widChargingStatus/widget.ts +++ b/apps/widChargingStatus/widget.ts @@ -6,11 +6,11 @@ ); const iconWidth = 18; - function draw(this: { x: number; y: number }) { + function draw(this: { x?: number; y?: number }) { g.reset(); if (Bangle.isCharging()) { g.setColor('#FD0'); - g.drawImage(icon, this.x + 1, this.y + 1, { + g.drawImage(icon, this.x! + 1, this.y! + 1, { scale: 0.6875, }); } diff --git a/apps/widalarmeta/ChangeLog b/apps/widalarmeta/ChangeLog index 66b9a4b8e..2b74766c8 100644 --- a/apps/widalarmeta/ChangeLog +++ b/apps/widalarmeta/ChangeLog @@ -6,3 +6,6 @@ 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 +0.07: Fix when no alarms are present diff --git a/apps/widalarmeta/metadata.json b/apps/widalarmeta/metadata.json index 20b54d7c8..6b3d8978b 100644 --- a/apps/widalarmeta/metadata.json +++ b/apps/widalarmeta/metadata.json @@ -2,7 +2,7 @@ "id": "widalarmeta", "name": "Alarm & Timer ETA", "shortName": "Alarm ETA", - "version": "0.05", + "version": "0.07", "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", diff --git a/apps/widalarmeta/widget.js b/apps/widalarmeta/widget.js index 3ad2e6ad2..0104eb3b1 100644 --- a/apps/widalarmeta/widget.js +++ b/apps/widalarmeta/widget.js @@ -1,24 +1,45 @@ (() => { 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; + if (alarms.length > 0) { + const times = alarms.map(alarm => require("sched").getTimeToAlarm(alarm, date) || Number.POSITIVE_INFINITY); + const eta = Math.min.apply(null, times); + 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 +60,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 +76,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 +87,8 @@ clearTimeout(this.timeoutId); } this.timeoutId = setTimeout(()=>{ - this.timeoutId = undefined; - this.draw(); + WIDGETS["widalarmeta"].timeoutId = undefined; + WIDGETS["widalarmeta"].draw(true); }, timeout); } /* draw */ diff --git a/apps/widbaroalarm/ChangeLog b/apps/widbaroalarm/ChangeLog index 2dfe8336d..3b2ae75c4 100644 --- a/apps/widbaroalarm/ChangeLog +++ b/apps/widbaroalarm/ChangeLog @@ -7,3 +7,4 @@ Show difference of last measurement to pressure average of the the last three hours in the widget Only use valid pressure values 0.06: Fix exception +0.07: Ensure barometer gets turned off after a few readings (isBarometerOn broken in 2v16) diff --git a/apps/widbaroalarm/metadata.json b/apps/widbaroalarm/metadata.json index ba6c47b37..0a6ddd71e 100644 --- a/apps/widbaroalarm/metadata.json +++ b/apps/widbaroalarm/metadata.json @@ -2,7 +2,7 @@ "id": "widbaroalarm", "name": "Barometer Alarm Widget", "shortName": "Barometer Alarm", - "version": "0.06", + "version": "0.07", "description": "A widget that can alarm on when the pressure reaches defined thresholds.", "icon": "widget.png", "type": "widget", diff --git a/apps/widbaroalarm/widget.js b/apps/widbaroalarm/widget.js index d877c4384..d65a1c09c 100644 --- a/apps/widbaroalarm/widget.js +++ b/apps/widbaroalarm/widget.js @@ -211,6 +211,28 @@ function calculcate3hAveragePressure() { } } +function barometerPressureHandler(e) { + const MEDIANLENGTH = 20; + while (currentPressures.length > MEDIANLENGTH) + currentPressures.pop(); + + const pressure = e.pressure; + if (isValidPressureValue(pressure)) { + currentPressures.unshift(pressure); + median = currentPressures.slice().sort(); + + if (median.length > 10) { + var mid = median.length >> 1; + medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9); + if (medianPressure > 0) { + turnOff(); + draw(); + handlePressureValue(medianPressure); + } + } + } +} + /* turn on barometer power take multiple measurements @@ -219,37 +241,15 @@ function calculcate3hAveragePressure() { turn off barometer power */ function getPressureValue() { - if (stop) - return; - const MEDIANLENGTH = 20; + if (stop) return; Bangle.setBarometerPower(true, "widbaroalarm"); - Bangle.on('pressure', function(e) { - while (currentPressures.length > MEDIANLENGTH) - currentPressures.pop(); - - const pressure = e.pressure; - if (isValidPressureValue(pressure)) { - currentPressures.unshift(pressure); - median = currentPressures.slice().sort(); - - if (median.length > 10) { - var mid = median.length >> 1; - medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9); - if (medianPressure > 0) { - turnOff(); - draw(); - handlePressureValue(medianPressure); - } - } - } - }); - - setTimeout(function() { turnOff(); }, 30000); + Bangle.on('pressure', barometerPressureHandler); + setTimeout(turnOff, 30000); } function turnOff() { - if (Bangle.isBarometerOn()) - Bangle.setBarometerPower(false, "widbaroalarm"); + Bangle.removeListener('pressure', barometerPressureHandler); + Bangle.setBarometerPower(false, "widbaroalarm"); } function draw() { diff --git a/apps/widbat/ChangeLog b/apps/widbat/ChangeLog index 59c58af32..cb11b1be9 100644 --- a/apps/widbat/ChangeLog +++ b/apps/widbat/ChangeLog @@ -7,3 +7,4 @@ 0.08: Ensure battery updates every 60s even if LCD was on at boot and stays on 0.09: Misc speed/memory tweaks 0.10: Color changes due to the battery level +0.11: Change level for medium charge (50% -> 40%), and darken color on light themes as yellow was almost invisible diff --git a/apps/widbat/metadata.json b/apps/widbat/metadata.json index 5f0d1b7d1..0151fcbd7 100644 --- a/apps/widbat/metadata.json +++ b/apps/widbat/metadata.json @@ -1,7 +1,7 @@ { "id": "widbat", "name": "Battery Level Widget", - "version": "0.10", + "version": "0.11", "description": "Show the current battery level and charging status in the top right of the clock", "icon": "widget.png", "type": "widget", diff --git a/apps/widbat/widget.js b/apps/widbat/widget.js index 7fce16335..98eb09227 100644 --- a/apps/widbat/widget.js +++ b/apps/widbat/widget.js @@ -33,7 +33,7 @@ g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14); var battery = E.getBattery(); if(battery < 20) {g.setColor("#f00");} - else if (battery < 50) {g.setColor("#ff0");} + else if (battery < 40) {g.setColor(g.theme.dark ? "#ff0" : "#f80");} else {g.setColor("#0f0");} g.fillRect(x+4,y+6,x+4+battery*(s-12)/100,y+17); }}; diff --git a/apps/widbatpc/ChangeLog b/apps/widbatpc/ChangeLog index 97da2cba6..3592656a9 100644 --- a/apps/widbatpc/ChangeLog +++ b/apps/widbatpc/ChangeLog @@ -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 diff --git a/apps/widbatpc/metadata.json b/apps/widbatpc/metadata.json index cb47475cb..d361da442 100644 --- a/apps/widbatpc/metadata.json +++ b/apps/widbatpc/metadata.json @@ -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", diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index 7f483c960..b508cce8b 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -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(); diff --git a/apps/widlockunlock/ChangeLog b/apps/widlockunlock/ChangeLog index b4d1ae593..b5efcaa86 100644 --- a/apps/widlockunlock/ChangeLog +++ b/apps/widlockunlock/ChangeLog @@ -1 +1,2 @@ 0.01: First commit +0.02: Add tap-to-lock functionality diff --git a/apps/widlockunlock/metadata.json b/apps/widlockunlock/metadata.json index d701279b9..cc4fa76cd 100644 --- a/apps/widlockunlock/metadata.json +++ b/apps/widlockunlock/metadata.json @@ -1,8 +1,8 @@ { "id": "widlockunlock", "name": "Lock/Unlock Widget", - "version": "0.01", - "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked, or an unlock icon otherwise", + "version": "0.02", + "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked, or an unlock icon otherwise. Tap to lock the lcd", "icon": "widget.png", "type": "widget", "tags": "widget,lock", diff --git a/apps/widlockunlock/widget.js b/apps/widlockunlock/widget.js index 0716a9edf..cfbbc87a3 100644 --- a/apps/widlockunlock/widget.js +++ b/apps/widlockunlock/widget.js @@ -1,5 +1,28 @@ -Bangle.on("lockunlock", function() { - Bangle.drawWidgets(); +Bangle.on("lock", () => Bangle.drawWidgets()); + +Bangle.on('touch', (_btn, xy) => { + const oversize = 5; + + const w = WIDGETS.lockunlock; + + const x = xy.x; + const y = xy.y; + + if(w.x - oversize <= x && x < w.x + 14 + oversize + && w.y - oversize <= y && y < w.y + 24 + oversize) + { + Bangle.setLocked(true); + + const backlightTimeout = Bangle.getOptions().backlightTimeout; // ms + + // seems to be a race/if we don't give the firmware enough time, + // it won't timeout the backlight and we'll restore it in our setTimeout below + Bangle.setOptions({ backlightTimeout: 100 }); + + setTimeout(() => { + Bangle.setOptions({ backlightTimeout }); + }, 300); + } }); WIDGETS["lockunlock"]={area:"tl",sortorder:10,width:14,draw:function(w) { g.reset().drawImage(atob(Bangle.isLocked() ? "DBGBAAAA8DnDDCBCBP////////n/n/n//////z/A" : "DBGBAAAA8BnDDCBABP///8A8A8Y8Y8Y8A8A//z/A"), w.x+1, w.y+3); diff --git a/typescript/types/clock_info.d.ts b/typescript/types/clock_info.d.ts index 9b664a6dc..06a2d400b 100644 --- a/typescript/types/clock_info.d.ts +++ b/typescript/types/clock_info.d.ts @@ -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 = diff --git a/typescript/types/main.d.ts b/typescript/types/main.d.ts index 1505c6f5a..1223f527d 100644 --- a/typescript/types/main.d.ts +++ b/typescript/types/main.d.ts @@ -83,7 +83,10 @@ type WidgetArea = "tl" | "tr" | "bl" | "br"; type Widget = { area: WidgetArea; width: number; - draw: (this: { x: number; y: number }) => void; + sortorder?: number; + draw: (this: Widget, w: Widget) => void; + x?: number; + y?: number; }; declare const WIDGETS: { [key: string]: Widget }; @@ -8688,6 +8691,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 +8721,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 +8966,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; /** * 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; } /**