diff --git a/apps/ac_ac/ChangeLog.txt b/apps/ac_ac/ChangeLog similarity index 100% rename from apps/ac_ac/ChangeLog.txt rename to apps/ac_ac/ChangeLog diff --git a/apps/arrow/README.md b/apps/arrow/README.md index 4b77dbc42..2833eab24 100644 --- a/apps/arrow/README.md +++ b/apps/arrow/README.md @@ -43,3 +43,7 @@ charge. This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev) + +Written by: [Hugh Barney](https://github.com/hughbarney) For support +and discussion please post in the [Bangle JS +Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/barometer/ChangeLog b/apps/barometer/ChangeLog index 031a86cdc..de3a5cb96 100644 --- a/apps/barometer/ChangeLog +++ b/apps/barometer/ChangeLog @@ -1,2 +1,3 @@ 0.01: Display pressure as number and hand 0.02: Use theme color +0.03: workaround for some firmwares that return 'undefined' for first call to barometer diff --git a/apps/barometer/app.js b/apps/barometer/app.js index 2ce95864b..77d4c974f 100644 --- a/apps/barometer/app.js +++ b/apps/barometer/app.js @@ -110,9 +110,13 @@ drawScaleLabels(); drawIcons(); try { - Bangle.getPressure().then(data => { - drawHand(Math.round(data.pressure)); - }); + function baroHandler(data) { + if (data===undefined) // workaround for https://github.com/espruino/BangleApps/issues/1429 + setTimeout(() => Bangle.getPressure().then(baroHandler), 500); + else + drawHand(Math.round(data.pressure)); + } + Bangle.getPressure().then(baroHandler); } catch(e) { print(e.message); print("barometer not supporter, show a demo value"); diff --git a/apps/barometer/metadata.json b/apps/barometer/metadata.json index 68af55c68..a385f2be2 100644 --- a/apps/barometer/metadata.json +++ b/apps/barometer/metadata.json @@ -1,7 +1,7 @@ { "id": "barometer", "name": "Barometer", "shortName":"Barometer", - "version":"0.02", + "version":"0.03", "description": "A simple barometer that displays the current air pressure", "icon": "barometer.png", "tags": "tool,outdoors", diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 702a8091e..4c3d3b930 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -45,3 +45,4 @@ 0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035) 0.40: Bootloader now rebuilds for new firmware versions 0.41: Add Keyboard and Mouse Bluetooth HID option +0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname..boot.js diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 1b826de5a..63424bfbf 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -195,7 +195,19 @@ if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares // Append *.boot.js files // These could change bleServices/bleServiceOptions if needed -require('Storage').list(/\.boot\.js/).forEach(bootFile=>{ +var getPriority = /.*\.(\d+)\.boot\.js$/; +require('Storage').list(/\.boot\.js/).sort((a,b)=>{ + var aPriority = a.match(getPriority); + var bPriority = b.match(getPriority); + if (aPriority && bPriority){ + return parseInt(aPriority[1]) - parseInt(bPriority[1]); + } else if (aPriority && !bPriority){ + return -1; + } else if (!aPriority && bPriority){ + return 1; + } + return a > b; +}).forEach(bootFile=>{ // we add a semicolon so if the file is wrapped in (function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() // which would cause an error! diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index ebbf762c0..4cbfd9c59 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.41", + "version": "0.42", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/bowserWF/README.md b/apps/bowserWF/README.md new file mode 100644 index 000000000..19dc84c83 --- /dev/null +++ b/apps/bowserWF/README.md @@ -0,0 +1,6 @@ +# Bowser Watchface + +Show your evil character. +With style! + +Bowser jumps once every minute to advance the clock. hehe diff --git a/apps/bowserWF/app-icon.js b/apps/bowserWF/app-icon.js new file mode 100644 index 000000000..7d040b0ad --- /dev/null +++ b/apps/bowserWF/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("Ly+EARERERERERERERERER//8RERERERERERERERERERERERERERERzP//d3EREREREREREREREREREREREREcz//3dxERERERERERERERERERERERERHMz/93dxERERERERERERERERERERERERd8zHd3f/8REREREREREREREREREREREXfMx3d3//ERERERERERERERERERERERF3d3d3d//3cRHBEREREREREREREREREXd3d3d3f/93fMDMERERERERERERERERF3d3d3d3//d3zAzBERERERERERERERERd3d3fMd3f//8zMwREREREREREREREREXd3d8zMd3f/zMzMERERERERERERERERF3d3fMzHd3/8zMzBERERERERER93d3//d3d3x3/Mx3fMzP8REREREREXzP/3d//3d3d8d3fMzMz/8RERERERERF8z/93f/93d3fHd3zMzM//ERERER//93zM//93//d3d3z/d/9////xEREREcz/d3zM/3d///d3d8zHERH/ERERERERHM/3d8zP93f//3d3fMxxER/xERERER/3zMd3d3d3d3f///d3zM8REREREREREf93d3d3d3d3d3d///d3zMERERERERERH/d3d3d3d3d3d3f//3d8zBERERERERERd3d3f///d3d3d3//93fMzP8RERERERH//3d3zP/3d//////3d3d8wRERERERER//93d8z/93f/////93d3fMEREREREREc//d3fMx3f//MzMd3d3d3dxERERERERHMx3d3d3d//8zMAPd3zMzHERERERERH/d3d3d3d3f3fMzwDMzAD8zBERERERER/3d3d3d3d393zM8AzMwA/MwRERERERH913d///d3d/d3AAzMzMAAzMERERERERREd3fM/3d//3d//MzMzMwP/BEREREREURHd3zP93f/93f/zMzMzMD/wRERERERFP/3d8zHd3//d3d3zMzMzAAMERERERER//93d3d3d//3d3d8zMzMwP8BEREREREf//d3d3d3f/93d3fMzMzMD/ARERERERFMzHf/93d3/3d3d3EczM/wABERERERERR3x3fMd3f/93d3dxEREREREREREREREUd8d3zHd3//d3d3cRERERERERERERERFHd3d3d///d3d3dxERERERERERERERERR3d3f///93d3dxEREREREREREREREREUd3d3////d3d3cRERERERERERERERERH//////3d3d3d8ERERERERERERERERER/////MzMx3fMz/8REREREREREREREREf////zMzMd3zM//EREREREREREREREREf/8zMzMzMzMzP//ERERERERERERERERERzMzM/8zP/xEREREREREREREREREREREczMzP/Mz/8REREREREREREREREREREczMzMz//8///xERERERERERERERERERA=")) diff --git a/apps/bowserWF/app.js b/apps/bowserWF/app.js new file mode 100644 index 000000000..e53d945cc --- /dev/null +++ b/apps/bowserWF/app.js @@ -0,0 +1,102 @@ +var sprite = { + width : 47, height : 47, bpp : 3, + transparent : 1, + buffer : require("heatshrink").decompress(atob("kmSpICFn/+BAwCImV//VICJuT//SogRMpmT/2SCJtSyQDB/4RMymRkmX/gRLygDC3/piVhCJElAYf/pNIkgRIlIDCl/6pVBkIRIGwWJEYPypMJCI9KGwQRBLANIPRI2CGoPkyVCBwmeyVLTYNJom8yImBz4gEqV/6Vf+g2BPwf/IIq8C/+kyVRkgDBp/5CIX/+mkz/+y/9BIOf0v6///5LdCz+kCIOk34RBYQMSp5XBGQVk/pNBAQP/9IyBxGSv4yCk/1OIK8EC4QgEpM/JgJ+EGoIRBTApQCEYvplLOFXIIdBO4SqBeQJABGoeTDQMlk5WCAAPSYQLgEz4aBlM/9IgB/7CCcAvP/QsBiVfUwOJBgUiCIcmpAVCy/+pMAKwMkRgIRCp6VBAwW6qVOgmSgPkwgRDv53E6WSuEkyEPRgmf2VJv5HBl2SgAKBwEJRgnJiVKp/Sr/0y/yBQOQv56DKwVSv2STwO/DgWD/BADmaDByRoBYoQRCgFCCIf/+jgDNwOUAwMg/kSPQbODX4IJBAwUH8B6DsmRl5oBl7OBklMyV+gBoDycSxMpiVLZwS8EAQeYyjaByR6BBIJBDAQnEIgbFCogOFRgQDBr//I4L0EAQsxAYP//5WCGQ6MCAAKbCpKYEAQiMB//kIQOUyf+CJF/CIIEBTYOfcgQRHBQv/CJKnBpP8GRTCDJIPkGRQCB5I3C/n/EZUgA")) +}; + +const boxes = { + width : 122, height : 56, bpp : 3, + transparent : 1, + buffer : require("heatshrink").decompress(atob("kmZkmSpICPwgDBmQUQAQMJAYNkFiOSiQDB5JESAYQsSpADByYsSyBZBydt23bAR+wgFJkwUQAQNggGSposR23AgMkzZESwECpM2IiUAgmSFiW2gDlBFiVsgDlBFiXYgDNBL4MDWZy2FgEGWZy2FgENWZy2EL4MbWZpTBWwZfBXJpTCWwZiCWZpTBWwZiCWZsbWwhiCWZpWCWwTORWwgXRWwgXRWwZESWwZESWwZESWwYXRWwgXRW362/W362/W362/W362/W362/W362/W362/W362/W362/W362/WwuAgazOWwsAgyzOWwsAhqzOWwhfBjazNKYK2DL4K5NKYS2DMQSzNKYK2DMQSzNja2EMQSzNKwS2CZyK2EC6K2EC6K2DIiS2DIiS2DIiUAFoMAAFTkBFtckyAtrLgWSpICnLIIsqyVAgAsqpIA=")) +}; + +const background = { + width : 176, height : 176, bpp : 3, + transparent : 5, + buffer : require("heatshrink").decompress(atob("kmSpIC/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ATWAgEAIP1///8iRB8gf/AAOCIPdIIARBBoJB/+E4IP4ABghB9v4CB8BB5g/92//9pB7wP/97FEIO9IgDACAAn8iVBIOlHH4xBDnA+wyY9IAAmB/BB//5B/IOQ/OAARBup5B/yV/IP5B/IP5BRt5B7/wDC7aD8/w+B+3bBgP7IP5B7HYNt23/AQPfIPX/9oCC24IDINwCBIRAAHIOACBHI3+g4EC/l/4BByAQkA//wpED//4gGAhJB3pMAgQFBgEBH3AC/AX4C/AX4C/AX4C/AX4C/AUOAgBB/v//ghB9gf///gH3UgiVIIAJBBwRB5j+CIIf8uBB5//wIIXb//+hJB6o/92/7v5B7/0/97GCIPYAG4MgIP/BjkSIP34/hB//5B/AAQ+0IP5B/IP5BN7ZB97///wCBIPX93yAB2wCB+5B5tv//dt24CB35B5v/+n/t+P/I4PH8ESIO38gFA/+CgH/+EIgiD3gACCPoMAgQ+2AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ASVIgAACgRB/IPY8GkAHBiRB/IPBLKgJB/IP5B/AQUAkmQghB/IP2AgEAyVAiRB/IP5BBpMAIP5B/IIUkgBB/IP5BpoAsBgJBOgEEIIoIBIP5BlyE27dt2EEIJ4CBBAlIgRBgpEAhu2IIO24ESQwxB/IJQhGkEJIL8GHwQCDgOweQpB/IKMkwAKJILVgAofYeQhBzsEAIKICLoESILmBQARBBtuwgZB3kA4B4ENIgJBcpMAIMYCDIOcAgEbHYgCGsEJkhEBE6cBIP5BZfYQ+JIIkDsEBIP5BVyEAIKtAHxgCDwBEBINk2IKCGCIKmSpECIP5BUkEBHyACD2BBUFoMJIP5BSpEbHyQCDIP5BXkmAIP5B/AQcAbKJB/ILH/AAP8hM/AgWSv4KCAAP+gmfAoXJk4ME//gpIEC8mTBgvwkgEC+QRDAAX4gVPAgP5kgsCLwWQh/kMIUf5LuFg4jBAoMBKAJ5EwF/AoUA/yFFoE/CI6RDgY+BCIQsDIP5B/IP5B/IP5B/IJ/AIJfghJBKv0EIJcAIJfwIP5BMhMAAAMEz5BGgmABoVJII9IBgUkII8kBgUSII8CoAMBhJB/IIsQoMAYoP/AAP4YpAMC/+BII9/BgXAYpAMC8DFIBgXwIIcCIP6DCgkQh/kCIRBIbQcBIJAFCgBBICI5BE/IRDFgQA=")) +}; + +numbersDims = { + width: 20, + height: 44 +}; +const numbers = [ + require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKYQIDAwUEBxMAAQNAgECpMgAQMkB4IOIAQQLCgEQBwQaBgEBB1oCBBwYCCiRWDCIRWEO5wOHAX4CnA=")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKYIIEwEAggOKNIQODyAHCBxQsWB3TUFgMgA4sSBwzU/AVA=")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBwwyDDwUSCgVAAwIOBEwI7EpI7FBw4FDghZGHwgOEF4Y+CEYQ+DBxQADNAIAFNAIOFa/4CoA=")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKosSAwsBBw4aCoEAgQjEBoIpEBwtIBoIUEwEAggUDBwwyDDoWQA4ZWHhIIEJQoOCgI+EBwMQEAYOJO4oLBO4oRDJQrX/AU4")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKgQIDwAGBgQOJNQYOCyAHDBxEggB6BBwYDBiVABxIjBCIIODF4YOEAAkBV40QBwxiDNAosEB0IC/AUg")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5UFkmQAwkCBxIdGCIIIDBxAsTgAaEkEASooOBiQOVJQgOBiBKDBxMSJQwRBLIgRCBwjX/AVA=")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKgYICAwcCBxADBiQdDkEANYoOGEAYyEHYoOIHYqfFBxIdDBAMQFgZHCBysSFgwRBO46GFa/4CnA")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5VGiAGFgIOIDQUgBwUCEYQOJGQYNBHAlADQgOHwEAggUDpANBCgYpBBwmQAwJiGhIjDB1gC/AU4A=")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKYYICAwcEBxGQgAaDgVJgACBDQQOJgB6CBwcAiQODHa4AEhIRBpAHDiARBwAGCgIgCFIYOCFIYOHiQrEJQxlCBwzX/AVAA=")), + require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBzkSTAsBHYoOIL4gOCMooOENAYOCoA4EBwoqDgiGGF4gOEa/4CoA=")), +]; +digitPositions = [ // relative to the box + {x:13, y:6}, {x:32, y:6}, + {x:74, y:6}, {x:93, y:6}, +]; + +var drawTimeout; +const animation_duration = 1; // seconds +const animation_steps = 20; +const jump_height = 45; // top coordinate of the jump +const seconds_per_minute = 60; + +function draw() { + const now = new Date(); + g.drawImage(background, 0, 0); + var boxTL_x = 27; var boxTL_y = 29; + var sprite_TL_x = 72; var sprite_TL_y = 161 - sprite.height; + const seconds = now.getSeconds()%seconds_per_minute + now.getMilliseconds()/1000; + const hours = now.getHours(); + const minutes = now.getMinutes(); + + var time_advance = seconds / animation_duration; + + if (time_advance < 0.5) { + sprite_TL_y += (jump_height - sprite_TL_y) * time_advance * 2; + } else if (time_advance < 1) { + sprite_TL_y = jump_height + (sprite_TL_y-jump_height) * (time_advance-0.5) * 2; + } + const box_penetration = boxTL_y + boxes.height - sprite_TL_y; + if (box_penetration > 0) { + boxTL_y -= box_penetration; + } + g.drawImage(boxes, boxTL_x, boxTL_y); + g.drawImage(numbers[(hours / 10) >> 0], boxTL_x+digitPositions[0].x, boxTL_y+digitPositions[0].y); + g.drawImage(numbers[(hours % 10) >> 0], boxTL_x+digitPositions[1].x, boxTL_y+digitPositions[1].y); + g.drawImage(numbers[(minutes / 10) >> 0], boxTL_x+digitPositions[2].x, boxTL_y+digitPositions[2].y); + g.drawImage(numbers[(minutes % 10) >> 0], boxTL_x+digitPositions[3].x, boxTL_y+digitPositions[3].y); + g.drawImage(sprite, sprite_TL_x, sprite_TL_y); + Bangle.drawWidgets(); + + const timeout = time_advance <= 1? + animation_duration / animation_steps + : (seconds_per_minute - seconds); + setTimeout( _=>{ + drawTimeout = undefined; + draw(); + }, timeout * 1000); +} + +// Clear the screen once, at startup +g.setTheme({bg:"#00f",fg:"#fff",dark:true}).clear(); + +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) { + clearTimeout(drawTimeout); + } + drawTimeout = undefined; + } +}); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); + +draw(); diff --git a/apps/bowserWF/app.png b/apps/bowserWF/app.png new file mode 100644 index 000000000..724d63078 Binary files /dev/null and b/apps/bowserWF/app.png differ diff --git a/apps/bowserWF/metadata.json b/apps/bowserWF/metadata.json new file mode 100644 index 000000000..22df2dea4 --- /dev/null +++ b/apps/bowserWF/metadata.json @@ -0,0 +1,14 @@ +{ "id": "bowserWF", + "name": "Bowser Watchface", + "shortName":"Bowser Watchface", + "version":"0.01", + "description": "Let bowser show you the time", + "icon": "app.png", + "tags": "", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"bowserWF.app.js","url":"app.js"}, + {"name":"bowserWF.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 3354a19e8..58d002f22 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -17,3 +17,6 @@ 0.06: Fix bug if no request waiting time is set Fix bug if no connection data was cached Fix error during disconnect +0.07: Recorder icon only blue if values actually arive + Adds some preset modes and a custom one + Restructure the settings menu diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md index f0c7775c2..42ad619bd 100644 --- a/apps/bthrm/README.md +++ b/apps/bthrm/README.md @@ -29,6 +29,7 @@ Heart Rate Service (`180D`) and characteristic (`2A37`). So far it has been tested on: * CooSpo Bluetooth Heart Rate Monitor +* Wahoo TICKR X 2 ## Internals @@ -36,7 +37,6 @@ This replaces `Bangle.setHRMPower` with its own implementation. ## TODO -* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off * A widget to show connection state? * Specify a specific device by address? diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 6ac6382d6..339f6f8c6 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -548,9 +548,7 @@ E.on("kill", ()=>{ if (gatt && gatt.connected){ log("Got killed, trying to disconnect"); - var promise = gatt.disconnect(); - promise.then(()=>log("Disconnected on kill")); - promise.catch((e)=>log("Error during disconnnect on kill", e)); + var promise = gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e)); } }); } diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json index c973eef25..64e638b8a 100644 --- a/apps/bthrm/default.json +++ b/apps/bthrm/default.json @@ -1,4 +1,5 @@ { + "mode": 1, "enabled": true, "replace": true, "debuglog": false, @@ -6,6 +7,12 @@ "allowFallback": true, "warnDisconnect": false, "fallbackTimeout": 10, + "custom_replace": false, + "custom_debuglog": false, + "custom_startWithHrm": false, + "custom_allowFallback": false, + "custom_warnDisconnect": false, + "custom_fallbackTimeout": 10, "gracePeriodNotification": 0, "gracePeriodConnect": 0, "gracePeriodService": 0, diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index 9aea5f3aa..1c21269e2 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,7 +2,7 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.06", + "version": "0.07", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", "type": "app", @@ -12,7 +12,7 @@ "storage": [ {"name":"bthrm.app.js","url":"bthrm.js"}, {"name":"bthrm.recorder.js","url":"recorder.js"}, - {"name":"bthrm.boot.js","url":"boot.js"}, + {"name":"bthrm.0.boot.js","url":"boot.js"}, {"name":"bthrm.img","url":"app-icon.js","evaluate":true}, {"name":"bthrm.settings.js","url":"settings.js"}, {"name":"bthrm.default.json","url":"default.json"} diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js index 938990362..21345a907 100644 --- a/apps/bthrm/recorder.js +++ b/apps/bthrm/recorder.js @@ -32,7 +32,7 @@ Bangle.removeListener('BTHRM', onHRM); if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder"); }, - draw : (x,y) => g.setColor((Bangle.isBTHRMConnected && Bangle.isBTHRMConnected())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) + draw : (x,y) => g.setColor((bpm != "")?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) }; } }) diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js index 62d2e7ea3..beefb00e9 100644 --- a/apps/bthrm/settings.js +++ b/apps/bthrm/settings.js @@ -20,182 +20,147 @@ var mainmenu = { '': { 'title': 'Bluetooth HRM' }, '< Back': back, - 'Use BT HRM': { - value: !!settings.enabled, - format: v => settings.enabled ? "On" : "Off", + 'Mode': { + value: 0 | settings.mode, + min: 0, + max: 3, + format: v => ["Off", "Default", "Both", "Custom"][v], onchange: v => { - writeSettings("enabled",v); + settings.mode = v; + switch (v){ + case 0: + writeSettings("enabled",false); + break; + case 1: + writeSettings("enabled",true); + writeSettings("replace",true); + writeSettings("debuglog",false); + writeSettings("startWithHrm",true); + writeSettings("allowFallback",true); + writeSettings("fallbackTimeout",10); + break; + case 2: + writeSettings("enabled",true); + writeSettings("replace",false); + writeSettings("debuglog",false); + writeSettings("startWithHrm",false); + writeSettings("allowFallback",false); + break; + case 3: + writeSettings("enabled",true); + writeSettings("replace",settings.custom_replace); + writeSettings("debuglog",settings.custom_debuglog); + writeSettings("startWithHrm",settings.custom_startWithHrm); + writeSettings("allowFallback",settings.custom_allowFallback); + writeSettings("fallbackTimeout",settings.custom_fallbackTimeout); + break; + } + writeSettings("mode",v); } }, - 'Replace HRM': { - value: !!settings.replace, - format: v => settings.replace ? "On" : "Off", - onchange: v => { - writeSettings("replace",v); - } - }, - 'Start with HRM': { - value: !!settings.startWithHrm, - format: v => settings.startWithHrm ? "On" : "Off", - onchange: v => {(function(back) { - function writeSettings(key, value) { - var s = require('Storage').readJSON(FILE, true) || {}; - s[key] = value; - require('Storage').writeJSON(FILE, s); - readSettings(); - } + 'Custom Mode': function() { E.showMenu(submenu_custom); }, + 'Debug': function() { E.showMenu(submenu_debug); } + }; - function readSettings(){ - settings = Object.assign( - require('Storage').readJSON("bthrm.default.json", true) || {}, - require('Storage').readJSON(FILE, true) || {} - ); - } - - var FILE="bthrm.json"; - var settings; - readSettings(); - - var mainmenu = { - '': { 'title': 'Bluetooth HRM' }, - '< Back': back, - 'Use BT HRM': { - value: !!settings.enabled, - format: v => settings.enabled ? "On" : "Off", + var submenu_debug = { + '' : { title: "Debug"}, + '< Back': function() { E.showMenu(mainmenu); }, + 'Alert on disconnect': { + value: !!settings.warnDisconnect, + format: v => settings.warnDisconnect ? "On" : "Off", onchange: v => { - writeSettings("enabled",v); + writeSettings("warnDisconnect",v); } }, - 'Replace HRM': { - value: !!settings.replace, - format: v => settings.replace ? "On" : "Off", + 'Debug log': { + value: !!settings.debuglog, + format: v => settings.debuglog ? "On" : "Off", onchange: v => { - writeSettings("replace",v); + writeSettings("debuglog",v); + } + }, + 'Grace periods': function() { E.showMenu(submenu_grace); } + }; + + var submenu_custom = { + '' : { title: "Custom mode"}, + '< Back': function() { E.showMenu(mainmenu); }, + 'Replace HRM': { + value: !!settings.custom_replace, + format: v => settings.custom_replace ? "On" : "Off", + onchange: v => { + writeSettings("custom_replace",v); } }, 'Start w. HRM': { - value: !!settings.startWithHrm, - format: v => settings.startWithHrm ? "On" : "Off", + value: !!settings.custom_startWithHrm, + format: v => settings.custom_startWithHrm ? "On" : "Off", onchange: v => { - writeSettings("startWithHrm",v); + writeSettings("custom_startWithHrm",v); } }, 'HRM Fallback': { - value: !!settings.allowFallback, - format: v => settings.allowFallback ? "On" : "Off", + value: !!settings.custom_allowFallback, + format: v => settings.custom_allowFallback ? "On" : "Off", onchange: v => { - writeSettings("allowFallback",v); + writeSettings("custom_allowFallback",v); } }, 'Fallback Timeout': { - value: settings.fallbackTimeout, + value: settings.custom_fallbackTimeout, min: 5, max: 60, step: 5, format: v=>v+"s", onchange: v => { - writeSettings("fallbackTimout",v*1000); + writeSettings("custom_fallbackTimout",v*1000); } }, - 'Conn. Alert': { - value: !!settings.warnDisconnect, - format: v => settings.warnDisconnect ? "On" : "Off", + }; + + var submenu_grace = { + '' : { title: "Grace periods"}, + '< Back': function() { E.showMenu(submenu_debug); }, + 'Request': { + value: settings.gracePeriodRequest, + min: 0, + max: 3000, + step: 100, + format: v=>v+"ms", onchange: v => { - writeSettings("warnDisconnect",v); + writeSettings("gracePeriodRequest",v); } }, - 'Debug log': { - value: !!settings.debuglog, - format: v => settings.debuglog ? "On" : "Off", + 'Connect': { + value: settings.gracePeriodConnect, + min: 0, + max: 3000, + step: 100, + format: v=>v+"ms", onchange: v => { - writeSettings("debuglog",v); + writeSettings("gracePeriodConnect",v); } }, - 'Grace periods >': function() { E.showMenu(submenu); } - }; - - var submenu = { - '' : { title: "Grace periods"}, - '< Back': function() { E.showMenu(mainmenu); }, - 'Request': { - value: settings.gracePeriodRequest, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodRequest",v); - } - }, - 'Connect': { - value: settings.gracePeriodConnect, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodConnect",v); - } - }, - 'Notification': { - value: settings.gracePeriodNotification, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodNotification",v); - } - }, - 'Service': { - value: settings.gracePeriodService, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodService",v); - } - } - }; - - E.showMenu(mainmenu); -}) - writeSettings("startWithHrm",v); - } - }, - 'Fallback to HRM': { - value: !!settings.allowFallback, - format: v => settings.allowFallback ? "On" : "Off", - onchange: v => { - writeSettings("allowFallback",v); - } - }, - 'Fallback Timeout': { - value: settings.fallbackTimeout, - min: 5, - max: 60, - step: 5, - format: v=>v+"s", - onchange: v => { - writeSettings("fallbackTimout",v*1000); - } - }, - 'Conn. Alert': { - value: !!settings.warnDisconnect, - format: v => settings.warnDisconnect ? "On" : "Off", - onchange: v => { - writeSettings("warnDisconnect",v); - } - }, - 'Debug log': { - value: !!settings.debuglog, - format: v => settings.debuglog ? "On" : "Off", - onchange: v => { - writeSettings("debuglog",v); - } - }, - 'Grace periods': function() { E.showMenu(submenu); } + 'Notification': { + value: settings.gracePeriodNotification, + min: 0, + max: 3000, + step: 100, + format: v=>v+"ms", + onchange: v => { + writeSettings("gracePeriodNotification",v); + } + }, + 'Service': { + value: settings.gracePeriodService, + min: 0, + max: 3000, + step: 100, + format: v=>v+"ms", + onchange: v => { + writeSettings("gracePeriodService",v); + } + } }; var submenu = { diff --git a/apps/bthrv/ChangeLog b/apps/bthrv/ChangeLog index 0e51186a4..e144fd8f9 100644 --- a/apps/bthrv/ChangeLog +++ b/apps/bthrv/ChangeLog @@ -1,11 +1,2 @@ 0.01: New App! -0.02: Make overriding the HRM event optional - Emit BTHRM event for external sensor - Add recorder app plugin -0.03: Prevent readings from internal sensor mixing into BT values - Mark events with src property - Show actual source of event in app -0.04: Allow reading additional data if available: HRM battery and position - Better caching of scanned BT device properties - New setting for not starting the BTHRM together with HRM - Save some RAM by not definining functions if disabled in settings +0.02: Write available data on reset or kill diff --git a/apps/bthrv/app.js b/apps/bthrv/app.js index 7f6ec2d35..067c84f56 100644 --- a/apps/bthrv/app.js +++ b/apps/bthrv/app.js @@ -11,34 +11,30 @@ var currentSlot = 0; var hrvSlots = [10,20,30,60,120,300]; var hrvValues = {}; var rrRmsProgress; -var saved = false; var rrNumberOfValues = 0; var rrSquared = 0; -var rrLastValue +var rrLastValue; var rrMax; var rrMin; function calcHrv(rr){ //Calculate HRV with RMSSD method: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5624990/ - for (currentRr of rr){ + for (var currentRr of rr){ if (!rrMax) rrMax = currentRr; if (!rrMin) rrMin = currentRr; rrMax = Math.max(rrMax, currentRr); rrMin = Math.min(rrMin, currentRr); - //print("Calc for: " + currentRr); rrNumberOfValues++; if (!rrLastValue){ rrLastValue = currentRr; continue; } rrSquared += (rrLastValue - currentRr)*(rrLastValue - currentRr); - - //print("rr²: " + rrSquared); + rrLastValue = currentRr; } var rms = Math.sqrt(rrSquared / rrNumberOfValues); - //print("rms: " + rms); return rms; } @@ -56,17 +52,36 @@ function draw(y, hrv) { if (hrvValues[hrvSlots[i]]) str += hrvValues[hrvSlots[i]].toFixed(1) + "ms"; g.setFontVector(16).drawString(str,px,y+44+(i*17)); } - + g.setRotation(3); g.setFontVector(12).drawString("Reset",g.getHeight()/2, g.getWidth()-10); g.setRotation(0); } +function write(){ + if (!hrvValues[hrvSlots[0]]){ + return; + } + + var file = require('Storage').open("bthrv.csv", "a"); + var data = new Date(startingTime).toISOString(); + for (var i = 0; i < hrvSlots.length; i++ ){ + data += ","; + if (hrvValues[hrvSlots[i]]){ + data += hrvValues[hrvSlots[i]]; + } + } + + data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues; + data += "\n"; + file.write(data); + Bangle.buzz(500); +} + function onBtHrm(e) { if (e.rr && !startingTime) Bangle.buzz(500); if (e.rr && !startingTime) startingTime=Date.now(); - //print("Event:" + e.rr); - + var hrv = calcHrv(e.rr); if (hrv){ if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){ @@ -74,35 +89,25 @@ function onBtHrm(e) { currentSlot++; } } - if (!saved && currentSlot == hrvSlots.length){ - var file = require('Storage').open("bthrv.csv", "a"); - var data = new Date(startingTime).toISOString(); - for (var c of hrvSlots){ - data+=","+hrvValues[c]; - } - data+="," + rrMax + "," + rrMin + ","+rrNumberOfValues; - data+="\n"; - file.write(data); - saved = true; - Bangle.buzz(500); - } + if (hrv){ - if (!ui){ + if (!ui){ Bangle.setUI("leftright", ()=>{ resetHrv(); clear(30); }); ui = true; } + draw(30, hrv); } } function resetHrv(){ + write(); hrvValues={}; startingTime=undefined; currentSlot=0; - saved=false; rrNumberOfValues = 0; rrSquared = 0; rrLastValue = undefined; @@ -117,7 +122,6 @@ g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); - if (Bangle.setBTHRMPower){ Bangle.on('BTHRM', onBtHrm); Bangle.setBTHRMPower(1,'bthrv'); @@ -133,6 +137,11 @@ if (Bangle.setBTHRMPower){ file.write(data); } + E.on('kill', ()=>{ + write(); + Bangle.setBTHRMPower(0,'bthrv'); + }); + g.reset().setFont("6x8",2).setFontAlign(0,0); g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); } else { @@ -140,4 +149,3 @@ if (Bangle.setBTHRMPower){ g.drawString("Missing BT HRM",g.getWidth()/2,g.getHeight()/2 - 16); } -E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrv')); diff --git a/apps/bthrv/metadata.json b/apps/bthrv/metadata.json index 6a8e7e940..183008034 100644 --- a/apps/bthrv/metadata.json +++ b/apps/bthrv/metadata.json @@ -2,7 +2,7 @@ "id": "bthrv", "name": "Bluetooth Heart Rate variance calculator", "shortName": "BT HRV", - "version": "0.01", + "version": "0.02", "description": "Calculates HRV from a a BT HRM with interval data", "icon": "app.png", "type": "app", diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index 58ab4cd48..7165f8521 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -19,3 +19,4 @@ Colors of circles can be configured Color depending on value (green -> red, red -> green) option Good HRM value will not be overwritten so fast anymore +0.10: Use roboto font for time, date and day of week and center align them diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 49af2a057..903c7bdb2 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -20,25 +20,26 @@ const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); -let settings = storage.readJSON("circlesclock.json", 1) || { - 'minHR': 40, - 'maxHR': 200, - 'confidence': 0, - 'stepGoal': 10000, - 'stepDistanceGoal': 8000, - 'stepLength': 0.8, - 'batteryWarn': 30, - 'showWidgets': false, - 'weatherCircleData': 'humidity', - 'circleCount': 3, - 'circle1': 'hr', - 'circle2': 'steps', - 'circle3': 'battery', - 'circle4': 'weather' +Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) { + // Actual height 39 (40 - 2) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16)); + return this; }; + +Graphics.prototype.setFontRobotoRegular21 = function(scale) { + // Actual height 22 (21 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAB/+YH/5gAAAAAAAAAAA+AAD4AAAAAA+AAD4AAAAAAAAAAAMAAYwABj+AH/4D/wAfjABGM4AZ/gD/4B/sAHYwABiAAEAAAAAAAAOAPw8A/h4HGBh4cHvgweGDhgcHOA8fwBw/AAAAAAAAPgAB/AAGMAAYwMBjDgD48AHHAABwAAOfADjuAcMYAAxgAD+AAHwAAAAAAAAAHgBx/AP3eB/wYGHBgY+GBjeYH4/gPA8AAHwAB/gAHGAAAAAAAA+AAD4AAAAAAAAAAA/gAf/wH//w8AHnAAHwAAEAAAMAAB4AAN4ABz8B+D//gD/4AAAAAAAAMAAAxAADMAAHwAH8AAf4AAPwAAzAADAAAAAAABgAAGAAAYAABgAD/8AP/wABgAAGAAAYAABgAAGAAAADAAB8AAPgAAYAAwAADAAAMAAAwAADAAAMAAAAAAAEAAA4AABgAAAAAAAAAAQAAPAAH4AB8AA/AAPgAH4AAcAAAAAAAAAAD/wA//wH4fgYAOBgAYGABgYAGB4B4D//AH/4AAAAAAAAAAAAYAADAAAMAABwAAH//gf/+AAAAAAAAAAAAAAAAAAAHAGA8A4HAHgYA+BgHYGB5gYPGB7wYD+BgHgGAAAYAAAAHA4A8DwHgDgYMGBgwYGDBgYcGB/44D9/AHj4AAAAAAMAABwAAfAAHsAA8wAPDADwMAf/+B//4H//gAAwAADAAAAAAAAAfjgH+HAf4OBjAYGMBgYwGBjg4GH/AYP4AAOAAAAAAfgAP/gB//AO4OBzAYGMBgYwGBjg4AH/AAP4AAEAAAAAYAABgAAGABgYAeBgHwGD8AY/ABvgAH4AAeAABAAAAAAADB4A/fwH/3gccGBgwYGDBgYcGB744D9/AHj4AAAAAAAAD8AA/4AHjxgYDGBgMYGAzgcDMA87wD/+AD/gAAAAAAAABgGAHA4AYBgAAAAAAAAYB8BwPgGAYAAAAAAAAAYAADwAAPAAB+AAGYAA5wADDAAcOABgYAAAAAAAAAZgABmAAGYAAZgABmAAGYAAZgABmAAGYAAZgAAAAAAAABgYAHDgAMMAA5wABmAAGYAAPAAA8AADwAAGAAAAAAAAAOAAA4AAHAAAYHmBg+YGHgAf4AA/AAAwAAAAAAAfwAH/4B4DwOADhwAGGD8Mw/4zHBjMYGMxgYzGPDMf+Mw8YhgBgHAGAOA4Af/AAPwAAAAAAAgAAeAAP4AD+AB/gA/mAHwYAeBgB/GAA/4AAfwAAfwAAPgAAGAAAAAAAAf/+B//4GDBgYMGBgwYGDBgYcGBz44D//AHz8AAHAAAAAAAAAH+AB/+AP/8A4A4HABgYAGBgAYGABgYAGB4A4DwPAHA4AAAAAAAAAAAB//4H//gYAGBgAYGABgYAGBgAYDADAPA8Af/gAf4AAAAAAAAAAAAf/+B//4GDBgYMGBgwYGDBgYMGBgwYGDBgYAGAAAAAAAAAAAB//4H//gYMABgwAGDAAYMABgwAGDAAYMABgAAAAAAB/AAf/AD//AeAcBgA4GABgYEGBgYYGBhgcGGA8fwBx/AAH4AAAAAAAAAAAB//4H//gAMAAAwAADAAAMAAAwAADAAAMAAAwAH//gf/+AAAAAAAAAAAAAAAH//gf/+AAAAAAAAAAQAADwAAPgAAOAAAYAABgAAGAAA4H//Af/8B//AAAAAAAAAAAAH//gf/+AA4AAHAAA+AAP8AB48APB4B4DwGADgQAGAAAIAAAAAAAB//4H//gAAGAAAYAABgAAGAAAYAABgAAGAAAIAAAAAAAB//4H//gfgAAfgAAPwAAPwAAH4AAHgAB+AAfgAPwAH8AB+AAH//gf/+B//4AAAAAAAAAAAH//gf/+A8AAB8AAB4AAD4AADwAAHwAAHgAAPgf/+B//4AAAAAAAAAAAAf4AH/4A//wDgHAcAOBgAYGABgYAGBwAYDADgPz8Af/gAf4AAAAAAAAAAAAf/+B//4GBgAYGABgYAGBgAYGABg4AHnAAP8AAfgAAAAAAAAAH+AB/+APz8AwAwHADgYAGBgAYGABgcAOA4B8D//4H/5gH+CAAAAAAAAAAAH//gf/+BgYAGBgAYGABgYAGD4AcP4A/z4D+HgDgGAAAAAAAADg4A/DwD+DAcYGBhwYGDBgYMGBg4YHBzgPH8AcPgAAAAYAABgAAGAAAYAABgAAH//gf/+B//4GAAAYAABgAAGAAAYAAAAAAH/4Af/4B//wAADgAAGAAAYAABgAAGAAA4AAfAf/8B//AAAAAQAAB4AAH8AAH+AAD+AAB/AAA+AAD4AB/AA/gAf4AH8AAeAABAAAAAAAfAAB/wAB/8AAP+AAD4AB/gA/wA/wAH4AAfgAAf4AAH+AAD+AAH4AH/gP/gB/gAHAAAAAAAAAAGABgcAOB8D4B4+AD/gAD8AAPwAD/wAeHgHwPgcAOBAAYAAAAQAABwAAHwAAPwAAPwAAP/gAP+AB/4AfAAHwAB8AAHAAAQAAAAAAGADgYAeBgH4GA9gYPmBh4YGfBgbwGB+AYHgBgcAGAAAIAAAA///7///v//+wAAbAABkAAAcAAB+AAB/AAA/AAAfgAAfgAAPAAAMMAAGwAAb///v//+AAAAAAAAAAAA4AAPgAH4AAeAAB+AAB+AAA4AAAgAAAAYAABgAAGAAAYAABgAAGAAAYAABgAAGAAAYAAABAAAGAAAcAAAwAAAAAAAAAAAAAAGOAA58AHu4AYxgBjGAGMYAYzgB/+AD/4AD/gAAAAAAAP//g//+D//4AcDgBgGAGAYAYBgBwOAD/wAH+AADAAAAAAD8AA/8AHh4AYBgBgGAGAYAYBgB4OADhwAGGAAAAAAAAAD8AA/8AHh4AYBgBgGAGAYAYBgAwMD//4P//gAAAAAAAAD8AA/8ADtwAYxgBjGAGMYAYxgB7OAD8wADyAAAAAEAAAYAAP/+B//4P//gxgADGAAMAAAAAAAA/AAP/MB4e4GAZgYBmBgGYGAZgMDeB//wH/+AAAAAAAD//4P//g//+AHAAAYAABgAAGAAAf/gA/+AB/4AAAAAAABn/4Gf/gZ/+AAAAAAAGZ//5n//mf/4AAAAAAAP//g//+D//4ABwAAPgAB/AAePABweAGA4AAAgAAAD//4P//g//+AAAAAAAAB/+AH/4Af/gBgAAGAAAYAABgAAH/4AP/gA/+AHAAAYAABgAAGAAAeAAA/+AB/4AAAAAAAAH/4Af/gB/+AHAAAYAABgAAGAAAf/gA/+AB/4AAAAAAAAA/AAP/AA4cAGAYAYBgBgGAGAYAcDgA/8AB/gAB4AAAAAAAAAf/+B//4H//gYBgBgGAGAYAYBgBwOAD/wAH+AADAAAAAAH8AA/8AHh4AYBgBgGAGAYAYBgAwOAH//gf/+AAAAAAAAAAAB/+AH/4Af/gBwAAGAAAYAAAAAABxgAPnAB+OAGcYAYxgBjGAHOYAefgA58AAAAAYAABgAA//wD//gBgOAGAYAQBgAAAAH/gAf/AB/+AAAYAABgAAGAAAYAf/gB/+AH/4AAAABAAAHgAAfwAAP4AAH4AAHgAD8AB/AAfgABwAAAAAAQAAB8A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"), 32, atob("BQYHDgwQDgQICAkMBAYGCQwMDAwMDAwMDAwFBQsMDAoUDg4ODg0MDxAGDA4MExAPDg8ODQ0ODhQODQ0GCQYJCgcMDAwMDAgMDAUFCwUTDA0MDQcLBwwLEQsKCwcFBw8ACw0ZEBgUGRoQGBQZGhIQDhMAAAAAEhEYExAUEBQQEA0FBQwNEAwFDgkRCgoMABEKCAwICAcMCwYFCAoKEBERCg4ODg4ODhUODQ0NDQYGBgYPEA8PDw8PDA8ODg4ODQ0NDAwMDAwMEwwMDAwMBQUFBQ0MDQ0NDQ0NDAwMDAwKDQo="), 22+(scale<<8)+(1<<16)); + return this; +}; + +const SETTINGS_FILE = "circlesclock.json"; +let settings = Object.assign( + storage.readJSON("circlesclock.default.json", true) || {}, + storage.readJSON(SETTINGS_FILE, true) || {} +); // Load step goal from pedometer widget as fallback if (settings.stepGoal == undefined) { - const d = storage.readJSON("wpedom.json", 1) || {}; + const d = storage.readJSON("wpedom.json", true) || {}; settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; } @@ -69,7 +70,7 @@ const widgetOffset = showWidgets ? 24 : 0; const dowOffset = circleCount == 3 ? 22 : 24; // dow offset relative to date const h = g.getHeight() - widgetOffset; const w = g.getWidth(); -const hOffset = 30 - widgetOffset; +const hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset; const h1 = Math.round(1 * h / 5 - hOffset); const h2 = Math.round(3 * h / 5 - hOffset); const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position @@ -125,17 +126,17 @@ function draw() { g.fillRect(0, widgetOffset, w, h2 + 22); // time - g.setFont("Vector:50"); + g.setFontRobotoRegular50NumericOnly(); g.setFontAlign(0, -1); g.setColor(colorFg); g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8); now = Math.round(new Date().getTime() / 1000); // date & dow - g.setFont("Vector:21"); - g.setFontAlign(-1, 0); - g.drawString(locale.date(new Date()), w > 180 ? 2 * w / 10 : w / 10, h2); - g.drawString(locale.dow(new Date()), w > 180 ? 2 * w / 10 : w / 10, h2 + dowOffset); + g.setFontRobotoRegular21(); + g.setFontAlign(0, 0); + g.drawString(locale.date(new Date()), w / 2, h2); + g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset); drawCircle(1); drawCircle(2); @@ -279,10 +280,10 @@ function drawSteps(w) { drawCircleBackground(w); - const color = getCircleColor("steps") || colorBlue; + const color = getCircleColor("steps"); let percent; - const stepGoal = settings.stepGoal || 10000; + const stepGoal = settings.stepGoal; if (stepGoal > 0) { percent = steps / stepGoal; if (stepGoal < steps) percent = 1; @@ -299,15 +300,15 @@ function drawSteps(w) { function drawStepsDistance(w) { if (!w) w = getCircleXPosition("stepsDistance"); const steps = getSteps(); - const stepDistance = settings.stepLength || 0.8; + const stepDistance = settings.stepLength; const stepsDistance = Math.round(steps * stepDistance); drawCircleBackground(w); - const color = getCircleColor("stepsDistance") || colorGreen; + const color = getCircleColor("stepsDistance"); let percent; - const stepDistanceGoal = settings.stepDistanceGoal || 8000; + const stepDistanceGoal = settings.stepDistanceGoal; if (stepDistanceGoal > 0) { percent = stepsDistance / stepDistanceGoal; if (stepDistanceGoal < stepsDistance) percent = 1; @@ -326,12 +327,12 @@ function drawHeartRate(w) { drawCircleBackground(w); - const color = getCircleColor("hr") || colorRed; + const color = getCircleColor("hr"); let percent; if (hrtValue != undefined) { - const minHR = settings.minHR || 40; - const maxHR = settings.maxHR || 200; + const minHR = settings.minHR; + const maxHR = settings.maxHR; percent = (hrtValue - minHR) / (maxHR - minHR); if (isNaN(percent)) percent = 0; drawGauge(w, h3, percent, color); @@ -350,7 +351,7 @@ function drawBattery(w) { drawCircleBackground(w); - let color = getCircleColor("battery") || colorYellow; + let color = getCircleColor("battery"); let percent; if (battery > 0) { @@ -380,9 +381,9 @@ function drawWeather(w) { drawCircleBackground(w); - const color = getCircleColor("weather") || colorYellow; + const color = getCircleColor("weather"); let percent; - const data = settings.weatherCircleData || "humidity"; + const data = settings.weatherCircleData; switch (data) { case "humidity": const humidity = weather ? weather.hum : undefined; @@ -427,7 +428,7 @@ function drawSunProgress(w) { drawCircleBackground(w); - const color = getCircleColor("sunprogress") || colorYellow; + const color = getCircleColor("sunprogress"); drawGauge(w, h3, percent, color); @@ -467,7 +468,7 @@ function drawTemperature(w) { getPressureValue("temperature").then((temperature) => { drawCircleBackground(w); - const color = getCircleColor("temperature") || colorGreen; + const color = getCircleColor("temperature"); let percent; if (temperature) { @@ -493,7 +494,7 @@ function drawPressure(w) { getPressureValue("pressure").then((pressure) => { drawCircleBackground(w); - const color = getCircleColor("pressure") || colorGreen; + const color = getCircleColor("pressure"); let percent; if (pressure && pressure > 0) { @@ -519,7 +520,7 @@ function drawAltitude(w) { getPressureValue("altitude").then((altitude) => { drawCircleBackground(w); - const color = getCircleColor("altitude") || colorGreen; + const color = getCircleColor("altitude"); let percent; if (altitude) { @@ -578,23 +579,23 @@ function getWeatherIconByCode(code) { default: return weatherRainy; } - case 6: - return weatherSnowy; - case 7: - return weatherFoggy; - case 8: - switch (code) { - case 800: - return isDay() ? weatherSunny : weatherMoon; - case 801: - return weatherPartlyCloudy; - case 802: - return weatherPartlyCloudy; + case 6: + return weatherSnowy; + case 7: + return weatherFoggy; + case 8: + switch (code) { + case 800: + return isDay() ? weatherSunny : weatherMoon; + case 801: + return weatherPartlyCloudy; + case 802: + return weatherPartlyCloudy; + default: + return weatherCloudy; + } default: - return weatherCloudy; - } - default: - return undefined; + return undefined; } } @@ -797,7 +798,7 @@ Bangle.on('lock', function(isLocked) { let timerHrm; Bangle.on('HRM', function(hrm) { if (isCircleEnabled("hr")) { - if (hrm.confidence >= (settings.confidence || 0)) { + if (hrm.confidence >= (settings.confidence)) { hrtValue = hrm.bpm; if (Bangle.isLCDOn()) { drawHeartRate(); @@ -809,7 +810,7 @@ Bangle.on('HRM', function(hrm) { timerHrm = setTimeout(() => { hrtValue = '...'; drawHeartRate(); - }, settings.hrmValidity * 1000 || 30000); + }, settings.hrmValidity * 1000); } } }); @@ -826,5 +827,10 @@ if (isCircleEnabled("hr")) { Bangle.setUI("clock"); Bangle.loadWidgets(); +// schedule a draw for the next minute +setTimeout(function() { + // draw every 60 seconds + setInterval(draw,60000); +}, 60000 - (Date.now() % 60000)); + draw(); -setInterval(draw, 60000); diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json new file mode 100644 index 000000000..cb6bfcff8 --- /dev/null +++ b/apps/circlesclock/default.json @@ -0,0 +1,25 @@ +{ + "minHR": 40, + "maxHR": 200, + "confidence": 0, + "stepGoal": 10000, + "stepDistanceGoal": 8000, + "stepLength": 0.8, + "batteryWarn": 30, + "showWidgets": false, + "weatherCircleData": "humidity", + "circleCount": 3, + "circle1": "hr", + "circle2": "steps", + "circle3": "battery", + "circle4": "weather", + "circle1color": "green-red", + "circle2color": "#0000ff", + "circle3color": "red-green", + "circle4color": "#ffff00", + "circle1colorizeIcon": true, + "circle2colorizeIcon": true, + "circle3colorizeIcon": true, + "circle4colorizeIcon": false, + "hrmValidity": 60 +} diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index f426a1681..3279ec2cf 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -1,7 +1,7 @@ { "id": "circlesclock", "name": "Circles clock", "shortName":"Circles clock", - "version":"0.09", + "version":"0.10", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "icon": "app.png", "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], @@ -13,7 +13,8 @@ "storage": [ {"name":"circlesclock.app.js","url":"app.js"}, {"name":"circlesclock.img","url":"app-icon.js","evaluate":true}, - {"name":"circlesclock.settings.js","url":"settings.js"} + {"name":"circlesclock.settings.js","url":"settings.js"}, + {"name":"circlesclock.default.json","url":"default.json"} ], "data": [ {"name":"circlesclock.json"} diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index 348d187eb..bec539376 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -1,7 +1,11 @@ (function(back) { const SETTINGS_FILE = "circlesclock.json"; const storage = require('Storage'); - let settings = storage.readJSON(SETTINGS_FILE, 1) || {}; + let settings = Object.assign( + storage.readJSON("circlesclock.default.json", true) || {}, + storage.readJSON(SETTINGS_FILE, true) || {} + ); + function save(key, value) { settings[key] = value; storage.write(SETTINGS_FILE, settings); @@ -10,8 +14,8 @@ const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"]; const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"]; - const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"]; - const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"]; + const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"]; + const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"]; const weatherData = ["empty", "humidity", "wind"]; @@ -20,7 +24,7 @@ '': { 'title': 'Circles clock' }, /*LANG*/'< Back': back, /*LANG*/'circle count': { - value: "circleCount" in settings ? settings.circleCount : 3, + value: settings.circleCount, min: 3, max : 4, step: 1, @@ -33,7 +37,7 @@ /*LANG*/'heartrate': ()=>showHRMenu(), /*LANG*/'steps': ()=>showStepMenu(), /*LANG*/'battery warn': { - value: "batteryWarn" in settings ? settings.batteryWarn : 30, + value: settings.batteryWarn, min: 10, max : 100, step: 10, @@ -43,12 +47,12 @@ onchange: x => save('batteryWarn', x), }, /*LANG*/'show widgets': { - value: "showWidgets" in settings ? settings.showWidgets : false, + value: !!settings.showWidgets, format: () => (settings.showWidgets ? 'Yes' : 'No'), onchange: x => save('showWidgets', x), }, - /*LANG*/'weather circle': { - value: settings.weatherCircleData ? weatherData.indexOf(settings.weatherCircleData) : 1, + /*LANG*/'weather data': { + value: weatherData.indexOf(settings.weatherCircleData), min: 0, max: 2, format: v => weatherData[v], onchange: x => save('weatherCircleData', weatherData[x]), @@ -62,7 +66,7 @@ '': { 'title': /*LANG*/'Heartrate' }, /*LANG*/'< Back': ()=>showMainMenu(), /*LANG*/'minimum': { - value: "minHR" in settings ? settings.minHR : 40, + value: settings.minHR, min: 0, max : 250, step: 5, @@ -72,7 +76,7 @@ onchange: x => save('minHR', x), }, /*LANG*/'maximum': { - value: "maxHR" in settings ? settings.maxHR : 200, + value: settings.maxHR, min: 20, max : 250, step: 5, @@ -82,7 +86,7 @@ onchange: x => save('maxHR', x), }, /*LANG*/'min. confidence': { - value: "confidence" in settings ? settings.confidence : 0, + value: settings.confidence, min: 0, max : 100, step: 10, @@ -92,7 +96,7 @@ onchange: x => save('confidence', x), }, /*LANG*/'valid period': { - value: "hrmValidity" in settings ? settings.hrmValidity : 30, + value: settings.hrmValidity, min: 10, max : 600, step: 10, @@ -110,7 +114,7 @@ '': { 'title': /*LANG*/'Steps' }, /*LANG*/'< Back': ()=>showMainMenu(), /*LANG*/'goal': { - value: "stepGoal" in settings ? settings.stepGoal : 10000, + value: settings.stepGoal, min: 2000, max : 50000, step: 2000, @@ -120,7 +124,7 @@ onchange: x => save('stepGoal', x), }, /*LANG*/'distance goal': { - value: "stepDistanceGoal" in settings ? settings.stepDistanceGoal : 8000, + value: settings.stepDistanceGoal, min: 2000, max : 30000, step: 1000, @@ -130,7 +134,7 @@ onchange: x => save('stepDistanceGoal', x), }, /*LANG*/'step length': { - value: "stepLength" in settings ? settings.stepLength : 0.8, + value: settings.stepLength, min: 0.1, max : 1.5, step: 0.01, @@ -142,9 +146,6 @@ }; E.showMenu(menu); } - - const defaultCircleTypes = ["steps", "hr", "battery", "weather"]; - function showCircleMenu(circleId) { const circleName = "circle" + circleId; const colorKey = circleName + "color"; @@ -154,19 +155,19 @@ '': { 'title': /*LANG*/'Circle ' + circleId }, /*LANG*/'< Back': ()=>showMainMenu(), /*LANG*/'data': { - value: settings[circleName]!=undefined ? valuesCircleTypes.indexOf(settings[circleName]) : valuesCircleTypes.indexOf(defaultCircleTypes[circleId -1]), + value: valuesCircleTypes.indexOf(settings[circleName]), min: 0, max: valuesCircleTypes.length - 1, format: v => namesCircleTypes[v], onchange: x => save(circleName, valuesCircleTypes[x]), }, /*LANG*/'color': { - value: settings[colorKey] ? valuesColors.indexOf(settings[colorKey]) : 0, + value: valuesColors.indexOf(settings[colorKey]) || 0, min: 0, max: valuesColors.length - 1, format: v => namesColors[v], onchange: x => save(colorKey, valuesColors[x]), }, /*LANG*/'colorize icon': { - value: colorizeIconKey in settings ? settings[colorizeIconKey] : false, + value: settings[colorizeIconKey] || false, format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'), onchange: x => save(colorizeIconKey, x), }, diff --git a/apps/contourclock/ChangeLog b/apps/contourclock/ChangeLog index b8d01ff86..0b6709d24 100644 --- a/apps/contourclock/ChangeLog +++ b/apps/contourclock/ChangeLog @@ -3,3 +3,4 @@ 0.21: Fixed settings menu, four more fonts 0.22: Changed timing code, original "Nunito" Font is back! 0.23: Customizer! Unused fonts no longer take up precious memory. +0.24: Added previews to the customizer. diff --git a/apps/contourclock/custom.html b/apps/contourclock/custom.html index 7d780d39c..602182573 100644 --- a/apps/contourclock/custom.html +++ b/apps/contourclock/custom.html @@ -1,70 +1,74 @@ + +

   Select Fonts to upload:

-
+
-
+
-
+
-
- -
+
-
+
-
+
-
+
-
+
-
- -
+
-
+
-
+
-
+
-
-

Click

+
+

+ if (n>0) + sendCustomizedApp({storage:fonts}); + else + alert("Please select at least one Font!"); + }); + +
diff --git a/apps/contourclock/fonts/BarlowCond-p1.png b/apps/contourclock/fonts/BarlowCond-p1.png new file mode 100644 index 000000000..fc1e66797 Binary files /dev/null and b/apps/contourclock/fonts/BarlowCond-p1.png differ diff --git a/apps/contourclock/fonts/BarlowCond-p2.png b/apps/contourclock/fonts/BarlowCond-p2.png new file mode 100644 index 000000000..09f0cd5b6 Binary files /dev/null and b/apps/contourclock/fonts/BarlowCond-p2.png differ diff --git a/apps/contourclock/fonts/BebasNeue-p1.png b/apps/contourclock/fonts/BebasNeue-p1.png new file mode 100644 index 000000000..7bcec6894 Binary files /dev/null and b/apps/contourclock/fonts/BebasNeue-p1.png differ diff --git a/apps/contourclock/fonts/BebasNeue-p2.png b/apps/contourclock/fonts/BebasNeue-p2.png new file mode 100644 index 000000000..9ef333b06 Binary files /dev/null and b/apps/contourclock/fonts/BebasNeue-p2.png differ diff --git a/apps/contourclock/fonts/Dekko-p1.png b/apps/contourclock/fonts/Dekko-p1.png new file mode 100644 index 000000000..de2da2646 Binary files /dev/null and b/apps/contourclock/fonts/Dekko-p1.png differ diff --git a/apps/contourclock/fonts/Dekko-p2.png b/apps/contourclock/fonts/Dekko-p2.png new file mode 100644 index 000000000..d82129543 Binary files /dev/null and b/apps/contourclock/fonts/Dekko-p2.png differ diff --git a/apps/contourclock/fonts/DinAlternate-p1.png b/apps/contourclock/fonts/DinAlternate-p1.png new file mode 100644 index 000000000..5957d2c5a Binary files /dev/null and b/apps/contourclock/fonts/DinAlternate-p1.png differ diff --git a/apps/contourclock/fonts/DinAlternate-p2.png b/apps/contourclock/fonts/DinAlternate-p2.png new file mode 100644 index 000000000..fdc9b38a3 Binary files /dev/null and b/apps/contourclock/fonts/DinAlternate-p2.png differ diff --git a/apps/contourclock/fonts/Impact-p1.png b/apps/contourclock/fonts/Impact-p1.png new file mode 100644 index 000000000..5ce675ba4 Binary files /dev/null and b/apps/contourclock/fonts/Impact-p1.png differ diff --git a/apps/contourclock/fonts/Impact-p2.png b/apps/contourclock/fonts/Impact-p2.png new file mode 100644 index 000000000..0474caea8 Binary files /dev/null and b/apps/contourclock/fonts/Impact-p2.png differ diff --git a/apps/contourclock/fonts/Nunito-p1.png b/apps/contourclock/fonts/Nunito-p1.png new file mode 100644 index 000000000..44697c671 Binary files /dev/null and b/apps/contourclock/fonts/Nunito-p1.png differ diff --git a/apps/contourclock/fonts/Nunito-p2.png b/apps/contourclock/fonts/Nunito-p2.png new file mode 100644 index 000000000..4312d15ec Binary files /dev/null and b/apps/contourclock/fonts/Nunito-p2.png differ diff --git a/apps/contourclock/fonts/OpenSansEC-p1.png b/apps/contourclock/fonts/OpenSansEC-p1.png new file mode 100644 index 000000000..c5fe85494 Binary files /dev/null and b/apps/contourclock/fonts/OpenSansEC-p1.png differ diff --git a/apps/contourclock/fonts/OpenSansEC-p2.png b/apps/contourclock/fonts/OpenSansEC-p2.png new file mode 100644 index 000000000..f1d6381f2 Binary files /dev/null and b/apps/contourclock/fonts/OpenSansEC-p2.png differ diff --git a/apps/contourclock/fonts/Phosphate-p1.png b/apps/contourclock/fonts/Phosphate-p1.png new file mode 100644 index 000000000..2d0786c0a Binary files /dev/null and b/apps/contourclock/fonts/Phosphate-p1.png differ diff --git a/apps/contourclock/fonts/Phosphate-p2.png b/apps/contourclock/fonts/Phosphate-p2.png new file mode 100644 index 000000000..af11fea32 Binary files /dev/null and b/apps/contourclock/fonts/Phosphate-p2.png differ diff --git a/apps/contourclock/fonts/Quicksand-p1.png b/apps/contourclock/fonts/Quicksand-p1.png new file mode 100644 index 000000000..692fb9253 Binary files /dev/null and b/apps/contourclock/fonts/Quicksand-p1.png differ diff --git a/apps/contourclock/fonts/Quicksand-p2.png b/apps/contourclock/fonts/Quicksand-p2.png new file mode 100644 index 000000000..00ac7aef4 Binary files /dev/null and b/apps/contourclock/fonts/Quicksand-p2.png differ diff --git a/apps/contourclock/fonts/SairaEC-p1.png b/apps/contourclock/fonts/SairaEC-p1.png new file mode 100644 index 000000000..82b43af02 Binary files /dev/null and b/apps/contourclock/fonts/SairaEC-p1.png differ diff --git a/apps/contourclock/fonts/SairaEC-p2.png b/apps/contourclock/fonts/SairaEC-p2.png new file mode 100644 index 000000000..d32fd51b4 Binary files /dev/null and b/apps/contourclock/fonts/SairaEC-p2.png differ diff --git a/apps/contourclock/fonts/Teko-p1.png b/apps/contourclock/fonts/Teko-p1.png new file mode 100644 index 000000000..6c33087f9 Binary files /dev/null and b/apps/contourclock/fonts/Teko-p1.png differ diff --git a/apps/contourclock/fonts/Teko-p2.png b/apps/contourclock/fonts/Teko-p2.png new file mode 100644 index 000000000..8dcd86d43 Binary files /dev/null and b/apps/contourclock/fonts/Teko-p2.png differ diff --git a/apps/contourclock/fonts/Yumaro-p1.png b/apps/contourclock/fonts/Yumaro-p1.png new file mode 100644 index 000000000..5e99dd668 Binary files /dev/null and b/apps/contourclock/fonts/Yumaro-p1.png differ diff --git a/apps/contourclock/fonts/Yumaro-p2.png b/apps/contourclock/fonts/Yumaro-p2.png new file mode 100644 index 000000000..ac72818f5 Binary files /dev/null and b/apps/contourclock/fonts/Yumaro-p2.png differ diff --git a/apps/contourclock/fonts/YuseiMagic-p1.png b/apps/contourclock/fonts/YuseiMagic-p1.png new file mode 100644 index 000000000..27485a658 Binary files /dev/null and b/apps/contourclock/fonts/YuseiMagic-p1.png differ diff --git a/apps/contourclock/fonts/YuseiMagic-p2.png b/apps/contourclock/fonts/YuseiMagic-p2.png new file mode 100644 index 000000000..5b01ea036 Binary files /dev/null and b/apps/contourclock/fonts/YuseiMagic-p2.png differ diff --git a/apps/contourclock/fonts/temp b/apps/contourclock/fonts/temp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/contourclock/fonts/temp @@ -0,0 +1 @@ + diff --git a/apps/contourclock/metadata.json b/apps/contourclock/metadata.json index 9b9238703..a5d764f2d 100644 --- a/apps/contourclock/metadata.json +++ b/apps/contourclock/metadata.json @@ -1,13 +1,12 @@ { "id": "contourclock", "name": "Contour Clock", "shortName" : "Contour Clock", - "version":"0.23", + "version":"0.24", "icon": "app.png", "description": "A Minimalist clockface with large Digits. Now with more fonts!", "screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}], "tags": "clock", "custom": "custom.html", - "allow_emulator":true, "supports" : ["BANGLEJS2"], "type": "clock", "storage": [ diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 62a0cab9f..556472eaa 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -5,3 +5,4 @@ 0.05: add Bangle 2 version 0.06: Adds settings page (hide clocks or launchers) 0.07: Adds setting for directly launching app on touch for Bangle 2 +0.08: Optimize line wrapping for Bangle 2 diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 800ec456c..96e562add 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -45,11 +45,23 @@ function draw_icon(p,n,selected) { g.setColor(g.theme.fg); try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){} g.setFontAlign(0,-1,0).setFont("6x8",1); - var txt = apps[p*4+n].name.split(" "); - for (var i = 0; i < txt.length; i++) { - txt[i] = txt[i].trim(); - g.drawString(txt[i],x+36,y+54+i*8); + var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); + var lineY = 0; + var line = ""; + while (txt.length > 0){ + var c = txt.shift(); + + if (c.length + 1 + line.length > 13){ + if (line.length > 0){ + g.drawString(line.trim(),x+36,y+54+lineY*8); + lineY++; + } + line = c; + } else { + line += " " + c; + } } + g.drawString(line.trim(),x+36,y+54+lineY*8); } function drawPage(p){ diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index 8ff5bd592..6cd1dbe73 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.07", + "version": "0.08", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index 316b98a84..8b1a3e4aa 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -6,3 +6,4 @@ 0.06: Bangle.js 2 support 0.07: Fix "previous" button image 0.08: Fix scrolling title background color +0.09: Move event listener from widget to boot code, stops music from showing up in messages diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 1bddf70f7..c8395f745 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -175,10 +175,8 @@ function rIcon(l) { } let layout; function makeUI() { - global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices) Bangle.loadWidgets(); Bangle.drawWidgets(); - delete (global.gbmusic_active); const Layout = require("Layout"); layout = new Layout({ type: "v", c: [ @@ -331,7 +329,7 @@ function formatNum(info) { * Update music info * @param {Object} info - Gadgetbridge musicinfo event */ -function musicInfo(info) { +function info(info) { scrollStop(); layout.title.label = info.track || ""; layout.album.label = info.album || ""; @@ -360,7 +358,7 @@ let tPxt, tIxt; // Timeouts to eXiT when Paused/Inactive for too long * Update music state * @param {Object} e - Gadgetbridge musicstate event */ -function musicState(e) { +function state(e) { stat = e.state; // if paused for five minutes, load the clock // (but timeout resets if we get new info, even while paused) @@ -584,8 +582,8 @@ function startEmulator() { println: (line) => {console.log("Bluetooth:", line);}, }; // some example info - GB({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2}); - GB({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1}); + info({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2}); + state({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1}); } } function startWatches() { @@ -596,25 +594,6 @@ function startWatches() { function start() { makeUI(); - // start listening for music updates - const _GB = global.GB; - global.GB = (event) => { - // we eat music events! - switch(event.t) { - case "musicinfo": - musicInfo(event); - break; - case "musicstate": - musicState(event); - break; - default: - // pass on other events - if (_GB) { - setTimeout(_GB, 0, event); - } - return; - } - }; startWatches(); tick(); startEmulator(); @@ -625,11 +604,11 @@ function init() { let saved = require("Storage").readJSON("gbmusic.load.json", true); require("Storage").erase("gbmusic.load.json"); if (saved) { - // autoloaded: load state was saved by widget + // autoloaded: load state as saved by widget auto = true; start(); - musicInfo(saved.info); - musicState(saved.state); + info(saved.info); + state(saved.state); return; } diff --git a/apps/gbmusic/boot.js b/apps/gbmusic/boot.js new file mode 100644 index 000000000..154f85c2b --- /dev/null +++ b/apps/gbmusic/boot.js @@ -0,0 +1,37 @@ +setTimeout( // make other boot code run first, so we override e.g. android.boot.js GB + () => { + const APP = global.__FILE__==="gbmusic.app.js", + a = !!(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart; + + let s, i; // state, info + /** + * Save current song and check if we want to load the gbmusic app + * + * Only runs while other apps are loaded + */ + function check() { + if (s!=="play" || !i || !a || !Bangle.CLOCK) return; // only launch app if we know which song we are playing, and autoLoad is enabled + delete (i.t); + // store info and launch music app + require("Storage").writeJSON("gbmusic.load.json", { + state: s, + info: i, + }); + load("gbmusic.app.js"); + } + + global.GB = (_GB => e => { + // we eat music events! + switch(e.t) { + case "musicinfo": + i = e; + return APP ? info(e) : check(); + case "musicstate": + s = e.state; + return APP ? state(e) : check(); + default: + // pass on other events + if (_GB) setTimeout(_GB, 0, e); + } + })(global.GB); + }, 1); diff --git a/apps/gbmusic/metadata.json b/apps/gbmusic/metadata.json index 9400f70e0..f578f1f48 100644 --- a/apps/gbmusic/metadata.json +++ b/apps/gbmusic/metadata.json @@ -2,7 +2,7 @@ "id": "gbmusic", "name": "Gadgetbridge Music Controls", "shortName": "Music Controls", - "version": "0.08", + "version": "0.09", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", "screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}], @@ -14,7 +14,7 @@ "storage": [ {"name":"gbmusic.app.js","url":"app.js"}, {"name":"gbmusic.settings.js","url":"settings.js"}, - {"name":"gbmusic.wid.js","url":"widget.js"}, + {"name":"gbmusic.boot.js","url":"boot.js"}, {"name":"gbmusic.img","url":"icon.js","evaluate":true} ], "data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}] diff --git a/apps/gbmusic/widget.js b/apps/gbmusic/widget.js deleted file mode 100644 index 86bda99a1..000000000 --- a/apps/gbmusic/widget.js +++ /dev/null @@ -1,44 +0,0 @@ -(() => { - if (global.gbmusic_active || !(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart) { - return; - } - if (typeof __FILE__ === 'string') { // only exists since 2v09 - const info = require("Storage").readJSON(__FILE__.split(".")[0]+".info", 1) || false; - if (info && info.type!=="clock") { // info can have no type (but then it isn't a clock) - return; - } - } - - let state, info; - function checkMusic() { - if (state!=="play" || !info) { - return; - } - // playing music: launch music app - require("Storage").writeJSON("gbmusic.load.json", { - state: state, - info: info, - }); - load("gbmusic.app.js"); - } - - const _GB = global.GB; - global.GB = (event) => { - // we eat music events! - switch(event.t) { - case "musicinfo": - info = event; - delete (info.t); - checkMusic(); - break; - case "musicstate": - state = event.state; - checkMusic(); - break; - default: - if (_GB) { - setTimeout(_GB, 0, event); - } - } - }; -})(); diff --git a/apps/gpssetup/README.md b/apps/gpssetup/README.md index 8e64c6a30..8c9445ec9 100644 --- a/apps/gpssetup/README.md +++ b/apps/gpssetup/README.md @@ -107,3 +107,8 @@ try { * Some useful code on Github can be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control) and [here](https://github.com/thasti/utrak/blob/master/gps.c) + + +Written by: [Hugh Barney, with support from Gordon Williams](https://github.com/hughbarney) For support +and discussion please post in the [Bangle JS +Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/kitchen/README.md b/apps/kitchen/README.md index 2a1b148fd..102881d15 100644 --- a/apps/kitchen/README.md +++ b/apps/kitchen/README.md @@ -280,3 +280,8 @@ The following error codes will be displayed if one of the dependancies is not me * Add a small graph to the heart rate monitor app * Add a facility to call the Arrow calibration process * Maybe create waypoints.json file if missing + + +Written by: [Hugh Barney](https://github.com/hughbarney) For support +and discussion please post in the [Bangle JS +Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index a8c55f8fd..7d8fecb1e 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -14,4 +14,5 @@ 0.14: Added altitude as an option to display. 0.15: Using wpedom to count steps. 0.16: Improved stability. Wind can now be shown. -0.17: Settings for mph/kph and other minor improvements. \ No newline at end of file +0.17: Settings for mph/kph and other minor improvements. +0.18: Fullscreen mode can now be enabled or disabled in the settings. \ No newline at end of file diff --git a/apps/lcars/README.md b/apps/lcars/README.md index b6bfda2c1..f979b2304 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -11,7 +11,7 @@ with Gadgetbride and the weather app must be installed. ## Features * LCARS Style watch face. - * Full screen mode - widgets are still loaded but not shown. + * Enable or disable fullscreen mode (widgets are always loaded, but hidden if fullscreen). * Tab on left/right to switch between different screens. * Cusomizable data that is shown on screen 1 (steps, weather etc.) * Shows random and real images of planets. @@ -33,7 +33,7 @@ with Gadgetbride and the weather app must be installed. ## Multiple screens support Access different screens via tap on the left/ right side of the screen -![](screenshot.png) +![](screenshot_1.png) ![](screenshot_2.png) diff --git a/apps/lcars/bg_left_small.png b/apps/lcars/bg_left_small.png new file mode 100644 index 000000000..bfdb110d9 Binary files /dev/null and b/apps/lcars/bg_left_small.png differ diff --git a/apps/lcars/bg_right_small.png b/apps/lcars/bg_right_small.png new file mode 100644 index 000000000..df9d32b38 Binary files /dev/null and b/apps/lcars/bg_right_small.png differ diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index dcbd294bb..7d5da2d8e 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -7,6 +7,7 @@ let settings = { dataRow2: "Temp", dataRow3: "Battery", speed: "kph", + fullscreen: false, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -30,23 +31,39 @@ let lcarsViewPos = 0; // let hrmValue = 0; var plotMonth = false; + /* * Requirements and globals */ -var bgLeft = { +var bgLeftFullscreen = { width : 27, height : 176, bpp : 3, transparent : 0, buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAFCh/eX5Q/KAwdCAGVbtu27YCCoAJBkuWrNlAQRGCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A==")) }; -var bgRight = { +var bgLeftNotFullscreen = { + width : 27, height : 152, bpp : 3, + transparent : 0, + buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAGVbtu27YCCoAJBkuWrNlAQRkCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=")) +}; + +var bgRightFullscreen = { width : 27, height : 176, bpp : 3, transparent : 0, buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAnUP7y/KH4yGeVYAJrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgAA=")) }; +var bgRightNotFullscreen = { + width : 27, height : 152, bpp : 3, + transparent : 0, + buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAxrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgA=")) +}; + +var bgLeft = settings.fullscreen ? bgLeftFullscreen : bgLeftNotFullscreen; +var bgRight= settings.fullscreen ? bgRightFullscreen : bgRightNotFullscreen; + var iconEarth = { width : 50, height : 50, bpp : 3, buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA=")) @@ -217,7 +234,7 @@ function drawHorizontalBgLine(color, x1, x2, y, h){ function drawInfo(){ - if(lcarsViewPos != 0){ + if(lcarsViewPos != 0 || !settings.fullscreen){ return; } @@ -276,9 +293,10 @@ function drawState(){ function drawPosition0(){ // Draw background image - g.drawImage(bgLeft, 0, 0); - drawHorizontalBgLine(cBlue, 25, 120, 0, 4); - drawHorizontalBgLine(cBlue, 130, 176, 0, 4); + var offset = settings.fullscreen ? 0 : 24; + g.drawImage(bgLeft, 0, offset); + drawHorizontalBgLine(cBlue, 25, 120, offset, 4); + drawHorizontalBgLine(cBlue, 130, 176, offset, 4); drawHorizontalBgLine(cPurple, 20, 70, 80, 4); drawHorizontalBgLine(cPurple, 80, 176, 80, 4); drawHorizontalBgLine(cOrange, 35, 110, 87, 4); @@ -304,15 +322,26 @@ function drawPosition0(){ var currentDate = new Date(); var timeStr = locale.time(currentDate,1); g.setFontAntonioLarge(); - g.drawString(timeStr, 27, 10); + if(settings.fullscreen){ + g.drawString(timeStr, 27, 10); + } else { + g.drawString(timeStr, 27, 33); + } // Write date g.setColor(cWhite); g.setFontAntonioMedium(); - var dayStr = locale.dow(currentDate, true).toUpperCase(); - dayStr += " " + currentDate.getDate(); - dayStr += " " + locale.month(currentDate, 1).toUpperCase(); - g.drawString(dayStr, 30, 56); + if(settings.fullscreen){ + var dayStr = locale.dow(currentDate, true).toUpperCase(); + dayStr += " " + currentDate.getDate(); + dayStr += " " + locale.month(currentDate, 1).toUpperCase(); + g.drawString(dayStr, 30, 56); + } else { + var dayStr = locale.dow(currentDate, true).toUpperCase(); + var date = currentDate.getDate(); + g.drawString(dayStr, 128, 35); + g.drawString(date, 128, 55); + } // Draw data g.setFontAlign(-1, -1, 0); @@ -327,8 +356,11 @@ function drawPosition0(){ function drawPosition1(){ // Draw background image - g.drawImage(bgRight, 149, 0); - drawHorizontalBgLine(cBlue, 0, 140, 0, 4); + var offset = settings.fullscreen ? 0 : 24; + g.drawImage(bgRight, 149, offset); + if(settings.fullscreen){ + drawHorizontalBgLine(cBlue, 0, 140, offset, 4); + } drawHorizontalBgLine(cPurple, 0, 80, 80, 4); drawHorizontalBgLine(cPurple, 90, 150, 80, 4); drawHorizontalBgLine(cOrange, 0, 50, 87, 4); @@ -388,8 +420,13 @@ function drawPosition1(){ g.setFontAlign(1, 1, 0); g.setFontAntonioMedium(); g.setColor(cWhite); - g.drawString("M-HRM", 154, 27); - g.drawString("M-STEPS [K]", 154, 115); + + if(settings.fullscreen){ + g.drawString("M-HRM", 154, 27); + g.drawString("M-STEPS [K]", 154, 115); + } else { + g.drawString("MONTH", 154, 115); + } // Plot day } else { @@ -429,8 +466,13 @@ function drawPosition1(){ g.setFontAlign(1, 1, 0); g.setFontAntonioMedium(); g.setColor(cWhite); - g.drawString("D-HRM", 154, 27); - g.drawString("D-STEPS", 154, 115); + + if(settings.fullscreen){ + g.drawString("D-HRM", 154, 27); + g.drawString("D-STEPS", 154, 115); + } else { + g.drawString("DAY", 154, 115); + } } } @@ -451,6 +493,13 @@ function draw(){ } else if (lcarsViewPos == 1) { drawPosition1(); } + + // After drawing the watch face, we can draw the widgets + if(settings.fullscreen){ + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + } else { + Bangle.drawWidgets(); + } } @@ -501,8 +550,9 @@ function getWeather(){ weather.hum = weather.hum + "%"; // Wind - var speedFactor = settings.speed == "kph" ? 1.60934 : 1.0; - weather.wind = Math.round(weather.wind * speedFactor); + const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); + var speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934; + weather.wind = Math.round(wind[1] * speedFactor); return weather } @@ -652,16 +702,7 @@ Bangle.on('touch', function(btn, e){ // Show launcher when middle button pressed Bangle.setUI("clock"); Bangle.loadWidgets(); -/* - * we are not drawing the widgets as we are taking over the whole screen - * so we will blank out the draw() functions of each widget and change the - * area to the top bar doesn't get cleared. - */ -for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} // Clear the screen once, at startup and draw clock g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); draw(); - -// After drawing the watch face, we can draw the widgets -// Bangle.drawWidgets(); diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 176f88593..75add1ece 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -9,6 +9,7 @@ dataRow2: "Steps", dataRow3: "Temp", speed: "kph", + fullscreen: false, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -52,6 +53,14 @@ save(); }, }, + 'Full Screen': { + value: settings.fullscreen, + format: () => (settings.fullscreen ? 'Yes' : 'No'), + onchange: () => { + settings.fullscreen = !settings.fullscreen; + save(); + }, + }, 'Speed': { value: 0 | speedOptions.indexOf(settings.speed), min: 0, max: 1, diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json index a402b35a4..e6ca10f79 100644 --- a/apps/lcars/metadata.json +++ b/apps/lcars/metadata.json @@ -3,13 +3,15 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.17", + "version":"0.18", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", "type": "clock", "tags": "clock", - "screenshots": [{"url":"screenshot.png"}], + "screenshots": [ + {"url":"screenshot_1.png"}, + {"url":"screenshot_3.png"}], "storage": [ {"name":"lcars.app.js","url":"lcars.app.js"}, {"name":"lcars.img","url":"lcars.icon.js","evaluate":true}, diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png deleted file mode 100644 index e2d2cbd5f..000000000 Binary files a/apps/lcars/screenshot.png and /dev/null differ diff --git a/apps/lcars/screenshot_1.png b/apps/lcars/screenshot_1.png new file mode 100644 index 000000000..09a604069 Binary files /dev/null and b/apps/lcars/screenshot_1.png differ diff --git a/apps/lcars/screenshot_2.png b/apps/lcars/screenshot_2.png index 52ad295c4..a7a94db39 100644 Binary files a/apps/lcars/screenshot_2.png and b/apps/lcars/screenshot_2.png differ diff --git a/apps/lcars/screenshot_3.png b/apps/lcars/screenshot_3.png new file mode 100644 index 000000000..931ea15de Binary files /dev/null and b/apps/lcars/screenshot_3.png differ diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 99c867ef2..4811cd19b 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -30,3 +30,4 @@ If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) 0.19: Use a larger font for message text if it'll fit 0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031) +0.21: Improve list readability on dark theme diff --git a/apps/messages/app.js b/apps/messages/app.js index ec147daf8..4aaf97369 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -27,7 +27,7 @@ var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; var colBg = g.theme.dark ? "#141":"#4f4"; var colSBg1 = g.theme.dark ? "#121":"#cFc"; -var colSBg2 = g.theme.dark ? "#242":"#9F9"; +var colSBg2 = g.theme.dark ? "#000":"#9F9"; // hack for 2v10 firmware's lack of ':size' font handling try { g.setFont("6x8:2"); diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 7b6141cae..6834693ae 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.20", + "version": "0.21", "description": "App to display notifications from iOS and Gadgetbridge", "icon": "app.png", "type": "app", diff --git a/apps/mylocation/ChangeLog b/apps/mylocation/ChangeLog index 653f859ae..b9eba67f4 100644 --- a/apps/mylocation/ChangeLog +++ b/apps/mylocation/ChangeLog @@ -1,2 +1,3 @@ 0.01: First release 0.02: Enhanced icon, make it bolder +0.03: Fixed issue with defaulting back to London diff --git a/apps/mylocation/metadata.json b/apps/mylocation/metadata.json index b26a97290..a7fd8356c 100644 --- a/apps/mylocation/metadata.json +++ b/apps/mylocation/metadata.json @@ -4,7 +4,7 @@ "icon": "mylocation.png", "type": "app", "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.02", + "version":"0.03", "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", "readme": "README.md", "tags": "tool,utility", diff --git a/apps/mylocation/mylocation.app.js b/apps/mylocation/mylocation.app.js index fb2f73fa7..27ab17ea5 100644 --- a/apps/mylocation/mylocation.app.js +++ b/apps/mylocation/mylocation.app.js @@ -9,32 +9,35 @@ let s = { 'lat': 51.5072, 'lon': 0.1276, 'location': "London" -} +}; function loadSettings() { - settings = require('Storage').readJSON(SETTINGS_FILE, 1) || s; + settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}; + for (const key in settings) { + s[key] = settings[key] + } } function save() { - settings = s - require('Storage').write(SETTINGS_FILE, settings) + settings = s; + require('Storage').write(SETTINGS_FILE, settings); } -const locations = ["London", "Newcastle", "Edinburgh", "Paris", "New York", "Tokyo","???"]; -const lats = [51.5072 ,54.9783 ,55.9533 ,48.8566 ,40.7128 ,35.6762, 0.0]; -const lons = [-0.1276 ,-1.6178 ,-3.1883 ,2.3522 , -74.0060 ,139.6503, 0.0]; +const locations = ["London" ,"Newcastle","Edinburgh", "Paris" , "New York" , "Tokyo" , "Frankfurt", "Auckland", "???"]; +const lats = [ 51.5072 , 54.9783 , 55.9533 , 48.8566 , 40.7128 , 35.6762 , 50.1236 , -36.9 , 0.0 ]; +const lons = [ -0.1276 , -1.6178 , -3.1883 , 2.3522 , -74.0060 , 139.6503 , 8.6553 , 174.7832 , 0.0 ]; function setFromGPS() { Bangle.on('GPS', (gps) => { //console.log("."); if (gps.fix === 0) return; //console.log("fix from GPS"); - s = {'lat': gps.lat, 'lon': gps.lon, 'location': '???' } + s = {'lat': gps.lat, 'lon': gps.lon, 'location': '???' }; Bangle.buzz(1500); // buzz on first position Bangle.setGPSPower(0); save(); - Bangle.setUI("updown", ()=>{ load() }); + Bangle.setUI("updown", ()=>{ load(); }); E.showPrompt("Location has been saved from the GPS fix",{ title:"Location Saved", buttons : {"OK":1} @@ -49,13 +52,13 @@ function setFromGPS() { } function showMainMenu() { - console.log("showMainMenu"); + //console.log("showMainMenu"); const mainmenu = { '': { 'title': 'My Location' }, '{ load(); }, 'City': { value: 0 | locations.indexOf(s.location), - min: 0, max: 6, + min: 0, max: locations.length - 1, format: v => locations[v], onchange: v => { if (v != 6) { @@ -67,7 +70,7 @@ function showMainMenu() { } }, 'Set From GPS': ()=>{ setFromGPS(); } - } + }; return E.showMenu(mainmenu); } diff --git a/apps/neonx/ChangeLog b/apps/neonx/ChangeLog new file mode 100644 index 000000000..af7f83942 --- /dev/null +++ b/apps/neonx/ChangeLog @@ -0,0 +1 @@ +0.01: Initial release diff --git a/apps/neonx/README.md b/apps/neonx/README.md new file mode 100644 index 000000000..d836dfab3 --- /dev/null +++ b/apps/neonx/README.md @@ -0,0 +1,20 @@ +# Neon X and IO X Clock + +| ![Neon X](neonx-screenshot.png) | ![Neon IO X](neoniox-screenshot.png) | +|---------------------------------|--------------------------------------| +|
Neon X
|
Neon IO X
| + +This is a clock based on Pebble's Neon X and Neon IO X watchfaces by Sam Jerichow. +Can be switched between in the Settings menu, which can be accessed through +the app/widget settings menu of the Bangle.js + +## Settings + +### Neon IO X: +Activate the Neon IO X clock look, a bit hard to read until one gets used to it. + +### Thickness +The thickness of watch lines, from 1 to 5. + +### Date on touch +Shows the current date as DD MM on touch and reverts back to time after 5 seconds or with another touch. diff --git a/apps/neonx/metadata.json b/apps/neonx/metadata.json new file mode 100644 index 000000000..41b16d11b --- /dev/null +++ b/apps/neonx/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "neonx", + "name": "Neon X & IO X Clock", + "shortName": "Neon X Clock", + "version": "0.01", + "description": "Pebble Neon X & Neon IO X for Bangle.js", + "icon": "neonx.png", + "type": "clock", + "readme": "README.md", + "tags": "neonx,neonio,neoniox,clock", + "supports": ["BANGLEJS", "BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url": "neonx-screenshot.png"}, {"url": "neoniox-screenshot.png"}], + "storage": [ + {"name": "neonx.app.js", "url": "neonx.app.js"}, + {"name": "neonx.img", "url": "neonx-icon.js", "evaluate": true}, + {"name": "neonx.settings.js", "url": "neonx.settings.js"} + ], + "data": [{"name": "neonx.json"}] +} diff --git a/apps/neonx/neoniox-screenshot.png b/apps/neonx/neoniox-screenshot.png new file mode 100644 index 000000000..0b33b0819 Binary files /dev/null and b/apps/neonx/neoniox-screenshot.png differ diff --git a/apps/neonx/neonx-icon.js b/apps/neonx/neonx-icon.js new file mode 100644 index 000000000..3ed628b0b --- /dev/null +++ b/apps/neonx/neonx-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAwAAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAABQAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAgABttttttttsAAEkAAAAAAAEkANtttttttttgAEkAAAAAAAEkABttttttttsAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAAgAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkABttttttttsAAAAAAAAAAAEkANtttttttttgAAAAAAAAAAAgABttttttttsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")) diff --git a/apps/neonx/neonx-screenshot.png b/apps/neonx/neonx-screenshot.png new file mode 100644 index 000000000..f18348f52 Binary files /dev/null and b/apps/neonx/neonx-screenshot.png differ diff --git a/apps/neonx/neonx.app.js b/apps/neonx/neonx.app.js new file mode 100644 index 000000000..967fc8582 --- /dev/null +++ b/apps/neonx/neonx.app.js @@ -0,0 +1,153 @@ +/** + * Bangle.js Neon X/IO X Clock + * + * Author: Bundyo + * Repo: https://github.com/bundyo/BangleApps/tree/master/apps/neonx + * Initial code based on Numerals Clock by Raik M. + * Pebble Watchface Author: Sam Jerichow + * Created: February 2022 + */ + +const digits = { + 0:[[15,15,85,15,85,85,15,85,15,15]], + 1:[[85,15,85,85]], + 2:[[15,15,85,15,85,50], [15,50,15,85,85,85]], + 3:[[15,15,85,15,85,85,15,85]], + 4:[[15,15,15,50], [85,15,85,85]], + 5:[[85,15,15,15,15,50], [85,50,85,85,15,85]], + 6:[[85,15,15,15,15,85,85,85,85,50]], + 7:[[15,15,85,15,85,85]], + 8:[[15,15,85,15],[15,85,85,85]], + 9:[[15,50,15,15,85,15,85,85,15,85]], +}; + +const colors = { + x: [ + ["#FF00FF", "#00FFFF"], + ["#00FF00", "#FFFF00"] + ], + io: [ + ["#FF00FF", "#FFFF00"], + ["#00FF00", "#00FFFF"] + ] +}; + +const is12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false; +const screenWidth = g.getWidth(); +const halfWidth = screenWidth / 2; +const scale = screenWidth / 240; +const REFRESH_RATE = 10E3; + +let interval = 0; +let showingDate = false; + +function drawLine(poly, thickness){ + for (let i = 0; i < poly.length; i = i + 2){ + if (poly[i + 2] === undefined) { + break; + } + + if (poly[i] !== poly[i + 2]) { + g.fillRect(poly[i], poly[i + 1] - thickness / 2, poly[i + 2], poly[i + 3] + thickness / 2); + } else { + g.fillRect(poly[i] - thickness / 2, poly[i + 1], poly[i + 2] + thickness / 2, poly[i + 3]); + } + + g.fillCircle(poly[i], poly[i + 1], thickness / 2); + g.fillCircle(poly[i + 2], poly[i + 3], thickness / 2); + } +} + +let settings = require('Storage').readJSON('neonx.json', 1); + +if (!settings) { + settings = { + thickness: 4, + io: 0, + showDate: 1 + }; +} + +function drawClock(num){ + let tx, ty; + + for (let x = 0; x <= 1; x++) { + for (let y = 0; y <= 1; y++) { + const current = ((y + 1) * 2 + x - 1); + let newScale = scale; + + g.setColor(colors[settings.io ? 'io' : 'x'][y][x]); + + if (!settings.io) { + tx = (x * 100 + 18) * newScale; + ty = (y * 100 + 32) * newScale; + } else { + newScale = 0.33 + current * 0.4; + + tx = (halfWidth - 139) * newScale + halfWidth; + ty = (halfWidth - 139) * newScale + halfWidth + 12; + } + + for (let i = 0; i < digits[num[y][x]].length; i++) { + drawLine(g.transformVertices(digits[num[y][x]][i], { x: tx, y: ty, scale: newScale}), settings.thickness); + } + } + } +} + +function draw(date){ + let d = new Date(); + let l1, l2; + + showingDate = date; + + if (date) { + setUpdateInt(0); + + l1 = ('0' + (new Date()).getDate()).substr(-2); + l2 = ('0' + ((new Date()).getMonth() + 1)).substr(-2); + + setTimeout(_ => { + draw(); + setUpdateInt(1); + }, 5000); + } else { + l1 = ('0' + (d.getHours() % (is12hour ? 12 : 24))).substr(-2); + l2 = ('0' + d.getMinutes()).substr(-2); + } + + g.clearRect(0,24,240,240); + + drawClock([l1, l2]); +} + +function setUpdateInt(set){ + if (interval) { + clearInterval(interval); + } + + if (set) { + interval = setInterval(draw, REFRESH_RATE); + } +} + +g.clear(1); + +Bangle.setUI("clock"); + +setUpdateInt(1); +draw(); + +if (settings.showDate) { + Bangle.on('touch', () => draw(!showingDate)); +} + +Bangle.on('lcdPower', function(on){ + if (on){ + draw(); + setUpdateInt(1); + } else setUpdateInt(0); +}); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/neonx/neonx.png b/apps/neonx/neonx.png new file mode 100644 index 000000000..5c7563051 Binary files /dev/null and b/apps/neonx/neonx.png differ diff --git a/apps/neonx/neonx.settings.js b/apps/neonx/neonx.settings.js new file mode 100644 index 000000000..0e205e03b --- /dev/null +++ b/apps/neonx/neonx.settings.js @@ -0,0 +1,54 @@ +(function(back) { + function updateSettings() { + storage.write('neonx.json', neonXSettings); + } + + function resetSettings() { + neonXSettings = { + thickness: 4, + io: 0, + showDate: 1 + }; + + updateSettings(); + } + + let neonXSettings = storage.readJSON('neonx.json',1); + + if (!neonXSettings) resetSettings(); + + let thicknesses = [1, 2, 3, 4, 5]; + + const menu = { + "" : { "title":"Neon X & IO"}, + "< Back": back, + "Neon IO X": { + value: 0 | neonXSettings.io, + min: 0, max: 1, + format: v => v ? "On" : "Off", + onchange: v => { + neonXSettings.io = v; + updateSettings(); + } + }, + "Thickness": { + value: 0 | thicknesses.indexOf(neonXSettings.thickness), + min: 0, max: thicknesses.length - 1, + format: v => thicknesses[v], + onchange: v => { + neonXSettings.thickness = thicknesses[v]; + updateSettings(); + } + }, + "Date on touch": { + value: 0 | neonXSettings.showDate, + min: 0, max: 1, + format: v => v ? "On" : "Off", + onchange: v => { + neonXSettings.showDate = v; + updateSettings(); + } + } + }; + E.showMenu(menu); +}) diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index 570c37507..d133697b3 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -13,3 +13,6 @@ which requires 2.11.27 firmware to reset at midnight 0.13: call process.memory(false) to avoid triggering a GC of memory supported in pre 2.12.13 firmware +0.14: incorporated lazybones idle timer, configuration settings to come +0.15: fixed tendancy for mylocation to default to London + added setting to enable/disable idle timer warning diff --git a/apps/pastel/metadata.json b/apps/pastel/metadata.json index 0a04491fc..da3c18eae 100644 --- a/apps/pastel/metadata.json +++ b/apps/pastel/metadata.json @@ -2,8 +2,8 @@ "id": "pastel", "name": "Pastel Clock", "shortName": "Pastel", - "version": "0.13", - "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times.", + "version": "0.15", + "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times", "icon": "pastel.png", "dependencies": {"mylocation":"app","weather":"app"}, "screenshots": [{"url":"screenshot_pastel.png"}, {"url":"weather_icons.png"}], diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 5def5737c..605b78ad0 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -4,9 +4,18 @@ const storage = require('Storage'); const locale = require("locale"); const SETTINGS_FILE = "pastel.json"; const LOCATION_FILE = "mylocation.json"; +const w = g.getWidth(); +const h = g.getHeight(); let settings; let location; +// variable for controlling idle alert +let lastStep = getTime(); +let lastStepTime = '??'; +let warned = 0; +let idle = false; +let IDLE_MINUTES = 26; + // cloud, sun, partSun, snow, rain, storm, error // create 1 bit, max contrast, brightness set to 85 var cloudIcon = require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA==")); @@ -16,16 +25,24 @@ var snowIcon = require("heatshrink").decompress(atob("kEggITQj/AAYM98ADBsEwAYPAj var rainIcon = require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA==")); var errIcon = require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A==")); +// saves having to recode all the small font calls +function setSmallFont() { + g.setFontLatoSmall(); +} function loadSettings() { settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; settings.grid = settings.grid||false; settings.font = settings.font||"Lato"; + settings.idle_check = settings.idle_check||true; } // requires the myLocation app function loadLocation() { - location = require("Storage").readJSON(LOCATION_FILE,1)||{"lat":51.5072,"lon":0.1276,"location":"London"}; + location = require("Storage").readJSON(LOCATION_FILE,1)||{}; + location.lat = location.lat||51.5072; + location.lon = location.lon||0.1276; + location.location = location.location||"London"; } function extractTime(d){ @@ -71,17 +88,18 @@ function getSteps() { if (WIDGETS.wpedom !== undefined) return WIDGETS.wpedom.getSteps(); else - return '???' + return '???'; } } const infoData = { ID_BLANK: { calc: () => '' }, - ID_DATE: { calc: () => {var d = (new Date).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} }, - ID_DAY: { calc: () => {var d = require("locale").dow(new Date).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, + ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} }, + ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, ID_SR: { calc: () => 'Sunrise: ' + sunRise }, ID_SS: { calc: () => 'Sunset: ' + sunSet }, ID_STEP: { calc: () => 'Steps: ' + getSteps() }, + ID_LAST: { calc: () => 'Last Step: ' + lastStepTime }, ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' }, ID_MEM: { calc: () => {var val = process.memory(false); return 'Ram: ' + Math.round(val.usage*100/val.total) + '%';} }, ID_ID: { calc: () => {var val = NRF.getAddress().split(':'); return 'Id: ' + val[4] + val[5];} }, @@ -152,6 +170,14 @@ function getWeather() { } function draw() { + if (!idle) + drawClock(); + else + drawIdle(); + queueDraw(); +} + +function drawClock() { var d = new Date(); var da = d.toString().split(" "); var time = da[4].substr(0,5); @@ -166,11 +192,8 @@ function draw() { if (parseInt(hh) > 12) hh = h2.substr(h2.length -2); - var w = g.getWidth(); - var h = g.getHeight(); var x = (g.getWidth()/2); var y = (g.getHeight()/3); - var weatherJson = getWeather(); var w_temp; var w_icon; @@ -190,7 +213,8 @@ function draw() { } g.reset(); - g.clearRect(0, 30, w, h - 24); + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); // draw a grid like graph paper if (settings.grid && process.env.HWVERSION !=1) { @@ -249,6 +273,141 @@ function draw() { queueDraw(); } + +///////////////// IDLE TIMER ///////////////////////////////////// + +function log_debug(o) { + //print(o); +} + +function drawIdle() { + let mins = Math.round((getTime() - lastStep) / 60); + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); + g.setColor(g.theme.fg); + setSmallFont(); + g.setFontAlign(0, 0); + g.drawString('Last step was', w/2, (h/3)); + g.drawString(mins + ' minutes ago', w/2, 20+(h/3)); + dismissBtn.draw(); +} + +/////////////// BUTTON CLASS /////////////////////////////////////////// + +// simple on screen button class +function BUTTON(name,x,y,w,h,c,f,tx) { + this.name = name; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.color = c; + this.callback = f; + this.text = tx; +} + +// if pressed the callback +BUTTON.prototype.check = function(x,y) { + //console.log(this.name + ":check() x=" + x + " y=" + y +"\n"); + + if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { + log_debug(this.name + ":callback\n"); + this.callback(); + return true; + } + return false; +}; + +BUTTON.prototype.draw = function() { + g.setColor(this.color); + g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); + g.setColor("#000"); // the icons and boxes are drawn black + setSmallFont(); + g.setFontAlign(0, 0); + g.drawString(this.text, (this.x + this.w/2), (this.y + this.h/2)); + g.drawRect(this.x, this.y, (this.x + this.w), (this.y + this.h)); +}; + +function dismissPrompt() { + idle = false; + warned = false; + lastStep = getTime(); + Bangle.buzz(100); + draw(); +} + +var dismissBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", dismissPrompt, "Dismiss"); + +Bangle.on('touch', function(button, xy) { + if (idle && dismissBtn.check(xy.x, xy.y)) return; +}); + +// if we get a step then we are not idle +Bangle.on('step', s => { + setLastStepTime(); + lastStep = getTime(); + // redraw if we had been idle + if (idle == true) { + dismissPrompt(); + } + idle = false; + warned = 0; +}); + +function setLastStepTime() { + var date = new Date(); + lastStepTime = require("locale").time(date,1); +} + +function checkIdle() { + if (!settings.idle_check) { + idle = false; + warned = false; + return; + } + + let hour = (new Date()).getHours(); + let active = (hour >= 9 && hour < 21); + //let active = true; + let dur = getTime() - lastStep; + + if (active && dur > IDLE_MINUTES * 60) { + drawIdle(); + if (warned++ < 3) { + buzzer(warned); + log_debug("checkIdle: warned=" + warned); + Bangle.setLocked(false); + } + idle = true; + } else { + idle = false; + warned = 0; + } +} + +setLastStepTime(); + +// timeout for multi-buzzer +var buzzTimeout; + +// n buzzes +function buzzer(n) { + log_debug("buzzer n=" + n); + + if (n-- < 1) return; + Bangle.buzz(250); + + if (buzzTimeout) clearTimeout(buzzTimeout); + buzzTimeout = setTimeout(function() { + buzzTimeout = undefined; + buzzer(n); + }, 500); +} + + +/////////////////////////////////////////////////////////////////////////////// + // timeout used to update every minute var drawTimeout; @@ -258,6 +417,7 @@ function queueDraw() { drawTimeout = setTimeout(function() { drawTimeout = undefined; prevInfo(); + checkIdle(); draw(); }, 60000 - (Date.now() % 60000)); } diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index bf83fa7c2..26dafd271 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -5,6 +5,7 @@ let s = { 'grid': false, 'weather': false, + 'idle_check': true, 'font': "Lato" } @@ -51,6 +52,24 @@ s.weather = !s.weather; save(); }, + }, + // for use when the new menu system goes live + /* + 'Idle Warning': { + value: s.idle_check, + onchange : v => { + s.idle_check = v; + save(); + }, + }, + */ + 'Idle Warning': { + value: s.idle_check, + format: () => (s.idle_check ? 'Yes' : 'No'), + onchange: () => { + s.idle_check = !s.idle_check; + save(); + }, } }) }) diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html index 08021374d..8cf339e85 100644 --- a/apps/recorder/interface.html +++ b/apps/recorder/interface.html @@ -4,8 +4,11 @@
+
+ + - + + + + + + - - + diff --git a/core b/core index 187af1527..bf29f5697 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 187af1527e0b830c804049aae834ed658ffeed08 +Subproject commit bf29f5697445686255a785476e6b1ed6a13ff697