diff --git a/.gitignore b/.gitignore index d2001bf41..47233d1f5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ appdates.csv .vscode .idea/ _config.yml - +tests/Layout/bin/tmp.* +tests/Layout/testresult.bmp diff --git a/apps.json b/apps.json index 1cc200bb2..7a0fde795 100644 --- a/apps.json +++ b/apps.json @@ -94,7 +94,7 @@ "name": "Notifications (default)", "shortName":"Notifications", "icon": "notify.png", - "version":"0.09", + "version":"0.10", "description": "A handler for displaying notifications that displays them in a bar at the top of the screen", "tags": "widget", "type": "notify", @@ -107,9 +107,9 @@ "name": "Fullscreen Notifications", "shortName":"Notifications", "icon": "notify.png", - "version":"0.10", + "version":"0.11", "description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.", - "tags": "widget", + "tags": "widget,b2", "type": "notify", "storage": [ {"name":"notify","url":"notify.js"} @@ -155,7 +155,7 @@ "icon": "app.png", "version":"0.24", "description": "The default notification handler for Gadgetbridge notifications from Android", - "tags": "tool,system,android,widget", + "tags": "tool,system,android,widget,b2", "readme": "README.md", "type":"widget", "dependencies": { "notify":"type" }, @@ -571,7 +571,7 @@ { "id": "weather", "name": "Weather", "icon": "icon.png", - "version":"0.07", + "version":"0.08", "description": "Show Gadgetbridge weather report", "readme": "readme.md", "tags": "widget,outdoors", @@ -1184,7 +1184,7 @@ { "id": "widpedom", "name": "Pedometer widget", "icon": "widget.png", - "version":"0.18", + "version":"0.19", "description": "Daily pedometer widget", "tags": "widget,b2", "type":"widget", @@ -1670,7 +1670,7 @@ "name": "Calculator", "shortName":"Calculator", "icon": "calculator.png", - "version":"0.03", + "version":"0.04", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", "tags": "app,tool,b2", "storage": [ @@ -2508,7 +2508,7 @@ "name": "Cycling speed sensor", "shortName":"CSCSensor", "icon": "icons8-cycling-48.png", - "version":"0.04", + "version":"0.05", "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch", "tags": "outdoors,exercise,ble,bluetooth", "readme": "README.md", @@ -3520,5 +3520,29 @@ "data": [ {"name":"pastel.json"} ] +}, +{ "id": "antonclk", + "name": "Anton Clock", + "icon": "app.png", + "version":"0.01", + "description": "A simple clock using the bold Anton font.", + "tags":"clock,b2", + "type":"clock", + "storage": [ + {"name":"antonclk.app.js","url":"app.js"}, + {"name":"antonclk.img","url":"app-icon.js","evaluate":true} + ] +}, +{ "id": "waveclk", + "name": "Wave Clock", + "icon": "app.png", + "version":"0.01", + "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**", + "tags":"clock,b2", + "type":"clock", + "storage": [ + {"name":"waveclk.app.js","url":"app.js"}, + {"name":"waveclk.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/accellog/app.js b/apps/accellog/app.js index 5408264f1..dcc7702c9 100644 --- a/apps/accellog/app.js +++ b/apps/accellog/app.js @@ -97,7 +97,7 @@ function startRecord(force) { {type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5}, {type:"txt", font:"6x8", label:"Time", pad:2}, {type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5}, - {type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:true}, + {type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1}, ] },[ // Buttons... {label:"STOP", cb:()=>{ @@ -105,7 +105,6 @@ function startRecord(force) { showMenu(); }} ]); - layout.update(); layout.render(); // now start writing diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/antonclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/antonclk/app-icon.js b/apps/antonclk/app-icon.js new file mode 100644 index 000000000..fad03d50f --- /dev/null +++ b/apps/antonclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA==")) diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js new file mode 100644 index 000000000..1b92d4a8b --- /dev/null +++ b/apps/antonclk/app.js @@ -0,0 +1,57 @@ +Graphics.prototype.setFontAnton = function(scale) { +// Actual height 69 (68 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78+(scale<<8)+(1<<16)); +} + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + var date = new Date(); + var timeStr = require("locale").time(date,1); + var dateStr = require("locale").date(date).toUpperCase(); + // draw time + g.setFontAlign(0,0).setFont("Anton"); + g.clearRect(0,y-40,g.getWidth(),y+35); // clear the background + g.drawString(timeStr,x,y); + // draw date + y += 40; + g.setFontAlign(0,0).setFont("6x8",2); + g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background + g.drawString(dateStr,x,y); + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first, queue update +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// Show launcher when middle button pressed +Bangle.setUI("clock"); + diff --git a/apps/antonclk/app.png b/apps/antonclk/app.png new file mode 100644 index 000000000..d96f17758 Binary files /dev/null and b/apps/antonclk/app.png differ diff --git a/apps/antonclk/screenshot.png b/apps/antonclk/screenshot.png new file mode 100644 index 000000000..c66f8bdd8 Binary files /dev/null and b/apps/antonclk/screenshot.png differ diff --git a/apps/calculator/ChangeLog b/apps/calculator/ChangeLog index c711731d7..6a3308c50 100644 --- a/apps/calculator/ChangeLog +++ b/apps/calculator/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: fix precision rounding issue + no reset when equals pressed 0.03: Support for different screen sizes and touchscreen +0.04: Display current operation on LHS diff --git a/apps/calculator/app.js b/apps/calculator/app.js index 89520ab94..9f801cf22 100644 --- a/apps/calculator/app.js +++ b/apps/calculator/app.js @@ -199,8 +199,7 @@ function doMath(x, y, operator) { function displayOutput(num) { var len; var minusMarge = 0; - g.setColor(0); - g.fillRect(0, 0, g.getWidth(), RESULT_HEIGHT-1); + g.setBgColor(0).clearRect(0, 0, g.getWidth(), RESULT_HEIGHT-1); g.setColor(-1); if (num === Infinity || num === -Infinity || isNaN(num)) { // handle division by 0 @@ -244,6 +243,10 @@ function displayOutput(num) { } g.setFontAlign(1,0); g.drawString(num, g.getWidth()-20, RESULT_HEIGHT/2); + if (operator) { + g.setFont('Vector', 22).setFontAlign(1,0); + g.drawString(operator, g.getWidth()-1, RESULT_HEIGHT/2); + } } var wasPressedEquals = false; var hasPressedNumber = false; diff --git a/apps/cscsensor/ChangeLog b/apps/cscsensor/ChangeLog index 7be2ed3e2..9af9f9926 100644 --- a/apps/cscsensor/ChangeLog +++ b/apps/cscsensor/ChangeLog @@ -2,4 +2,4 @@ 0.02: Add wheel circumference settings dialog 0.03: Save total distance traveled 0.04: Add sensor battery level indicator - +0.05: Add cadence sensor support diff --git a/apps/cscsensor/README.md b/apps/cscsensor/README.md index a31a4dc28..e19ebe60e 100644 --- a/apps/cscsensor/README.md +++ b/apps/cscsensor/README.md @@ -13,6 +13,6 @@ Currently the app displays the following data: Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app. If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor. +Button 2 switches between the display for cycling speed and cadence. -I do not have access to a cadence sensor at the moment, so only the speed part is currently implemented. Values displayed are imperial or metric (depending on locale), -the wheel circumference can be adjusted in the global settings app. +Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app. diff --git a/apps/cscsensor/cscsensor.app.js b/apps/cscsensor/cscsensor.app.js index c402c06da..3d4120269 100644 --- a/apps/cscsensor/cscsensor.app.js +++ b/apps/cscsensor/cscsensor.app.js @@ -28,6 +28,10 @@ class CSCSensor { this.distFactor = this.qMetric ? 1.609344 : 1; this.screenInit = true; this.batteryLevel = -1; + this.lastCrankTime = 0; + this.lastCrankRevs = 0; + this.showCadence = false; + this.cadence = 0; } reset() { @@ -40,6 +44,11 @@ class CSCSensor { this.screenInit = true; } + toggleDisplayCadence() { + this.showCadence = !this.showCadence; + this.screenInit = true; + } + setBatteryLevel(level) { if (level!=this.batteryLevel) { this.batteryLevel = level; @@ -62,7 +71,7 @@ class CSCSensor { else g.setFontVector(14).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16, 66); } - updateScreen() { + updateScreenRevs() { var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0; var ddist = Math.round(100*dist)/100; var tdist = Math.round(this.distFactor*this.totaldist*10)/10; @@ -108,45 +117,88 @@ class CSCSensor { g.setColor(0).fillRect(88, 209, 238, 238); g.setColor(0xffff).drawString(tdist + " " + this.distUnit, 92, 226); } - + + updateScreenCadence() { + if (this.screenInit) { + for (var i=0; i<2; ++i) { + if ((i&1)==0) g.setColor(0, 0, 0); + else g.setColor(0x30cd); + g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); + if ((i&1)==1) g.setColor(0); + else g.setColor(0x30cd); + g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); + g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239); + g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80); + } + g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); + g.drawString("Cadence:", 87, 98); + this.drawBatteryIcon(); + this.screenInit = false; + } + g.setFontAlign(-1, 0, 0).setFontVector(26); + g.setColor(0).fillRect(88, 81, 238, 111); + g.setColor(0xffff).drawString(Math.round(this.cadence), 92, 98); + } + + updateScreen() { + if (!this.showCadence) { + this.updateScreenRevs(); + } else { + this.updateScreenCadence(); + } + } + updateSensor(event) { var qChanged = false; if (event.target.uuid == "0x2a5b") { - var wheelRevs = event.target.value.getUint32(1, true); - var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0); - if (dRevs>0) { - qChanged = true; - this.totaldist += dRevs*this.wheelCirc/63360.0; - if ((this.totaldist-this.settings.totaldist)>0.1) { - this.settings.totaldist = this.totaldist; - storage.writeJSON(SETTINGS_FILE, this.settings); + if (event.target.value.getUint8(0, true) & 0x2) { + // crank revolution + const crankRevs = event.target.value.getUint16(1, true); + const crankTime = event.target.value.getUint16(3, true); + if (crankTime > this.lastCrankTime) { + this.cadence = (crankRevs-this.lastCrankRevs)/(crankTime-this.lastCrankTime)*(60*1024); + qChanged = true; } - } - this.lastRevs = wheelRevs; - if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs; - var wheelTime = event.target.value.getUint16(5, true); - var dT = (wheelTime-this.lastTime)/1024; - var dBT = (Date.now()-this.lastBangleTime)/1000; - this.lastBangleTime = Date.now(); - if (dT<0) dT+=64; - if (Math.abs(dT-dBT)>3) dT = dBT; - this.lastTime = wheelTime; - this.speed = this.lastSpeed; - if (dRevs>0 && dT>0) { - this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT; - this.speedFailed = 0; - this.movingTime += dT; - } - else { - this.speedFailed++; - qChanged = false; - if (this.speedFailed>3) { - this.speed = 0; - qChanged = (this.lastSpeed>0); + this.lastCrankRevs = crankRevs; + this.lastCrankTime = crankTime; + } else { + // wheel revolution + var wheelRevs = event.target.value.getUint32(1, true); + var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0); + if (dRevs>0) { + qChanged = true; + this.totaldist += dRevs*this.wheelCirc/63360.0; + if ((this.totaldist-this.settings.totaldist)>0.1) { + this.settings.totaldist = this.totaldist; + storage.writeJSON(SETTINGS_FILE, this.settings); + } } + this.lastRevs = wheelRevs; + if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs; + var wheelTime = event.target.value.getUint16(5, true); + var dT = (wheelTime-this.lastTime)/1024; + var dBT = (Date.now()-this.lastBangleTime)/1000; + this.lastBangleTime = Date.now(); + if (dT<0) dT+=64; + if (Math.abs(dT-dBT)>3) dT = dBT; + this.lastTime = wheelTime; + this.speed = this.lastSpeed; + if (dRevs>0 && dT>0) { + this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT; + this.speedFailed = 0; + this.movingTime += dT; + } + else { + this.speedFailed++; + qChanged = false; + if (this.speedFailed>3) { + this.speed = 0; + qChanged = (this.lastSpeed>0); + } + } + this.lastSpeed = this.speed; + if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed; } - this.lastSpeed = this.speed; - if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed; } if (qChanged && this.qUpdateScreen) this.updateScreen(); } @@ -199,6 +251,7 @@ connection_setup(); setWatch(function() { mySensor.reset(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20}); E.on('kill',()=>{ if (gatt!=undefined) gatt.disconnect(); mySensor.settings.totaldist = mySensor.totaldist; storage.writeJSON(SETTINGS_FILE, mySensor.settings); }); setWatch(function() { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }, BTN3, {repeat:true, debounce:20}); +setWatch(function() { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN2, {repeat:true, debounce:20}); NRF.on('disconnect', connection_setup); Bangle.loadWidgets(); diff --git a/apps/notify/ChangeLog b/apps/notify/ChangeLog index 291f32a5a..d1826f57e 100644 --- a/apps/notify/ChangeLog +++ b/apps/notify/ChangeLog @@ -6,3 +6,4 @@ 0.07: Auto-calculate height, and pad text down even when there's no title (so it stays on-screen) 0.08: Don't turn on screen during Quiet Mode 0.09: Add onHide callback +0.10: Improvements to help notifications work with themes diff --git a/apps/notify/README.md b/apps/notify/README.md index 11c493102..f186aaab2 100644 --- a/apps/notify/README.md +++ b/apps/notify/README.md @@ -5,6 +5,10 @@ A handler for displaying notifications that displays them in a bar at the top of This is not an app, but instead it is a library that can be used by other applications or widgets to display messages. +**Note:** There are other implementations of this library available such +as `notifyfs` (Fullscreen Notifications). These can be used in the exact +same way from code, but they look different to the user. + ## Usage ```JS diff --git a/apps/notify/notify.js b/apps/notify/notify.js index 68bc1a954..4448de73b 100644 --- a/apps/notify/notify.js +++ b/apps/notify/notify.js @@ -96,15 +96,17 @@ exports.show = function(options) { b = y+h-1, r = x+w-1; // bottom,right g.setClipRect(x,y, r,b); // clear area - g.setColor(options.bgColor||0).fillRect(x,y, r,b); + g.reset(); + if (options.bgColor!==undefined) g.setColor(options.bgColor); + g.clearRect(x,y, r,b); // bottom border - g.setColor(0x39C7).fillRect(0,b-1, r,b); + g.setColor("#333").fillRect(0,b-1, r,b); b -= 2;h -= 2; // title bar if (options.title || options.src) { g.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20); const title = options.title||options.src; - g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2); + g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2); g.drawString(title.trim().substring(0, 13), x+25,y+3); if (options.title && options.src) { g.setFont("6x8", 1).setFontAlign(1, 1, 0); @@ -122,7 +124,7 @@ exports.show = function(options) { } // body text if (options.body) { - g.setColor(-1).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(text, x+6,y+4); + g.setColor(g.theme.fg).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(text, x+6,y+4); } if (options.render) { diff --git a/apps/notifyfs/ChangeLog b/apps/notifyfs/ChangeLog index ace651a0d..1c39bcbd5 100644 --- a/apps/notifyfs/ChangeLog +++ b/apps/notifyfs/ChangeLog @@ -8,3 +8,4 @@ 0.08: Don't turn on screen during Quiet Mode 0.09: Add onHide callback 0.10: Ensure dismissing a notification dismissal doesn't enter launcher if in clock mode +0.11: Improvements to help notifications work with themes, Bangle.js 2 support diff --git a/apps/notifyfs/notify.js b/apps/notifyfs/notify.js index cd8d108d5..a45d889f0 100644 --- a/apps/notifyfs/notify.js +++ b/apps/notifyfs/notify.js @@ -50,22 +50,24 @@ exports.show = function(options) { if (options.on===undefined) options.on=true; id = ("id" in options)?options.id:null; let size = options.size||120; - if (size>120) {size=120} - Bangle.setLCDMode("direct"); + if (size>120) size=120; + try { Bangle.setLCDMode("direct"); } catch(e) {} // not supported/needed on Bangle.js 2 let x = 0, y = 40, - w = 240, + w = g.getWidth(), h = size; // clear screen - g.setColor(options.bgColor||0).fillRect(0,0,g.getWidth(),g.getHeight()); + g.reset(); + if (options.bgColor!==undefined) g.setColor(options.bgColor); + g.clearRect(0,0,g.getWidth(),g.getHeight()); // top bar if (options.title||options.src) { - const title = options.title || options.src - g.setColor(options.titleBgColor||0x39C7).fillRect(x, y, x+w-1, y+30); - g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 3); + const title = options.title || options.src; + g.setColor(options.titleBgColor||"#333").fillRect(x, y, x+w-1, y+30); + g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 3); g.drawString(title.trim().substring(0, 13), x+5, y+3); if (options.title && options.src) { - g.setColor(-1).setFontAlign(1, 1, 0).setFont("6x8", 2); + g.setColor(g.theme.fg).setFontAlign(1, 1, 0).setFont("6x8", 2); // above drawing area, but we are fullscreen g.drawString(options.src.substring(0, 10), w-16, y-4); } @@ -73,8 +75,8 @@ exports.show = function(options) { } if (options.icon) { let i = options.icon, iw,ih; - if ("string"==typeof i) {iw=i.charCodeAt(0); ih=i.charCodeAt(1)} - else {iw=i[0]; ih=i[1]} + if ("string"==typeof i) {iw=i.charCodeAt(0); ih=i.charCodeAt(1);} + else {iw=i[0]; ih=i[1];} const iy=y ? (y+4) : (h-ih)/2; // show below title bar if present, otherwise center vertically g.drawImage(i, x+4,iy); x += iw+4;w -= iw+4; @@ -84,16 +86,13 @@ exports.show = function(options) { const maxRows = Math.floor((h-4)/16), // font=2*(6x8) maxChars = Math.floor((w-4)/12), text=fitWords(options.body, maxRows, maxChars); - g.setColor(-1).setFont("6x8", 2).setFontAlign(-1, -1, 0).drawString(text, x+4, y+4); + g.setColor(g.theme.fg).setFont("6x8", 2).setFontAlign(-1, -1, 0).drawString(text, x+4, y+4); } - if (options.render) { - const area={x:x, y:y, w:w, h:h} - options.render(area); - } - if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) { + if (options.render) + options.render({x:x, y:y, w:w, h:h}); + if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) Bangle.setLCDPower(1); // light up - } Bangle.on("touch", exports.hide); if (options.onHide) hideCallback = options.onHide; diff --git a/apps/waveclk/ChangeLog b/apps/waveclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/waveclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/waveclk/app-icon.js b/apps/waveclk/app-icon.js new file mode 100644 index 000000000..00892e9c3 --- /dev/null +++ b/apps/waveclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwge27dtAX4C+/dt+wFB/wCECIu3/dvBYNv34RC7/tCIu//99EYN9C4IpB74jG3379ovDFIIRBEYxHD/47D2wjHCIX+AQJHBCIIXBNZt/+5QBEZIgBAQX///9EZRWBBARHDEwhlC/9/EAJoBDQIOBNwyPCEYYCDJQ4CJSQ4CB0O2lojL2lwBIXFiwsK0f/KgUbuwRJo6cBPAO34cUmJHH7U/97tBgEGBIODEY/RXoOw7cAgHbtlxoojGx7hCjAjD20ANA1378MEIIAB4d0u5HGNAPYCAYAB2n2SQSPDjv3CIsF2lxEYto//+CoOGCIUt0O3EYtHvqMBvlw4UAgJQBqIjERgQDBsO+7FAhaMH64DB+4qB+3AgARG9uhIgQJD4dghd+7dLBQZoBaISwC4cArf27dpCIf/23f9uHCIQABhoNClsl20ttuwgYKBEAIAChOmCIOH/vx9ttwB2BDgMBAoIRBmnbpkbtk2ltsgAMCJQOwAgMBk+eq3AhiSBsE2GAX//4WCAAOBAoVbtt8mCJBgHHfYMHdgoRBott+zmDsEAn/tEwkC7UAVoYACgPbv4REAASTBEYY0BPIPwCJAjEu3Dvq8BCAnbsEwGgm2jbAC8EAjFvEAQREjuwDQXbvvx7cd2K/EgEb9oRCAoLOBjEAgk/A=")) diff --git a/apps/waveclk/app.js b/apps/waveclk/app.js new file mode 100644 index 000000000..7e1870aa7 --- /dev/null +++ b/apps/waveclk/app.js @@ -0,0 +1,72 @@ +function getImg() { + return require("heatshrink").decompress(atob("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")); +} +var IMAGEWIDTH = 176; +var IMAGEHEIGHT = 109; + +Graphics.prototype.setFontZCOOL = function() { +// Actual height 40 (46 - 7) +var widths = atob("CxAhEh8hJCIjGSMdCw=="); +var font = atob("AAAAAAAAAAAAAAAEAAAAAAAPAAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP8AAAAAAB/AAAAAAAfAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAfgAAAAAAf4AAAAAA/+AAAAAA//AAAAAB/+AAAAAB/+AAAAAD/8AAAAAD/8AAAAAD/4AAAAAH/4AAAAAH/wAAAAAD/wAAAAAAfwAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAP8AAAAAB//gAAAAP//4AAAB////AAAP////wAAf///h8AAH//8AfAAD//gAHwAA/8AAB8AAPgAAAfAAD4AAAHwAAfAAAB+AAHwAAAPgAB8AAAD4AAfAAAA+AAHwAAAPgAB8AAAD4AAPgAAA+AAD4AAAPgAA+AAAD8AAPgAAAfAAD4AAAHwAA/////8AAH/////AAB/////wAAf////4AAD////8AAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAA/AAAAAAAfgAAAAAAH4AAAAAAD8AAAAAAB+AAAAAAA/AAAAAAAPwAAH/gAH4D///4AD/////+AB//////gAf/////4AD////gAAA//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAB8AAAGAAAfAAAD4AAPwAAB/AAH4AAA/wAB8AAAf8AA/AAAP/AAfgAAP/gAHwAAH/4AD8AAD++AB+AAB/PgAfAAA/j4AHwAAfw+AB+AAP4PgAfwAH8D4AD8AH+A+AAfgD/AfAAD8B/AHwAAfg/gB8AAD8fwAfAAA/v4AHwAAH/8AB8AAA/+AAfAAAH/AAHwAAA/AAB8AAAHgAA+AAAAAAAPgAAAAAAD4AAAAAAA+AAAAAAAAAAAAAAABAAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAAAA+AAAAAAAHwAAMAAAB8AAfAAAAfAAHwAAAHwAB8AAAA+AAfAAAAPgAHwAfAD4AA+AP4A+AAPgD+APgAD4B/gB8AA+A/8AfAAPgf/AHwAD4H/wB8AA+D98AfgAHh+fgD4AB4/D4A+AAffg+A/gAH34Pg/4AB/8D8f8AAf+Aff+AAH/AH/+AAA/wB/+AAAP4Af+AAAD8AD/AAAA+AA/AAAAHgADAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAD+AAAAAAH/gAAAAAH/4AAAAAP/+AAAAAP//gAAAAf/z4AAAAf/w+AAAA//gPgAAA//gD4AAAf/AA+AAAH/AAPgAAB/AAD4AAAeAAA+AAACAAAPgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAAAA+AAAAAAAP/+AAAAP///gAB/////4AAf////+AAH/////AAB///+AAAAf/APgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAAAAAAAAAAAAACAAAAAAAHwAAAAAAB8AAAAAAAfAAAAAAAH4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAfwA+AAAH/+AHwAAf//gB8AAP//4AfAAH//+AHwAB/+PwB+AAfwB8APgAHwAfAD4AB8AHwA+AAfAB8APgAHwAfAB8AB8AHwAfAAfAB8AHwAHwAfAB8AB8AH4AfAAfAA+AH4AHwAPgH+AB8AD4H/gAfAA+H/wAHwAPv/4AB8AD//4AAfAA//wAAHwAP/wAAB8AD/gAAAAAAfgAAAAAABAAAAAAAAAAAAAAAAAHwAAAAAAH8AAAAAAD/gAAAAAD/4AAAAAD/+AAAAAD/3wAAAAD/x8AAAAD/4PgAAAD/4D4AAAD/4A+AAAB/4AHwAAB/4AB8AAB/4AAfAAB/4CAD4AB/8DgA+AAf8B8APgAD8AfAB8AA8AH4AfAAEAA+AH4AAAAPgA+AAAAD8AfgAAAAfAP4AAAAH4P8AAAAA+H+AAAAAPj+AAAAAB9/AAAAAAf/gAAAAAH/wAAAAAA/4AAAAAAP8AAAAAAB8AAAAAAAOAAAAAAAAAAAAAAAAAAAAD4AAAAAAA+AAAAAAAPwAAAAAAB8AAAAAAAfAAAAAAAHwAAAAAAB8AAAAAAAfAAAAAAAHwAAADAAB8AAAD4AAfAAAD+AAHwAAH/wAB8AAH/4AAfgAH/4AAH4AH/4AAA+AH/wAAAPgH/wAAAD4H/wAAAA+H/wAAAAPv/wAAAAD//wAAAAA//gAAAAAP/gAAAAAD/gAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAH4AAAAAAB+AAAHwAA/wAAB8AAf8AAA/gAP/AAAP8AH/wAAH/gD8+AAB/4B+PgAAf/A/j4AAPn4Pw+AAD4/H4HwAB+Pz8B8AAfB/+AfAAPwP/AHwAD4B/gB+AA+AfwAPgAfAP+AD4AHwD/wA+AB+B/+AfgAfw/PgPwAB/fj8D8AAP/wfh+AAB/4D8fAAAP+AfPwAAB/AH/4AAAPgA/8AAAAAAH/AAAAAAA/gAAAAAAPwAAAAAAB8AAAAAAAAAAAAAAAAAAAAAGAAAAAAAD4AAAAAAB/AAAAAAB/wAAAAAA/+AAAAAAf/gAAAAAf78AAAAAP8fAAAAAP+H4AAAAD+A+AAAAB/APgAAAAfAB8AAAAH4AfAAYAA+AD4AfAAPgA+AfwAD4APgf+AA+ABgf/AAHwAA//AAB8AA//AAAfAA//AAAHwA//AAAB+A/+AAAAPh/+AAAAD5/+AAAAA//+AAAAAP/+AAAAAB/8AAAAAAf8AAAAAAH8AAAAAAAAAAAAAAAAADgAOAAAAH4AfgAAAD+AP4AAAA/wD/AAAAH8AfwAAAB/AH8AAAAfwB/AAAADgAOAAAAAAAAAAAAAAAAA="); +var scale = 1; // size multiplier for this font +g.setFontCustom(font, 46, widths, 50+(scale<<8)+(1<<16)); +}; + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + var x = g.getWidth()/2; + var y = 24+20; + + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); + if (g.getWidth() == IMAGEWIDTH) + g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT); + else { + let scale = g.getWidth()/IMAGEWIDTH; + y *= scale; + g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT*scale,{scale:scale}); + } + // work out locale-friendly date/time + var date = new Date(); + var timeStr = require("locale").time(date,1); + var dateStr = require("locale").date(date).toUpperCase(); + // draw time + g.setFontAlign(0,0).setFont("ZCOOL"); + g.drawString(timeStr,x,y); + // draw date + y += 35; + g.setFontAlign(0,0,1).setFont("6x8"); + g.drawString(dateStr,g.getWidth()-8,g.getHeight()/2); + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.setTheme({bg:"#f0f",fg:"#fff",dark:true}).clear(); +// draw immediately at first, queue update +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// Show launcher when middle button pressed +Bangle.setUI("clock"); \ No newline at end of file diff --git a/apps/waveclk/app.png b/apps/waveclk/app.png new file mode 100644 index 000000000..ff43bb106 Binary files /dev/null and b/apps/waveclk/app.png differ diff --git a/apps/waveclk/screenshot.png b/apps/waveclk/screenshot.png new file mode 100644 index 000000000..7f05ce688 Binary files /dev/null and b/apps/waveclk/screenshot.png differ diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index 8e99f8faf..fd5d4d146 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -3,4 +3,5 @@ 0.04: Adjust "weather unknown" message according to Bluetooth connection. 0.05: Add wind direction. 0.06: Use setUI for launcher. -0.07: Add theme support and unknown icon. \ No newline at end of file +0.07: Add theme support and unknown icon. +0.08: Refactor and reduce widget ram usage. \ No newline at end of file diff --git a/apps/weather/app.js b/apps/weather/app.js index f5200c8ae..9d64583e9 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -1,5 +1,6 @@ (() => { const weather = require('weather'); + let current = weather.get(); function formatDuration(millis) { let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); @@ -10,16 +11,15 @@ } function draw() { - let w = weather.current; g.reset(); g.clearRect(0, 24, 239, 239); - weather.drawIcon(w.txt, 65, 90, 55); + weather.drawIcon(current.txt, 65, 90, 55); const locale = require("locale"); g.reset(); - const temp = locale.temp(w.temp-273.15).match(/^(\D*\d*)(.*)$/); + const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); let width = g.setFont("Vector", 40).stringWidth(temp[1]); width += g.setFont("Vector", 20).stringWidth(temp[2]); g.setFont("Vector", 40).setFontAlign(-1, -1, 0); @@ -31,19 +31,19 @@ g.setFontAlign(-1, 0, 0); g.drawString("Humidity", 135, 130); g.setFontAlign(1, 0, 0); - g.drawString(w.hum+"%", 225, 130); - if ('wind' in w) { + g.drawString(current.hum+"%", 225, 130); + if ('wind' in current) { g.setFontAlign(-1, 0, 0); g.drawString("Wind", 135, 142); g.setFontAlign(1, 0, 0); - g.drawString(locale.speed(w.wind)+' '+w.wrose.toUpperCase(), 225, 142); + g.drawString(locale.speed(current.wind)+' '+current.wrose.toUpperCase(), 225, 142); } g.setFont("6x8", 2).setFontAlign(0, 0, 0); - g.drawString(w.loc, 120, 170); + g.drawString(current.loc, 120, 170); g.setFont("6x8", 1).setFontAlign(0, 0, 0); - g.drawString(w.txt.charAt(0).toUpperCase()+w.txt.slice(1), 120, 190); + g.drawString(current.txt.charAt(0).toUpperCase()+current.txt.slice(1), 120, 190); drawUpdateTime(); @@ -51,8 +51,8 @@ } function drawUpdateTime() { - if (!weather.current || !weather.current.time) return; - let text = `Last update received ${formatDuration(Date.now() - weather.current.time)} ago`; + if (!current || !current.time) return; + let text = `Last update received ${formatDuration(Date.now() - current.time)} ago`; g.reset(); g.clearRect(0, 202, 239, 210); g.setFont("6x8", 1).setFontAlign(0, 0, 0); @@ -60,8 +60,9 @@ } function update() { + current = weather.get(); NRF.removeListener("connect", update); - if (weather.current) { + if (current) { draw(); } else if (NRF.getSecurityStatus().connected) { E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?"); diff --git a/apps/weather/lib.js b/apps/weather/lib.js index 6a57e1f00..f08df4a4a 100644 --- a/apps/weather/lib.js +++ b/apps/weather/lib.js @@ -1,6 +1,6 @@ const storage = require('Storage'); -let expiryTimeout = undefined; +let expiryTimeout; function scheduleExpiry(json) { if (expiryTimeout) { clearTimeout(expiryTimeout); @@ -9,53 +9,35 @@ function scheduleExpiry(json) { let expiry = "expiry" in json ? json.expiry : 2*3600000; if (json.weather && json.weather.time && expiry) { let t = json.weather.time + expiry - Date.now(); - expiryTimeout = setTimeout(() => { - expiryTimeout = undefined; - - let json = storage.readJSON('weather.json')||{}; - delete json.weather; - storage.write('weather.json', json); - - exports.current = undefined; - exports.emit("update"); - }, t); + expiryTimeout = setTimeout(update, t); } } -/** - * Convert numeric direction into human-readable label - * - * @param {number} deg - Direction in degrees - * @return {string|null} - Nearest compass point - */ -function compassRose(deg) { - if (typeof deg === 'undefined') return null; - while (deg<0 || deg>360) { - deg = (deg+360)%360; - } - return ['n','ne','e','se','s','sw','w','nw','n'][Math.floor((deg+22.5)/45)]; -} - -function setCurrentWeather(json) { - scheduleExpiry(json); - exports.current = json.weather; -} - function update(weatherEvent) { - let weather = Object.assign({}, weatherEvent); - weather.time = Date.now(); - if ('wdir' in weather) { - weather.wrose = compassRose(weather.wdir); - } - delete weather.t; - let json = storage.readJSON('weather.json')||{}; - json.weather = weather; + + if (weatherEvent) { + let weather = weatherEvent.clone(); + delete weather.t; + weather.time = Date.now(); + if (weather.wdir != null) { + // Convert numeric direction into human-readable label + let deg = weather.wdir; + while (deg<0 || deg>360) { + deg = (deg+360)%360; + } + weather.wrose = ['n','ne','e','se','s','sw','w','nw','n'][Math.floor((deg+22.5)/45)]; + } + + json.weather = weather; + } + else { + delete json.weather; + } + storage.write('weather.json', json); - - setCurrentWeather(json); - - exports.emit("update"); + scheduleExpiry(json); + exports.emit("update", json.weather); } const _GB = global.GB; @@ -64,7 +46,11 @@ global.GB = (event) => { if (_GB) setTimeout(_GB, 0, event); }; -setCurrentWeather(storage.readJSON('weather.json')||{}); +exports.get = function() { + return storage.readJSON('weather.json').weather; +} + +scheduleExpiry(storage.readJSON('weather.json')||{}); exports.drawIcon = function(cond, x, y, r) { function drawSun(x, y, r) { diff --git a/apps/weather/widget.js b/apps/weather/widget.js index ba0c8604d..4871ceda4 100644 --- a/apps/weather/widget.js +++ b/apps/weather/widget.js @@ -1,45 +1,23 @@ (() => { const weather = require('weather'); - function draw() { - const w = weather.current; - if (!w) return; - g.reset(); - g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23); - if (w.txt) { - weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5); - } - if (w.temp) { - let t = require('locale').temp(w.temp-273.15); // applies conversion - t = t.match(/[\d\-]*/)[0]; // but we have no room for units - g.reset(); - g.setFontAlign(0, 1); // center horizontally at bottom of widget - g.setFont('6x8', 1); - g.drawString(t, this.x+10, this.y+24); - } - } - var dirty = false; - function update() { - if (!WIDGETS["weather"].width) { - WIDGETS["weather"].width = 20; - Bangle.drawWidgets(); - } else if (Bangle.isLCDOn()) { - WIDGETS["weather"].draw(); - } else { - dirty = true; + weather.on("update", w => { + if (w) { + if (!WIDGETS["weather"].width) { + WIDGETS["weather"].width = 20; + Bangle.drawWidgets(); + } else if (Bangle.isLCDOn()) { + WIDGETS["weather"].draw(); + } else { + dirty = true; + } + } + else { + WIDGETS["weather"].width = 0; + Bangle.drawWidgets(); } - } - - function hide() { - WIDGETS["weather"].width = 0; - Bangle.drawWidgets(); - } - - weather.on("update", () => { - if (weather.current) update(); - else hide(); }); Bangle.on('lcdPower', on => { @@ -51,7 +29,23 @@ WIDGETS["weather"] = { area: "tl", - width: weather.current ? 20 : 0, - draw: draw, + width: weather.get() ? 20 : 0, + draw: function() { + const w = weather.get(); + if (!w) return; + g.reset(); + g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23); + if (w.txt) { + weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5); + } + if (w.temp) { + let t = require('locale').temp(w.temp-273.15); // applies conversion + t = t.match(/[\d\-]*/)[0]; // but we have no room for units + g.reset(); + g.setFontAlign(0, 1); // center horizontally at bottom of widget + g.setFont('6x8', 1); + g.drawString(t, this.x+10, this.y+24); + } + }, }; })(); diff --git a/apps/widpedom/ChangeLog b/apps/widpedom/ChangeLog index 8844a7d44..2f36c7647 100644 --- a/apps/widpedom/ChangeLog +++ b/apps/widpedom/ChangeLog @@ -15,3 +15,7 @@ 0.16: Settings option to show large digits in widget area 0.17: Cope with 2v10+ firmware sometimes reporting >1 step 0.18: Adjust widget width when displaying large text +0.19: Allow goal in large font mode + Stop goal drawing outside widget area + Fix issue with widget overwrite in large font mode + Memory usage enhancements diff --git a/apps/widpedom/settings.js b/apps/widpedom/settings.js index 754b636c9..4455ce7d7 100644 --- a/apps/widpedom/settings.js +++ b/apps/widpedom/settings.js @@ -32,7 +32,7 @@ onchange: (g) => { s.goal = g s.progress = !!g - save() + save(); }, }, 'Show Progress': { @@ -40,7 +40,7 @@ format: () => (s.progress ? 'Yes' : 'No'), onchange: () => { s.progress = !s.progress - save() + save(); }, }, 'Large Digits': { @@ -48,7 +48,7 @@ format: () => (s.large ? 'Yes' : 'No'), onchange: () => { s.large = !s.large - save() + save(); }, }, 'Hide Widget': { @@ -56,7 +56,7 @@ format: () => (s.hide ? 'Yes' : 'No'), onchange: () => { s.hide = !s.hide - save() + save(); }, }, '< Back': back, diff --git a/apps/widpedom/widget.js b/apps/widpedom/widget.js index fbc2a87bb..3c861cf54 100644 --- a/apps/widpedom/widget.js +++ b/apps/widpedom/widget.js @@ -23,78 +23,6 @@ return (key in settings) ? settings[key] : DEFAULTS[key]; } - function drawProgress(stps) { - if (setting('hide')) return; - const width = 24, half = width/2; - const goal = setting('goal'), left = Math.max(goal-stps,0); - const c = left ? "#00f" : "#090"; // blue or dark green - g.setColor(c).fillCircle(this.x + half, this.y + half, half); - const TAU = Math.PI*2; - if (left) { - const f = left/goal; // fraction to blank out - let p = []; - p.push(half,half); - p.push(half,0); - if(f>1/8) p.push(0,0); - if(f>2/8) p.push(0,half); - if(f>3/8) p.push(0,width); - if(f>4/8) p.push(half,width); - if(f>5/8) p.push(width,width); - if(f>6/8) p.push(width,half); - if(f>7/8) p.push(width,0); - p.push(half - Math.sin(f * TAU) * half); - p.push(half - Math.cos(f * TAU) * half); - for (let i = p.length; i; i -= 2) { - p[i - 2] += this.x; - p[i - 1] += this.y; - } - g.setColor(g.theme.bg).fillPoly(p); - } - } - - // show the step count in the widget area in a readable sized font - function draw_large(st) { - this.width = 12 * st.length + 3; - g.reset(); - g.clearRect(this.x, this.y, this.x + this.width, this.y + 16); // erase background - g.setColor(g.theme.fg); - g.setFont("6x8",2); - g.setFontAlign(-1, -1); - g.drawString(st, this.x + 4, this.y + 2); - } - - // draw your widget - function draw() { - if (setting('hide')) return; - var width = 24; - if (stp_today > 99999){ - stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters - } - let stps = stp_today.toString(); - if (setting('large')) { - draw_large.call(this, stps); - return; - } - g.reset().clearRect(this.x, this.y, this.x + width, this.y + 23); // erase background - if (setting('progress')){ drawProgress.call(this, stps); } - g.setColor(g.theme.fg); - if (stps.length > 3){ - stps = stps.slice(0,-3) + "," + stps.slice(-3); - g.setFont("4x6", 1); // if big, shrink text to fix - } else { - g.setFont("6x8", 1); - } - g.setFontAlign(0, 0); // align to x: center, y: center - g.drawString(stps, this.x+width/2, this.y+19); - // on low bpp screens, draw 1 bit. Currently there is no getBPP so we just do it based on resolution - g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(width-10)/2,this.y+2); - } - - function reload() { - loadSettings() - draw() - } - Bangle.on('step', stepCount => { var steps = stepCount-lastStepCount; if (lastStepCount===undefined || steps<0) steps=1; @@ -115,11 +43,11 @@ } lastUpdate = date //console.log("up: " + up + " stp: " + stp_today + " " + date.toString()); - if (Bangle.isLCDOn()) WIDGETS["wpedom"].draw(); + WIDGETS["wpedom"].redraw(); }); // redraw when the LCD turns on Bangle.on('lcdPower', function(on) { - if (on) WIDGETS["wpedom"].draw(); + if (on) WIDGETS["wpedom"].redraw(); }); // When unloading, save state E.on('kill', () => { @@ -134,10 +62,80 @@ // add your widget WIDGETS["wpedom"]={area:"tl",width:26, - draw:draw, - reload:reload, - getSteps:()=>stp_today - }; + redraw:function() { // work out the width, and queue a full redraw if needed + let stps = stp_today.toString(); + let newWidth = 24; + if (setting('hide')) + newWidth = 0; + else { + if (setting('large')) { + newWidth = 12 * stps.length + 3; + if (setting('progress')) + newWidth += 24; + } + } + if (newWidth!=this.width) { + // width has changed, re-layout all widgets + this.width = newWidth; + Bangle.drawWidgets(); + } else { + // width not changed - just redraw + WIDGETS["wpedom"].draw(); + } + }, + draw:function() { + if (setting('hide')) return; + if (stp_today > 99999) + stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters + let stps = stp_today.toString(); + g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background + if (setting('progress')) { + const width = 23, half = 11; + const goal = setting('goal'), left = Math.max(goal-stps,0); + // blue or dark green + g.setColor(left ? "#08f" : "#080").fillCircle(this.x + half, this.y + half, half); + if (left) { + const TAU = Math.PI*2; + const f = left/goal; // fraction to blank out + let p = []; + p.push(half,half); + p.push(half,0); + if(f>1/8) p.push(0,0); + if(f>2/8) p.push(0,half); + if(f>3/8) p.push(0,width); + if(f>4/8) p.push(half,width); + if(f>5/8) p.push(width,width); + if(f>6/8) p.push(width,half); + if(f>7/8) p.push(width,0); + p.push(half - Math.sin(f * TAU) * half); + p.push(half - Math.cos(f * TAU) * half); + g.setColor(g.theme.bg).fillPoly(g.transformVertices(p,{x:this.x,y:this.y})); + } + g.reset(); + } + if (setting('large')) { + g.setFont("6x8",2); + g.setFontAlign(-1, 0); + g.drawString(stps, this.x + (setting('progress')?28:4), this.y + 12); + } else { + let w = 24; + if (stps.length > 3){ + stps = stps.slice(0,-3) + "," + stps.slice(-3); + g.setFont("4x6", 1); // if big, shrink text to fix + } else { + g.setFont("6x8", 1); + } + g.setFontAlign(0, 0); // align to x: center, y: center + g.drawString(stps, this.x+w/2, this.y+19); + g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(w-10)/2,this.y+2); + } + }, + reload:function() { + loadSettings(); + WIDGETS["wpedom"].redraw(); + }, + getSteps:()=>stp_today + }; // Load data at startup let pedomData = require("Storage").readJSON(PEDOMFILE,1); if (pedomData) { diff --git a/core b/core index 2aac601e3..0fd608f08 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 2aac601e38d659876eb7db5aebc7a12dd3c39da7 +Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba diff --git a/index.html b/index.html index a5ae7bff0..e1c195f7d 100644 --- a/index.html +++ b/index.html @@ -40,6 +40,10 @@
STOP! This page must be served over HTTPS. Please reload this page via HTTPS.
+